admin roadmap trips ops and payment ops

This commit is contained in:
2026-05-18 19:25:32 +07:00
parent e1966b69f1
commit 4bcb93e283
22 changed files with 1586 additions and 188 deletions
+21 -6
View File
@@ -404,9 +404,10 @@ export const tripService = {
},
/**
* Organizer batalkan trip (Trip.status = CLOSED). Atomic dalam satu
* serializable transaction:
* - Set Trip.status = CLOSED.
* Batalkan trip (Trip.status = CLOSED). Bisa dipicu organizer sendiri ATAU
* admin (intervensi). Atomic dalam satu serializable transaction:
* - Set Trip.status = CLOSED (+ `cancelledByAdminId` & `cancelledReason`
* kalau actor = ADMIN).
* - Untuk setiap peserta aktif:
* - Booking PAID → buat Refund ORGANIZER_CANCELLED (auto-approved, full
* amount). Booking tetap PAID sampai admin mark SUCCEEDED — jejak
@@ -420,7 +421,12 @@ export const tripService = {
* Idempotent: trip yang sudah CLOSED/COMPLETED akan ditolak supaya tidak
* dobel-buat refund.
*/
async closeTrip(tripId: string, organizerId: string) {
async closeTrip(
tripId: string,
actor:
| { type: "ORGANIZER"; userId: string }
| { type: "ADMIN"; adminId: string; reason: string }
) {
let lastErr: unknown;
for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) {
try {
@@ -433,7 +439,10 @@ export const tripService = {
if (!trip) {
throw new Error("Trip tidak ditemukan");
}
if (trip.organizerId !== organizerId) {
if (
actor.type === "ORGANIZER" &&
trip.organizerId !== actor.userId
) {
throw new Error(
"Hanya organizer trip ini yang bisa membatalkan trip"
);
@@ -544,7 +553,13 @@ export const tripService = {
await tx.trip.update({
where: { id: tripId },
data: { status: "CLOSED" },
data: {
status: "CLOSED",
...(actor.type === "ADMIN" && {
cancelledByAdminId: actor.adminId,
cancelledReason: actor.reason,
}),
},
});
return {