235 lines
6.9 KiB
JavaScript
235 lines
6.9 KiB
JavaScript
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();
|
|
});
|