feat: implement flexible payment processing with partial cash payments and automatic balance deduction in reports
This commit is contained in:
+180
-7
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user