import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); const OPENING_EVENT_TYPES = new Set(["OPENING_COST"]); const SALE_EVENT_TYPES = new Set(["SALE_REVENUE", "CONSIGNMENT_REVENUE"]); const RETURN_EVENT_TYPES = new Set(["SALE_RETURN", "CONSIGNMENT_RETURN"]); const COST_ADDITIONAL_EVENT_TYPES = new Set([ "WASHING_COST", "WASHING_SHRINKAGE", "TRANSFORMATION_SHRINKAGE", "SALE_SHRINKAGE", "CONSIGNMENT_SHRINKAGE", "STOCK_ADJUSTMENT_LOSS", "MANUAL_ADJUSTMENT" ]); const roundQty = (value) => Number(value.toFixed(3)); const roundAmount = (value) => Number(value.toFixed(2)); async function recalcSummary(purchaseId, agentSharePercent) { const entries = await prisma.purchaseRealizationEntry.findMany({ where: { purchaseId } }); const aggregate = entries.reduce( (state, entry) => { const qtyIn = entry.qtyIn.toNumber(); const qtyOut = entry.qtyOut.toNumber(); const qtyShrinkage = entry.qtyShrinkage.toNumber(); const amountCost = entry.amountCost.toNumber(); const amountRevenue = entry.amountRevenue.toNumber(); const amountExpense = entry.amountExpense.toNumber(); const agentAmount = entry.agentAmount.toNumber(); state.qtyRemaining += qtyIn - qtyOut - qtyShrinkage; state.profitLossTotal += amountRevenue - amountCost - amountExpense; state.agentProfitTotal += agentAmount; if (OPENING_EVENT_TYPES.has(entry.eventType)) { state.qtyOpening += qtyIn; state.costOpeningTotal += amountCost; } if (SALE_EVENT_TYPES.has(entry.eventType)) { state.qtySold += qtyOut; state.revenueTotal += amountRevenue; } if (RETURN_EVENT_TYPES.has(entry.eventType)) { state.qtyReturned += qtyIn; } if (COST_ADDITIONAL_EVENT_TYPES.has(entry.eventType)) { state.costAdditionalTotal += amountCost + amountExpense; } state.qtyShrinkage += qtyShrinkage; return state; }, { qtyOpening: 0, qtyRemaining: 0, qtySold: 0, qtyReturned: 0, qtyShrinkage: 0, costOpeningTotal: 0, costAdditionalTotal: 0, revenueTotal: 0, profitLossTotal: 0, agentProfitTotal: 0 } ); const status = aggregate.qtyOpening <= 0 ? "OPEN" : aggregate.qtyRemaining <= 0 ? "READY_TO_CLOSE" : aggregate.qtySold > 0 || aggregate.qtyShrinkage > 0 ? "PARTIAL" : "OPEN"; await prisma.purchaseRealizationSummary.upsert({ where: { purchaseId }, update: { status, qtyOpening: roundQty(aggregate.qtyOpening), qtyRemaining: roundQty(aggregate.qtyRemaining), qtySold: roundQty(aggregate.qtySold), qtyReturned: roundQty(aggregate.qtyReturned), qtyShrinkage: roundQty(aggregate.qtyShrinkage), costOpeningTotal: roundAmount(aggregate.costOpeningTotal), costAdditionalTotal: roundAmount(aggregate.costAdditionalTotal), revenueTotal: roundAmount(aggregate.revenueTotal), profitLossTotal: roundAmount(aggregate.profitLossTotal), agentSharePercent, agentProfitTotal: roundAmount(aggregate.agentProfitTotal), closedAt: null }, create: { purchaseId, status, qtyOpening: roundQty(aggregate.qtyOpening), qtyRemaining: roundQty(aggregate.qtyRemaining), qtySold: roundQty(aggregate.qtySold), qtyReturned: roundQty(aggregate.qtyReturned), qtyShrinkage: roundQty(aggregate.qtyShrinkage), costOpeningTotal: roundAmount(aggregate.costOpeningTotal), costAdditionalTotal: roundAmount(aggregate.costAdditionalTotal), revenueTotal: roundAmount(aggregate.revenueTotal), profitLossTotal: roundAmount(aggregate.profitLossTotal), agentSharePercent, agentProfitTotal: roundAmount(aggregate.agentProfitTotal), closedAt: null } }); } async function main() { const purchases = await prisma.purchase.findMany({ where: { status: "SUBMITTED", purchaseType: { in: ["REGULAR", "OFFICE_BUYOUT"] } }, include: { profitShareScheme: { select: { shareAgent: true } }, lots: true }, orderBy: { id: "asc" } }); let allocationsCreated = 0; let entriesCreated = 0; for (const purchase of purchases) { for (const lot of purchase.lots) { const allocationCount = await prisma.lotPurchaseAllocation.count({ where: { lotId: lot.id, purchaseId: purchase.id } }); if (allocationCount === 0) { await prisma.lotPurchaseAllocation.create({ data: { lotId: lot.id, purchaseId: purchase.id, purchaseLineId: lot.purchaseLineId, sourceType: lot.sourceType, sourceRefId: lot.sourceRefId, agentIdSnapshot: purchase.agentId, profitShareSchemeIdSnapshot: purchase.profitShareSchemeId, qtyAllocated: lot.availableQty, costTotalAllocated: roundAmount(lot.availableQty.toNumber() * lot.unitCost.toNumber()), unitCostSnapshot: lot.unitCost, notes: "Backfill opening allocation" } }); allocationsCreated += 1; } const openingCount = await prisma.purchaseRealizationEntry.count({ where: { purchaseId: purchase.id, lotId: lot.id, eventType: "OPENING_COST" } }); if (openingCount === 0) { const allocation = await prisma.lotPurchaseAllocation.findFirst({ where: { lotId: lot.id, purchaseId: purchase.id }, orderBy: { id: "asc" } }); await prisma.purchaseRealizationEntry.create({ data: { purchaseId: purchase.id, lotId: lot.id, allocationId: allocation?.id ?? null, eventType: "OPENING_COST", referenceType: "PURCHASE", referenceId: purchase.id, occurredAt: lot.receivedAt, qtyIn: lot.originalQty, qtyOut: 0, qtyShrinkage: 0, amountCost: roundAmount(lot.originalQty.toNumber() * lot.unitCost.toNumber()), amountRevenue: 0, amountExpense: 0, amountProfit: 0, agentSharePercentSnapshot: purchase.profitShareScheme?.shareAgent ?? null, agentAmount: 0, notes: "Backfill opening realization" } }); entriesCreated += 1; } } await recalcSummary( purchase.id, purchase.profitShareScheme?.shareAgent?.toNumber() ?? null ); } console.log( JSON.stringify( { purchases: purchases.length, allocations_created: allocationsCreated, entries_created: entriesCreated }, null, 2 ) ); } main() .catch((error) => { console.error(error); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });