create public layout and admin and fix escrow and refund

This commit is contained in:
arifal
2026-05-12 00:05:30 +07:00
parent a07942c4b4
commit 958514d575
48 changed files with 1928 additions and 18 deletions
@@ -0,0 +1,47 @@
-- CreateEnum
CREATE TYPE "PayoutStatus" AS ENUM ('HELD', 'RELEASED', 'PAID', 'CANCELLED');
-- CreateTable
CREATE TABLE "Payout" (
"id" TEXT NOT NULL,
"bookingId" TEXT NOT NULL,
"tripId" TEXT NOT NULL,
"organizerId" TEXT NOT NULL,
"amount" INTEGER NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IDR',
"status" "PayoutStatus" NOT NULL DEFAULT 'HELD',
"heldUntil" TIMESTAMP(3) NOT NULL,
"releasedAt" TIMESTAMP(3),
"paidAt" TIMESTAMP(3),
"cancelledAt" TIMESTAMP(3),
"bankName" TEXT,
"bankAccountNumber" TEXT,
"bankAccountName" TEXT,
"adminNote" TEXT,
"processedById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Payout_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Payout_bookingId_key" ON "Payout"("bookingId");
-- CreateIndex
CREATE INDEX "Payout_organizerId_status_idx" ON "Payout"("organizerId", "status");
-- CreateIndex
CREATE INDEX "Payout_status_heldUntil_idx" ON "Payout"("status", "heldUntil");
-- AddForeignKey
ALTER TABLE "Payout" ADD CONSTRAINT "Payout_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payout" ADD CONSTRAINT "Payout_tripId_fkey" FOREIGN KEY ("tripId") REFERENCES "Trip"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payout" ADD CONSTRAINT "Payout_organizerId_fkey" FOREIGN KEY ("organizerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payout" ADD CONSTRAINT "Payout_processedById_fkey" FOREIGN KEY ("processedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+75
View File
@@ -34,6 +34,11 @@ model User {
reviewedRefunds Refund[] @relation("RefundReviewer")
/// Payout yang diterima user ini sebagai organizer (escrow trip selesai).
payouts Payout[] @relation("PayoutOrganizer")
/// Payout yang ditandai admin sebagai PAID/CANCELLED oleh user ini.
processedPayouts Payout[] @relation("PayoutProcessor")
profile UserProfile?
}
@@ -161,6 +166,7 @@ model Trip {
images TripImage[]
reviews TripReview[]
bookings Booking[]
payouts Payout[]
@@index([category, status, date])
@@index([vibe, status, date])
@@ -261,6 +267,7 @@ model Booking {
payments Payment[]
refunds Refund[]
payout Payout?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -436,3 +443,71 @@ enum RefundReporter {
PARTICIPANT
ORGANIZER
}
/// Escrow payout ke organizer. Uang peserta ditahan sejak Booking → PAID sampai
/// trip selesai + buffer beberapa hari, baru di-release untuk ditransfer admin.
///
/// State machine:
/// HELD → diciptakan saat booking PAID, heldUntil = endDate/date + 3 hari
/// RELEASED → cron flip setelah heldUntil lewat + trip COMPLETED
/// PAID → admin sudah transfer manual ke rekening organizer
/// CANCELLED → booking di-refund / trip dibatalkan; payout tidak jadi
///
/// Audit: 1-1 dengan Booking (unique). Refund SUCCEEDED mengurangi amount
/// (partial) atau membatalkan payout (full).
model Payout {
id String @id @default(cuid())
bookingId String @unique
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Restrict)
tripId String
trip Trip @relation(fields: [tripId], references: [id])
organizerId String
organizer User @relation("PayoutOrganizer", fields: [organizerId], references: [id])
/// Nominal yg organizer terima (IDR integer). Default = booking.amount saat
/// payout dibuat. Refund SUCCEEDED memotong nilai ini supaya total payout +
/// total refund = uang yang dibayar peserta.
amount Int
currency String @default("IDR")
status PayoutStatus @default(HELD)
/// Tanggal payout boleh di-release ke organizer
/// (= trip.endDate ?? trip.date + buffer days).
heldUntil DateTime
releasedAt DateTime?
paidAt DateTime?
cancelledAt DateTime?
/// Snapshot bank info organizer dari OrganizerVerification saat payout dibuat.
/// Disimpan inline supaya audit-friendly walau organizer ganti bank nanti.
bankName String?
bankAccountNumber String?
bankAccountName String?
/// Catatan admin: referensi transfer manual, alasan cancel, dst.
adminNote String?
/// Admin yang menandai PAID/CANCELLED.
processedById String?
processedBy User? @relation("PayoutProcessor", fields: [processedById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizerId, status])
@@index([status, heldUntil])
}
enum PayoutStatus {
/// Menunggu trip selesai + buffer beberapa hari sebelum boleh ditransfer.
HELD
/// Buffer lewat & trip COMPLETED, siap di-transfer admin ke rekening organizer.
RELEASED
/// Admin sudah transfer ke rekening organizer.
PAID
/// Booking di-refund penuh / trip dibatalkan — uang tidak jadi ke organizer.
CANCELLED
}