metallkart-erp/scripts/fix-pr-quantities.ts

150 lines
4.6 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
import { Decimal } from '@prisma/client/runtime/library';
const prisma = new PrismaClient();
/**
* fix-pr-quantities.ts — Restore corrupted PR quantities from specData
*
* Bug: analyzeImpact() matched ALL PRs by sourceId, so applyQtyChanged
* overwrote every PR's quantityPerUnit with the value from one changed line.
*
* This script reads specData from the active smeta and restores each PR's
* quantityPerUnit and quantity by matching on description.
*
* Usage: npx tsx scripts/fix-pr-quantities.ts [--dry-run]
*/
interface SpecLine {
description: string;
unit: string;
quantityPerUnit: number;
estimatedPrice: number | null;
budgetAmount: number | null;
materialCategory: string;
sourceReference: string;
}
function normalizeForMatch(s: string): string {
return s.trim().toLowerCase().replace(/,/g, '.').replace(/\s+/g, ' ');
}
function findSpecLine(prDesc: string, specLines: SpecLine[]): SpecLine | null {
const normPR = normalizeForMatch(prDesc);
// Exact normalized match
const exact = specLines.find(sl => normalizeForMatch(sl.description) === normPR);
if (exact) return exact;
// Inclusion match
const incl = specLines.find(sl => {
const normSL = normalizeForMatch(sl.description);
return normPR.includes(normSL) || normSL.includes(normPR);
});
if (incl) return incl;
return null;
}
async function main() {
const dryRun = process.argv.includes('--dry-run');
if (dryRun) console.log('=== DRY RUN MODE — no changes will be made ===\n');
// Find all LAUNCHED orders
const orders = await prisma.order.findMany({
where: { status: { in: ['LAUNCHED', 'IN_PRODUCTION', 'QUALITY_CONTROL'] } },
select: { id: true, orderCode: true, quantity: true },
});
console.log(`Found ${orders.length} active orders to check.\n`);
let totalFixed = 0;
for (const order of orders) {
// Get active smeta with specData
const smeta = await prisma.smeta.findFirst({
where: { orderId: order.id, status: { in: ['APPROVED', 'LOCKED'] } },
orderBy: { version: 'desc' },
select: { id: true, version: true, specData: true },
});
if (!smeta?.specData) {
console.log(`[${order.orderCode}] No active smeta with specData — skipping`);
continue;
}
const specLines = smeta.specData as unknown as SpecLine[];
if (!Array.isArray(specLines) || specLines.length === 0) {
console.log(`[${order.orderCode}] specData empty — skipping`);
continue;
}
// Get PRs
const prs = await prisma.purchaseRequirement.findMany({
where: { orderId: order.id, sourceType: 'SMETA_IMPORT' },
select: { id: true, description: true, quantity: true, quantityPerUnit: true, unit: true },
});
if (prs.length === 0) {
console.log(`[${order.orderCode}] No SMETA_IMPORT PRs — skipping`);
continue;
}
// Check if corrupted: all PRs have same quantityPerUnit
const distinctQPU = new Set(prs.map(pr => Number(pr.quantityPerUnit)));
if (distinctQPU.size > 2) {
console.log(`[${order.orderCode}] PRs have ${distinctQPU.size} distinct qPerUnit values — looks OK, skipping`);
continue;
}
console.log(`[${order.orderCode}] ⚠ Only ${distinctQPU.size} distinct qPerUnit value(s) for ${prs.length} PRs — CORRUPTED, fixing...`);
let fixed = 0;
let notFound = 0;
for (const pr of prs) {
const specLine = findSpecLine(pr.description, specLines);
if (!specLine) {
console.log(` ✗ No specLine match for PR "${pr.description}" — skipping`);
notFound++;
continue;
}
const correctQPU = specLine.quantityPerUnit;
const correctQty = correctQPU * order.quantity;
const currentQPU = Number(pr.quantityPerUnit);
if (Math.abs(currentQPU - correctQPU) < 0.0001) {
continue; // already correct
}
console.log(` ✓ "${pr.description}": qPerUnit ${currentQPU}${correctQPU}, qty ${Number(pr.quantity)}${correctQty}`);
if (!dryRun) {
await prisma.purchaseRequirement.update({
where: { id: pr.id },
data: {
quantityPerUnit: new Decimal(correctQPU),
quantity: new Decimal(correctQty),
revisionAlert: false,
revisionNote: null,
},
});
}
fixed++;
}
console.log(` → Fixed ${fixed} PRs, ${notFound} unmatched\n`);
totalFixed += fixed;
}
console.log(`\n=== DONE: ${totalFixed} PRs ${dryRun ? 'would be' : ''} fixed ===`);
await prisma.$disconnect();
}
main().catch((e) => {
console.error(e);
prisma.$disconnect();
process.exit(1);
});