78 lines
3.5 KiB
TypeScript
78 lines
3.5 KiB
TypeScript
|
|
/**
|
||
|
|
* One-off repair for the short pickups that were part-invoiced before
|
||
|
|
* Collection Confirm learned to split them (commit 2e55d21).
|
||
|
|
*
|
||
|
|
* Each line was invoiced for what was collected, then bounced back to
|
||
|
|
* `ready_invoice` with the rest still owed and never re-ordered. This does
|
||
|
|
* what the new split does, after the fact:
|
||
|
|
* - the original is cut to the invoiced qty and closed as `invoiced`;
|
||
|
|
* - the rest becomes a new `pending` line on the same order (New tab),
|
||
|
|
* with the `shortCollection` marker and its original PO for Back order.
|
||
|
|
*
|
||
|
|
* Guarded per line: still `ready_invoice`, `qty_invoiced > 0`,
|
||
|
|
* `qty_invoiced == qty_collected < quantity`. Dry run unless `--apply`.
|
||
|
|
*
|
||
|
|
* npx tsx scripts/2026-10-22-repair-short-splits.ts [--apply]
|
||
|
|
*/
|
||
|
|
import "./_env";
|
||
|
|
import { getAdminPocketBase } from "../src/lib/server/pocketbase-admin";
|
||
|
|
import { runWithAuditActor } from "../src/lib/server/audit-context";
|
||
|
|
import { appendNote } from "../src/lib/note-feed";
|
||
|
|
import { buildShortCollectionSplit, newRecordId } from "../src/lib/short-collection";
|
||
|
|
|
||
|
|
const IDS = [
|
||
|
|
"3sipxowwqkfw9x3", // 6632 U BOLT M24(400) HDG — 549 of 770
|
||
|
|
"4juzxtt2yj3tsyl", // MSP18104 Insertion rubber — 5 of 8
|
||
|
|
"9gojrwwszua8sgy", // ZA10PO26012735 Welding rod 2.5mm — 3 of 42
|
||
|
|
];
|
||
|
|
const APPLY = process.argv.includes("--apply");
|
||
|
|
const actor = { id: "claude-short-split-repair", name: "Claude (short-collection repair)" };
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
const pb = await getAdminPocketBase();
|
||
|
|
const at = new Date().toISOString();
|
||
|
|
for (const id of IDS) {
|
||
|
|
const row = (await pb.collection("order_items").getOne(id, { expand: "purchase_order" })) as Record<string, unknown> & {
|
||
|
|
expand?: { purchase_order?: { po_number?: string } };
|
||
|
|
};
|
||
|
|
const quantity = Number(row.quantity);
|
||
|
|
const collected = Number(row.qty_collected);
|
||
|
|
const invoiced = Number(row.qty_invoiced);
|
||
|
|
const label = `${id} ${String(row.description)} (${collected}/${quantity}, invoiced ${invoiced})`;
|
||
|
|
if (row.item_status !== "ready_invoice" || !(invoiced > 0) || invoiced !== collected || collected >= quantity) {
|
||
|
|
console.log(`SKIP ${label}: status ${String(row.item_status)} — no longer the stuck shape`);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const split = buildShortCollectionSplit(row as Parameters<typeof buildShortCollectionSplit>[0], {
|
||
|
|
remainderId: newRecordId(),
|
||
|
|
poNumber: row.expand?.purchase_order?.po_number ?? "",
|
||
|
|
at,
|
||
|
|
});
|
||
|
|
if (!split) {
|
||
|
|
console.log(`SKIP ${label}: nothing to split`);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const originalPatch = {
|
||
|
|
...split.originalPatch,
|
||
|
|
item_status: "invoiced",
|
||
|
|
notes: appendNote(row.notes as string | null, {
|
||
|
|
ts: at,
|
||
|
|
actor,
|
||
|
|
category: "quantity",
|
||
|
|
body: `Short collection repaired: ${collected} of ${quantity} collected and invoiced on ${String(row.customer_invoice_number ?? "")}. The ${split.remainderQty} still owed moved to a new line in New on this order.`,
|
||
|
|
}),
|
||
|
|
};
|
||
|
|
console.log(`${APPLY ? "APPLY" : "DRY "} ${label} → original qty ${collected} invoiced; new line ${split.remainder.id} qty ${split.remainderQty} pending (PO ${row.expand?.purchase_order?.po_number ?? "?"})`);
|
||
|
|
if (!APPLY) continue;
|
||
|
|
const batch = pb.createBatch();
|
||
|
|
batch.collection("order_items").create(split.remainder);
|
||
|
|
batch.collection("order_items").update(id, originalPatch);
|
||
|
|
await batch.send();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
runWithAuditActor({ ...actor, source: "server", ignoreRequestUser: true }, main).catch((err) => {
|
||
|
|
console.error(err);
|
||
|
|
process.exit(1);
|
||
|
|
});
|