MEGAINFRA-WEBAPP/scripts/dn-board.mts

63 lines
2.8 KiB
TypeScript
Raw Normal View History

// 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"}`);
}