Initial import of AbelBirdNest Stock
This commit is contained in:
234
scripts/backfill-purchase-realization.mjs
Normal file
234
scripts/backfill-purchase-realization.mjs
Normal file
@ -0,0 +1,234 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user