WFDASH-WEBAPP/scripts/2026-08-03-merge-yg1-drafts.ts

133 lines
6.2 KiB
TypeScript
Raw Normal View History

/**
* Merge YG-1 drafts.
*
* PO26073102STK (existing draft, 4 HSS drill-bit lines, 2026-07-31)
* PO26080301STK (new draft, 16 tap lines, loaded 2026-08-03 from quote SOQ078608)
*
* Both are still in `draft` status (neither has been sent to the supplier),
* so the merge is purely local — the supplier hasn't seen either PO number.
*
* ## Direction
*
* Keep the older PO number (`PO26073102STK`) — it's the operator's
* existing accumulated draft per the system's "one draft per supplier"
* design (see `stock-quick-draft-service.ts`). The new draft was created
* by `2026-08-03-load-yg1-quote-soq078608.ts`; merging its lines back
* into the existing draft collapses the two into a single draft.
*
* ## What this does
*
* 1. Updates all 16 `stock_purchase_items` rows on `PO26080301STK`
* to point at `PO26073102STK` (just flip the `purchase` field).
* 2. Deletes the now-empty `PO26080301STK` header.
* 3. Updates `PO26073102STK`'s `notes` to record both quote sources.
*
* The line `id`s, `created` timestamps, `description`, `quantity`,
* `cost_price`, `notes` (including the "No stock" flag on TS2352932),
* and `stock_item` references are all preserved — this is a pure
* parent-pointer swap on the line rows.
*
* ## Preconditions
*
* - Both purchase ids exist and are in `status="draft"`.
* - Neither has been sent (`email_message_id` is empty, no `documents`).
* - The new draft has 16 lines, the old draft has 4.
*
* Run: `npx tsx scripts/2026-08-03-merge-yg1-drafts.ts`
*/
import PocketBase from "pocketbase";
const PB_URL = "https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io";
const ADMIN_EMAIL = "david.els1@outlook.com";
const ADMIN_PASSWORD = "1234567890";
const EXISTING_DRAFT_ID = "2a6qo4nxb2ni4tm"; // PO26073102STK — keep this one
const NEW_DRAFT_ID = "cj0h9jdsas2s4gk"; // PO26080301STK — drain into the above
async function main() {
const pb = new PocketBase(PB_URL);
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
console.log("Authenticated as admin");
// ── 1. Pre-flight checks ─────────────────────────────────────────
console.log("\n=== Pre-flight ===");
const existing = await pb.collection("stock_purchases").getOne(EXISTING_DRAFT_ID);
const newDraft = await pb.collection("stock_purchases").getOne(NEW_DRAFT_ID);
console.log(` PO26073102STK status=${existing.status} notes="${existing.notes}"`);
console.log(` PO26080301STK status=${newDraft.status} notes="${newDraft.notes}"`);
if (existing.status !== "draft" || newDraft.status !== "draft") {
throw new Error(`Refusing to merge: both drafts must be in 'draft' status (existing=${existing.status}, new=${newDraft.status})`);
}
if (existing.email_message_id || newDraft.email_message_id) {
throw new Error("Refusing to merge: one or both drafts already have an email_message_id (already sent?)");
}
const existingLines = await pb.collection("stock_purchase_items").getList(1, 1, {
filter: `purchase = "${EXISTING_DRAFT_ID}"`,
});
const newLines = await pb.collection("stock_purchase_items").getList(1, 1, {
filter: `purchase = "${NEW_DRAFT_ID}"`,
});
console.log(` PO26073102STK lines: ${existingLines.totalItems}`);
console.log(` PO26080301STK lines: ${newLines.totalItems}`);
// ── 2. Re-parent the new draft's lines ───────────────────────────
console.log("\n=== Re-parenting lines ===");
const newLinesList = await pb
.collection("stock_purchase_items")
.getFullList({ filter: `purchase = "${NEW_DRAFT_ID}"`, fields: "id,description,quantity" });
for (const ln of newLinesList) {
await pb.collection("stock_purchase_items").update(ln.id, {
purchase: EXISTING_DRAFT_ID,
});
console.log(` ${ln.id} → PO26073102STK (${ln.description.slice(0, 40)} qty=${ln.quantity})`);
}
// ── 3. Delete the empty new draft ────────────────────────────────
console.log("\n=== Deleting empty draft PO26080301STK ===");
await pb.collection("stock_purchases").delete(NEW_DRAFT_ID);
console.log(` deleted ${NEW_DRAFT_ID}`);
// ── 4. Update notes on the surviving draft ───────────────────────
console.log("\n=== Updating notes on PO26073102STK ===");
const mergedNotes =
"YG-1 stock draft. Sources: existing draft since 2026-07-31 (4 HSS drill-bit lines) + YG-1 quote SOQ078608 dated 2026-08-03 for customer CNL001 (16 line items, merged 2026-08-03).";
await pb.collection("stock_purchases").update(EXISTING_DRAFT_ID, {
notes: mergedNotes,
});
console.log(` set notes: "${mergedNotes}"`);
// ── 5. Verify ────────────────────────────────────────────────────
console.log("\n=== Post-merge verification ===");
const finalDraft = await pb.collection("stock_purchases").getOne(EXISTING_DRAFT_ID, {
expand: "supplier",
});
const finalLines = await pb.collection("stock_purchase_items").getList(1, 1, {
filter: `purchase = "${EXISTING_DRAFT_ID}"`,
});
console.log(` Purchase: ${finalDraft.purchase_number} (${finalDraft.expand?.supplier?.suppliername})`);
console.log(` Status: ${finalDraft.status}`);
console.log(` Lines: ${finalLines.totalItems}`);
const totalCost = await pb
.collection("stock_purchase_items")
.getFullList({ filter: `purchase = "${EXISTING_DRAFT_ID}"` });
const total = totalCost.reduce((s, l) => s + l.quantity * l.cost_price, 0);
console.log(` Total cost (post-discount, excl VAT): R ${total.toFixed(2)}`);
// Confirm the new-draft id is gone.
try {
await pb.collection("stock_purchases").getOne(NEW_DRAFT_ID);
throw new Error("New draft still exists after delete!");
} catch (err) {
if (err instanceof Error && err.message.includes("not found")) {
console.log(` PO26080301STK (${NEW_DRAFT_ID}): deleted ✓`);
} else {
throw err;
}
}
}
main().catch((err) => {
console.error("FATAL:", err);
process.exit(1);
});