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 { 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, }; }, };