/* 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(); 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); });