feat: add PiutangManager component for handling receivables and bulk payments
This commit is contained in:
+92
-30
@@ -490,6 +490,11 @@ export async function processBulkPayment(data: {
|
||||
user_id: string
|
||||
}) {
|
||||
try {
|
||||
const me = await getSessionUser()
|
||||
if (!me || me.id !== data.creator_id) {
|
||||
return { success: false, error: 'Unauthorized' }
|
||||
}
|
||||
|
||||
let currentBalance = 0
|
||||
if (data.use_balance) {
|
||||
const balanceData = await getBalancesAsCreator(data.creator_id)
|
||||
@@ -499,7 +504,7 @@ export async function processBulkPayment(data: {
|
||||
|
||||
const submissions = await prisma.submission.findMany({
|
||||
where: { id: { in: data.submission_ids } },
|
||||
include: { order: { select: { date: true } } },
|
||||
include: { order: { select: { date: true, status: true } } },
|
||||
})
|
||||
|
||||
// Sort by order date ascending (oldest first)
|
||||
@@ -515,11 +520,31 @@ export async function processBulkPayment(data: {
|
||||
const sub = submissions[i]
|
||||
const isLast = i === submissions.length - 1
|
||||
|
||||
// Don't process if order isn't CLOSE
|
||||
if (sub.order.status !== 'CLOSE') continue
|
||||
|
||||
const subBill = sub.bill || 0
|
||||
const prevPaid = sub.paid_amount || 0
|
||||
const amountToCover = Math.max(0, subBill - prevPaid)
|
||||
const prevSaldoUsed = sub.saldo_used || 0
|
||||
const amountToCover = Math.max(0, subBill - prevPaid - prevSaldoUsed)
|
||||
|
||||
if (totalAvailable >= amountToCover && amountToCover > 0) {
|
||||
if (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 },
|
||||
})
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (totalAvailable >= amountToCover) {
|
||||
// Fully covered -> LUNAS
|
||||
let balanceToUse = Math.min(remainingBalance, amountToCover)
|
||||
remainingBalance -= balanceToUse
|
||||
@@ -533,44 +558,31 @@ export async function processBulkPayment(data: {
|
||||
finalPaid += remainingCash
|
||||
remainingCash = 0
|
||||
}
|
||||
let finalSaldoUsed = prevSaldoUsed + balanceToUse
|
||||
|
||||
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 },
|
||||
data: { payment_status: 'LUNAS', paid_amount: finalPaid, saldo_used: finalSaldoUsed },
|
||||
})
|
||||
)
|
||||
} 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
|
||||
// Partially covered -> BELUM_BAYAR
|
||||
let balanceToUse = Math.min(remainingBalance, totalAvailable)
|
||||
remainingBalance -= balanceToUse
|
||||
|
||||
let cashToUse = totalAvailable - balanceToUse
|
||||
remainingCash -= cashToUse
|
||||
|
||||
let finalPaid = prevPaid + cashToUse
|
||||
let finalSaldoUsed = prevSaldoUsed + balanceToUse
|
||||
|
||||
totalAvailable = 0
|
||||
|
||||
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' },
|
||||
data: { payment_status: 'BELUM_BAYAR', paid_amount: finalPaid, saldo_used: finalSaldoUsed },
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -873,3 +885,53 @@ export async function broadcastToMattermost(orderId: string, orderUrl: string) {
|
||||
return { success: false, error: 'Gagal mengirim ke Mattermost' }
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPiutangSummary(creator_id: string) {
|
||||
const unpaidSubmissions = await prisma.submission.findMany({
|
||||
where: {
|
||||
order: {
|
||||
creator_id,
|
||||
status: 'CLOSE',
|
||||
},
|
||||
payment_status: 'BELUM_BAYAR',
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: { id: true, name: true, photo: true },
|
||||
},
|
||||
order: {
|
||||
select: { id: true, title: true, date: true },
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
order: { date: 'asc' },
|
||||
},
|
||||
})
|
||||
|
||||
const grouped = new Map<string, any>()
|
||||
|
||||
unpaidSubmissions.forEach((sub) => {
|
||||
if (!grouped.has(sub.user_id)) {
|
||||
grouped.set(sub.user_id, {
|
||||
user: sub.user,
|
||||
totalPiutang: 0,
|
||||
submissions: [],
|
||||
})
|
||||
}
|
||||
|
||||
const u = grouped.get(sub.user_id)
|
||||
|
||||
const paidAmount = (sub.paid_amount || 0) + (sub.saldo_used || 0)
|
||||
const unpaid = Math.max(0, (sub.bill || 0) - paidAmount)
|
||||
|
||||
if (unpaid > 0) {
|
||||
u.totalPiutang += unpaid
|
||||
u.submissions.push({
|
||||
...sub,
|
||||
unpaidAmount: unpaid
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(grouped.values()).filter(g => g.totalPiutang > 0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user