feat: implement flexible payment processing with partial cash payments and automatic balance deduction in reports

This commit is contained in:
Firman Ramdhani
2026-09-03 14:26:45 +07:00
parent 83a09a4c03
commit d32e7eda91
9 changed files with 693 additions and 128 deletions
+180 -7
View File
@@ -381,36 +381,151 @@ export async function getOrderDetail(order_id: string) {
})
}
export async function updateSubmissionPayment(submission_id: string, bill: number | null, payment_status: string) {
export async function updateSubmissionPayment(submission_id: string, bill: number | null, payment_status: string, paid_amount: number | null = null) {
try {
await prisma.submission.update({
where: { id: submission_id },
data: { bill, payment_status }
data: { bill, payment_status, paid_amount }
})
revalidatePath(`/my-orders`)
revalidatePath(`/my-purchases`)
revalidatePath(`/reports`)
revalidatePath(`/balances`)
return { success: true }
} catch (e) {
console.error(e); return { success: false, error: 'Gagal menyimpan tagihan.' }
}
}
export async function updateBulkSubmissionPayment(submission_ids: string[], payment_status: string) {
export async function updateBulkSubmissionPayment(submission_ids: string[], payment_status: string, use_balance: boolean = false) {
try {
await prisma.submission.updateMany({
where: { id: { in: submission_ids } },
data: { payment_status }
})
if (payment_status === 'LUNAS') {
const submissions = await prisma.submission.findMany({ where: { id: { in: submission_ids } } })
const ops = submissions.map(sub => prisma.submission.update({
where: { id: sub.id },
data: {
payment_status,
paid_amount: use_balance ? 0 : (sub.paid_amount != null ? sub.paid_amount : sub.bill)
}
}))
await prisma.$transaction(ops)
} else {
await prisma.submission.updateMany({
where: { id: { in: submission_ids } },
data: { payment_status }
})
}
revalidatePath(`/my-orders`)
revalidatePath(`/my-purchases`)
revalidatePath(`/reports`)
revalidatePath(`/balances`)
return { success: true }
} catch (e) {
console.error(e)
return { success: false, error: 'Gagal mengubah status tagihan massal.' }
}
}
export async function processBulkPayment(data: {
submission_ids: string[];
cash_amount: number;
use_balance: boolean;
creator_id: string;
user_id: string;
}) {
try {
let currentBalance = 0
if (data.use_balance) {
const balanceData = await getBalancesAsCreator(data.creator_id)
const userBalance = balanceData.find(b => b.user.id === data.user_id)
if (userBalance) currentBalance = userBalance.amount
}
const submissions = await prisma.submission.findMany({
where: { id: { in: data.submission_ids } },
include: { order: { select: { date: true } } }
})
// Sort by order date ascending (oldest first)
submissions.sort((a, b) => new Date(a.order.date).getTime() - new Date(b.order.date).getTime())
let remainingBalance = currentBalance
let remainingCash = data.cash_amount
let totalAvailable = remainingBalance + remainingCash
const ops = []
for (let i = 0; i < submissions.length; i++) {
const sub = submissions[i]
const isLast = i === submissions.length - 1
const subBill = sub.bill || 0
const prevPaid = sub.paid_amount || 0
const amountToCover = Math.max(0, subBill - prevPaid)
if (totalAvailable >= amountToCover && amountToCover > 0) {
// Fully covered -> LUNAS
let balanceToUse = Math.min(remainingBalance, amountToCover)
remainingBalance -= balanceToUse
let cashToUse = amountToCover - balanceToUse
remainingCash -= cashToUse
totalAvailable -= amountToCover
let finalPaid = prevPaid + cashToUse
if (isLast && remainingCash > 0) {
finalPaid += remainingCash
remainingCash = 0
}
ops.push(prisma.submission.update({
where: { id: sub.id },
data: { payment_status: 'LUNAS', paid_amount: finalPaid }
}))
} else if (totalAvailable >= amountToCover && amountToCover === 0) {
// It's already fully paid somehow, just mark LUNAS. Give excess cash if last.
let finalPaid = prevPaid
if (isLast && remainingCash > 0) {
finalPaid += remainingCash
remainingCash = 0
}
ops.push(prisma.submission.update({
where: { id: sub.id },
data: { payment_status: 'LUNAS', paid_amount: finalPaid }
}))
} else if (totalAvailable > 0) {
// Partially covered -> BELUM_BAYAR. Only use cash.
let finalPaid = prevPaid + remainingCash
remainingCash = 0
totalAvailable = remainingBalance // only balance left, which can't be used
ops.push(prisma.submission.update({
where: { id: sub.id },
data: { payment_status: 'BELUM_BAYAR', paid_amount: finalPaid }
}))
} else {
// totalAvailable == 0. No more money. Just leave it as is, or update to BELUM_BAYAR.
ops.push(prisma.submission.update({
where: { id: sub.id },
data: { payment_status: 'BELUM_BAYAR' }
}))
}
}
await prisma.$transaction(ops)
revalidatePath(`/my-orders`)
revalidatePath(`/my-purchases`)
revalidatePath(`/reports`)
revalidatePath(`/balances`)
return { success: true }
} catch (e) {
console.error(e)
return { success: false, error: 'Gagal memproses pembayaran massal cerdas.' }
}
}
// === SUBMISSION (PESANAN SAYA) ===
export async function getUserSubmission(order_id: string, user_id: string) {
return await prisma.submission.findFirst({
@@ -524,3 +639,61 @@ export async function getCreatorReport(creator_id: string, startDate?: Date, end
orderBy: { date: 'desc' }
})
}
// === BALANCE ACTIONS ===
export async function getBalancesAsCreator(creator_id: string) {
const submissions = await prisma.submission.findMany({
where: {
order: { creator_id },
payment_status: 'LUNAS'
},
include: {
user: { select: { id: true, name: true, photo: true } }
}
})
const balanceMap = new Map<string, { user: any, amount: number }>()
submissions.forEach(sub => {
if (sub.paid_amount == null || sub.bill == null) return
const diff = sub.paid_amount - sub.bill
if (!balanceMap.has(sub.user.id)) {
balanceMap.set(sub.user.id, { user: sub.user, amount: diff })
} else {
balanceMap.get(sub.user.id)!.amount += diff
}
})
return Array.from(balanceMap.values()).filter(b => b.amount !== 0)
}
export async function getBalancesAsSubmittor(user_id: string) {
const submissions = await prisma.submission.findMany({
where: {
user_id,
payment_status: 'LUNAS'
},
include: {
order: {
include: {
creator: { select: { id: true, name: true, photo: true } }
}
}
}
})
const balanceMap = new Map<string, { creator: any, amount: number }>()
submissions.forEach(sub => {
if (sub.paid_amount == null || sub.bill == null) return
const diff = sub.paid_amount - sub.bill
const creator = sub.order.creator
if (!balanceMap.has(creator.id)) {
balanceMap.set(creator.id, { creator: creator, amount: diff })
} else {
balanceMap.get(creator.id)!.amount += diff
}
})
return Array.from(balanceMap.values()).filter(b => b.amount !== 0)
}