dn-snapshot prints row counts across the delivery-note collections. The two that matter are delivery_notes and delivery_note_items: the staged pipeline must never move them on its own, so a reading before and after a test says plainly whether anything reached the ledger. dn-board renders the inbox board as text, which is how the pipeline can be read over SSH or while the app is mid-deploy. Both are read-only. The other scripts written while diagnosing this work were one-off probes and are not kept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
62 lines
2.8 KiB
TypeScript
62 lines
2.8 KiB
TypeScript
// The delivery-note inbox board, as text. Read-only.
|
|
//
|
|
// Renders what /dashboard/financial/delivery-notes/inbox would show, so
|
|
// the pipeline can be checked over SSH or while the app is mid-deploy.
|
|
// Batches hold the left-hand columns; once assembly is settled the batch
|
|
// dissolves and its documents continue individually.
|
|
//
|
|
// npx tsx --env-file=.env.local scripts/dn-board.mts
|
|
//
|
|
// .mts, not .ts: top-level await, and the package has no "type": "module".
|
|
import { pbAdmin } from "../src/lib/pb";
|
|
import { displayBatchStage, displayCandidateStage } from "../src/lib/dn-pipeline/stages";
|
|
|
|
type Row = Record<string, any>;
|
|
|
|
const COLUMNS: { key: string; label: string }[] = [
|
|
{ key: "queued", label: "Queued" },
|
|
{ key: "reading", label: "Reading photos" },
|
|
{ key: "split_review", label: "Check page grouping" },
|
|
{ key: "needs_review", label: "Needs matching" },
|
|
{ key: "ready_to_post", label: "Ready to post" },
|
|
{ key: "posted", label: "Complete" },
|
|
];
|
|
|
|
const pb = await pbAdmin();
|
|
const batches = (await pb.collection("dn_batches").getFullList()) as Row[];
|
|
const notes = (await pb.collection("dn_batch_notes").getFullList({ expand: "supplier" })) as Row[];
|
|
const batchById = new Map(batches.map((batch) => [batch.id, batch]));
|
|
|
|
const columns: Record<string, string[]> = Object.fromEntries(COLUMNS.map((column) => [column.key, []]));
|
|
|
|
for (const batch of batches) {
|
|
const stage = displayBatchStage(batch.status ?? "", batch.stage ?? "");
|
|
if (stage === "failed") continue;
|
|
if (columns[stage] && stage !== "complete") {
|
|
columns[stage].push(`BATCH ${batch.id} · ${batch.source_count ?? 0} source(s) · ${batch.page_count ?? 0} page(s)`);
|
|
}
|
|
}
|
|
|
|
for (const note of notes) {
|
|
const batch = batchById.get(note.batch);
|
|
if (!batch || displayBatchStage(batch.status ?? "", batch.stage ?? "") !== "complete") continue;
|
|
const stage = displayCandidateStage(note.status ?? "", note.stage ?? "");
|
|
// `matching` is the runner's transient state; to an operator it is the
|
|
// same outstanding job as needs_review.
|
|
const key = stage === "matching" ? "needs_review" : stage;
|
|
if (!columns[key]) continue;
|
|
const supplier = note.expand?.supplier?.name ?? note.supplier_name_raw ?? "supplier unknown";
|
|
columns[key].push(`${note.doc_number || "(unnumbered)"} — ${supplier}`);
|
|
}
|
|
|
|
for (const { key, label } of COLUMNS) {
|
|
console.log(`\n${label} (${columns[key].length})`);
|
|
for (const line of columns[key]) console.log(` • ${line}`);
|
|
if (columns[key].length === 0) console.log(" —");
|
|
}
|
|
|
|
const failed = batches.filter((batch) => displayBatchStage(batch.status ?? "", batch.stage ?? "") === "failed");
|
|
if (failed.length > 0) {
|
|
console.log(`\nFailed (${failed.length})`);
|
|
for (const batch of failed) console.log(` • ${batch.id} — ${batch.last_error || "no error recorded"}`);
|
|
}
|