diff --git a/scripts/book-invoice.mjs b/scripts/book-invoice.mjs index ab1ad94..7fe75d4 100644 --- a/scripts/book-invoice.mjs +++ b/scripts/book-invoice.mjs @@ -74,6 +74,40 @@ if (!targets.length) { const allLines = await pb.collection("invoice_lines").getFullList({ sort: "line_no" }); const existingIncome = await pb.collection("monthly_income").getFullList(); +// ---- Repair stale booking pointers ------------------------------------------------ +// `booked_asset` stores an asset RECORD ID, and `npm run seed` deletes and re-creates +// every asset — so the id a line was booked against stops existing after a seed. The line +// then points at nothing and silently drops out of monthly_income, which is exactly the +// "it booked yesterday and is unbooked today" symptom. +// +// The invoice line already carries the register code it was matched on (`asset_code`), +// so the pointer can be re-derived rather than retyped. This ONLY ever points a line at +// the asset its own code names; it never invents a match and never clears one, so a line +// with no code stays unbooked exactly as before. +const allAssets = await pb.collection("assets").getFullList(); +const assetIds = new Set(allAssets.map((a) => a.id)); +const idByCode = new Map(allAssets.filter((a) => a.code).map((a) => [String(a.code).trim().toUpperCase(), a.id])); +const codeFromMachine = (code) => { + const m = /^([A-Za-z]+)[- ]?([0-9]{1,3})/.exec(String(code || "").trim()); + return m ? `${m[1].toUpperCase()}${m[2]}` : ""; +}; + +let repaired = 0; +if (!dryRun) { + for (const l of allLines) { + if (l.booked_site) continue; // booked to a site — a different, deliberate choice + const fresh = (String(l.asset_code || "").trim().toUpperCase() || codeFromMachine(l.machine_code)) + && idByCode.get(String(l.asset_code || "").trim().toUpperCase() || codeFromMachine(l.machine_code)); + // Only act when the pointer is genuinely gone: empty, or pointing at a deleted asset. + const needsRepair = !l.booked_asset || !assetIds.has(l.booked_asset); + if (!needsRepair || !fresh || fresh === l.booked_asset) continue; + await pb.collection("invoice_lines").update(l.id, { booked_asset: fresh }); + l.booked_asset = fresh; + repaired++; + } +} +if (repaired) console.log(` · re-linked ${repaired} line(s) whose target asset had been replaced by a seed`); + // Remove previously invoice-derived rows for the target invoices only. const targetIds = new Set(targets.map((i) => i.id)); const linesOfTargets = allLines.filter((l) => targetIds.has(l.invoice));