Files
setrip/server/services/system-health.service.ts
T

73 lines
2.1 KiB
TypeScript

import { prisma } from "@/lib/prisma";
const HOUR_MS = 60 * 60 * 1000;
const DAY_MS = 24 * HOUR_MS;
export interface StaleSummary {
/** Payment MIDTRANS status AWAITING > 25 jam (lewat expiresAt) — webhook gagal? */
stalePaymentsCount: number;
/** Booking AWAITING_PAY tapi trip sudah lewat hari ini — peserta lupa bayar. */
awaitingPayPastDepartureCount: number;
/** Payout HELD tapi heldUntil sudah lebih 1 hari lewat — cron release tidak jalan? */
overduePayoutsCount: number;
/** Refund APPROVED > 7 hari belum di-process — admin lupa? */
stuckRefundsCount: number;
}
/**
* Deteksi entity yang nyangkut di state non-final terlalu lama. Dipanggil dari
* `/admin/system` page on-demand (bukan cron) supaya selalu show realtime.
*
* Threshold draft — review setelah jalan 1-2 minggu (false positive vs miss).
*/
export const systemHealthService = {
async detectStale(): Promise<StaleSummary> {
const now = new Date();
const twentyFiveHoursAgo = new Date(now.getTime() - 25 * HOUR_MS);
const oneDayAgo = new Date(now.getTime() - DAY_MS);
const sevenDaysAgo = new Date(now.getTime() - 7 * DAY_MS);
const todayStart = new Date(now);
todayStart.setUTCHours(0, 0, 0, 0);
const [
stalePayments,
awaitingPayPast,
overduePayouts,
stuckRefunds,
] = await Promise.all([
prisma.payment.count({
where: {
provider: "MIDTRANS",
status: "AWAITING",
createdAt: { lte: twentyFiveHoursAgo },
},
}),
prisma.booking.count({
where: {
status: "AWAITING_PAY",
trip: { date: { lt: todayStart } },
},
}),
prisma.payout.count({
where: {
status: "HELD",
heldUntil: { lte: oneDayAgo },
},
}),
prisma.refund.count({
where: {
status: "APPROVED",
reviewedAt: { lte: sevenDaysAgo },
},
}),
]);
return {
stalePaymentsCount: stalePayments,
awaitingPayPastDepartureCount: awaitingPayPast,
overduePayoutsCount: overduePayouts,
stuckRefundsCount: stuckRefunds,
};
},
};