113 lines
3.7 KiB
TypeScript
113 lines
3.7 KiB
TypeScript
|
|
/* eslint-disable no-console */
|
||
|
|
/**
|
||
|
|
* scripts/nss-backfill-in-stock-qty.ts
|
||
|
|
*
|
||
|
|
* One-shot backfill for in_stock=true order_items rows whose
|
||
|
|
* `qty_collected` was never stamped to match the customer quantity.
|
||
|
|
*
|
||
|
|
* The order process page's "in stock" toggle now stamps qty_collected
|
||
|
|
* = quantity automatically (so the Invoicing page can bill those
|
||
|
|
* rows at the customer-facing qty), but rows that were marked
|
||
|
|
* in_stock before that fix shipped will still have qty_collected=0
|
||
|
|
* — the Invoicing page reads min(qty_collected, quantity), so the
|
||
|
|
* invoice line would land at 0 and the operator can't invoice the
|
||
|
|
* customer.
|
||
|
|
*
|
||
|
|
* This script:
|
||
|
|
* 1. Finds every order_item with in_stock=true AND
|
||
|
|
* qty_collected < quantity.
|
||
|
|
* 2. Stamps qty_collected = quantity.
|
||
|
|
* 3. Reports a count of rows touched.
|
||
|
|
*
|
||
|
|
* Idempotent — re-running is a no-op once all rows are aligned.
|
||
|
|
*
|
||
|
|
* Usage:
|
||
|
|
* npx tsx scripts/nss-backfill-in-stock-qty.ts
|
||
|
|
*/
|
||
|
|
import PocketBase from "pocketbase";
|
||
|
|
import { readFileSync, existsSync } from "node:fs";
|
||
|
|
import { resolve } from "node:path";
|
||
|
|
|
||
|
|
for (const file of [".env.local", ".env"]) {
|
||
|
|
const path = resolve(process.cwd(), file);
|
||
|
|
if (existsSync(path)) {
|
||
|
|
const text = readFileSync(path, "utf8");
|
||
|
|
for (const rawLine of text.split(/\r?\n/)) {
|
||
|
|
const line = rawLine.trim();
|
||
|
|
if (!line || line.startsWith("#")) continue;
|
||
|
|
const eq = line.indexOf("=");
|
||
|
|
if (eq <= 0) continue;
|
||
|
|
const key = line.slice(0, eq).trim();
|
||
|
|
let value = line.slice(eq + 1).trim();
|
||
|
|
if (
|
||
|
|
(value.startsWith('"') && value.endsWith('"')) ||
|
||
|
|
(value.startsWith("'") && value.endsWith("'"))
|
||
|
|
) {
|
||
|
|
value = value.slice(1, -1);
|
||
|
|
}
|
||
|
|
if (!process.env[key]) process.env[key] = value;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const url = process.env.NEXT_PUBLIC_POCKETBASE_URL ?? "http://127.0.0.1:8090";
|
||
|
|
const email = process.env.PB_ADMIN_EMAIL;
|
||
|
|
const password = process.env.PB_ADMIN_PASSWORD;
|
||
|
|
if (!email || !password) {
|
||
|
|
console.error("PB_ADMIN_EMAIL / PB_ADMIN_PASSWORD are required (set in .env.local).");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const pb = new PocketBase(url);
|
||
|
|
pb.autoCancellation(false);
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
await pb.collection("_superusers").authWithPassword(email, password);
|
||
|
|
console.log(`Connected to ${url} as ${email}.`);
|
||
|
|
|
||
|
|
// Pull every order_item with in_stock=true. We filter in JS rather
|
||
|
|
// than PB because qty_collected < quantity isn't trivially expressible
|
||
|
|
// in PB filter syntax (would need to subtract via JS anyway).
|
||
|
|
const all = await pb.collection("order_items").getFullList({
|
||
|
|
filter: "in_stock = true",
|
||
|
|
fields: "id,description,quantity,qty_collected,item_status,customer,order",
|
||
|
|
batch: 500,
|
||
|
|
});
|
||
|
|
|
||
|
|
const stale = all.filter((it) => {
|
||
|
|
const q = Number(it.quantity ?? 0);
|
||
|
|
const qc = Number(it.qty_collected ?? 0);
|
||
|
|
return q > 0 && qc < q;
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log(
|
||
|
|
`\nScanned ${all.length} in_stock rows; ${stale.length} have qty_collected < quantity and need a backfill.`,
|
||
|
|
);
|
||
|
|
|
||
|
|
if (stale.length === 0) {
|
||
|
|
console.log("Nothing to do — all in_stock rows already have qty_collected = quantity.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let updated = 0;
|
||
|
|
for (const row of stale) {
|
||
|
|
const q = Number(row.quantity ?? 0);
|
||
|
|
try {
|
||
|
|
await pb.collection("order_items").update(row.id, {
|
||
|
|
qty_collected: q,
|
||
|
|
});
|
||
|
|
updated += 1;
|
||
|
|
console.log(` ✓ ${row.id} qty=${q} → qty_collected=${q} (${String(row.description).slice(0, 50)})`);
|
||
|
|
} catch (err) {
|
||
|
|
console.error(` ✗ ${row.id} failed:`, err instanceof Error ? err.message : err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`\nDone. Stamped qty_collected = quantity on ${updated} rows.`);
|
||
|
|
}
|
||
|
|
|
||
|
|
main().catch((err) => {
|
||
|
|
console.error("FAILED:", err);
|
||
|
|
process.exit(1);
|
||
|
|
});
|