WFDASH-WEBAPP/scripts/utils/resolve-price-list-ids.ts
SJCoetz 05abe499b0 feat(sprint): add new sprint pricing workflow and supporting code
Add full sprint pricing workflow and supporting infrastructure:
- New sidebar navigation entry for the sprint dashboard
- PocketBase category collection schema and migration script
- Server API routes for sprint item claims, attachments, counts, and file proxying
- Dev identity utilities and UI switcher for multi-user testing
- Reusable FilterTabs component replacing ItemStatusTabs
- Updated TypeScript types for sprint-related entities

Also include:
- Fix PocketBase file URL method casing (getUrl → getURL) across codebase
- Add development utility, smoke test, and helper scripts
- Update package.json with new sprint migration command
2026-07-03 07:29:43 +02:00

124 lines
No EOL
4.2 KiB
TypeScript

/* eslint-disable */
/**
* Resolve a list of `price_list` PB record ids to their actual data,
* with the supplier name resolved from the `suppliers` collection.
*
* Usage:
* env PB_ADMIN_EMAIL=… PB_ADMIN_PASSWORD=… NEXT_PUBLIC_POCKETBASE_URL=…
* npx tsx scripts/utils/resolve-price-list-ids.ts id1 id2 id3 …
*
* Or pipe ids via stdin (one per line). Or pass them as a comma-
* separated list. Output is paste-ready text for an LLM.
*
* The output preserves the input order (so if your LLM is doing
* a re-rank, the input order is the canonical "before" view).
*/
import PocketBase from "pocketbase";
async function main() {
const URL = process.env.NEXT_PUBLIC_POCKETBASE_URL;
const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL;
const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD;
if (!URL || !ADMIN_EMAIL || !ADMIN_PASSWORD) {
throw new Error(
"Set PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, NEXT_PUBLIC_POCKETBASE_URL."
);
}
// Pull ids from argv (space-separated), stdin, or a JSON list
// passed as the first arg. Keeps the call ergonomic.
const rawArg = process.argv.slice(2).join(" ").trim();
let ids: string[] = [];
if (rawArg) {
// Try JSON first (handles {"ranked_ids": [...]} shapes too)
try {
const parsed = JSON.parse(rawArg);
ids = Array.isArray(parsed)
? parsed
: Array.isArray(parsed.ranked_ids)
? parsed.ranked_ids
: [];
} catch {
ids = rawArg.split(/[\s,]+/).filter(Boolean);
}
}
if (ids.length === 0) {
console.error("No ids supplied. Pass them as argv, e.g.:");
console.error(' npx tsx scripts/utils/resolve-price-list-ids.ts id1 id2 id3');
process.exit(1);
}
const pb = new PocketBase(URL);
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
// Batch fetch the price_list rows in one filter call. PB 0.26 has
// no `IN` operator, so OR-chain the equalities. 30 ids is well
// under the filter-length budget.
const filter =
"(" + ids.map((id) => `id = "${id.replace(/"/g, '\\"')}"`).join(" || ") + ")";
const rows = await pb.collection("price_list").getFullList({
filter,
fields: "id,sku,description,cost_price,selling_price,unit,supplier,brand,category,subcategory,sku_normalised",
});
// Index by id so we can re-emit in input order even if PB returns
// them out of order.
const byId = new Map(rows.map((r) => [r.id, r]));
// Resolve supplier ids → supplier names in one shot. Collect all
// unique supplier ids first so we do at most a handful of lookups.
const supplierIds = Array.from(
new Set(
rows
.map((r) => (r.supplier || "").trim())
.filter((s) => s && s.length === 15) // PB record ids are 15 chars
)
);
const supplierNames = new Map<string, string>();
if (supplierIds.length > 0) {
const sFilter =
"(" +
supplierIds
.map((id) => `id = "${id.replace(/"/g, '\\"')}"`)
.join(" || ") +
")";
const suppliers = await pb.collection("suppliers").getFullList({
filter: sFilter,
fields: "id,suppliername",
});
for (const s of suppliers) supplierNames.set(s.id, s.suppliername);
}
// Emit paste-ready list. Each row: `[n] SKU | description | Rcost | supplier`
console.log(`# ${ids.length} price_list rows (input order preserved)\n`);
let emitted = 0;
for (const id of ids) {
const r = byId.get(id);
if (!r) {
console.log(`[${++emitted}] id=${id} -- NOT FOUND --`);
continue;
}
const supplierDisplay =
supplierNames.get(r.supplier || "") ||
(r.supplier ? r.supplier : "—");
const sku = r.sku || "(no sku)";
const desc = (r.description || "(no description)")
.replace(/\s+/g, " ")
.trim();
const cost = Number(r.cost_price) || 0;
const unit = r.unit || "";
const brand = r.brand || "";
const category = r.category || "";
const meta = [brand, category].filter(Boolean).join(" · ");
const costStr = `R${cost.toFixed(2)}${unit ? "/" + unit : ""}`;
console.log(
`[${++emitted}] ${sku} | ${desc} | ${costStr} | ${supplierDisplay}` +
(meta ? ` | ${meta}` : "")
);
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});