Ship full split-invoice stock purchase order workflow: - Add 5 new MCP tools for per-line stock purchase item management and catalogue updates - Add idempotent database migration adding sku, unit, and supplier_sku fields to stock_purchase_items - Implement service layers for all new operations, including auto-closing the parent PO when all lines are fully collected - Add complete test coverage for new services and API routes - Update all relevant documentation including MCP API specs, agent instructions, and feature request logs
96 lines
3.6 KiB
TypeScript
96 lines
3.6 KiB
TypeScript
// scripts/migrations/2026-08-07-stock-purchase-items-catalogue-fields.ts
|
|
//
|
|
// One-shot schema migration: add three catalogue fields to
|
|
// `stock_purchase_items`:
|
|
//
|
|
// - sku (text, optional) — the WFDASH catalogue SKU
|
|
// that this PO line corresponds to.
|
|
// May differ from the supplier's
|
|
// sku when the supplier relabels.
|
|
// - unit (text, optional) — supplier-side unit string
|
|
// (e.g. "L", "m", "kg"). Used by
|
|
// the agent when the supplier
|
|
// invoice disagrees with the PO.
|
|
// - supplier_sku (text, optional) — the SKU printed on the
|
|
// supplier's catalogue / invoice.
|
|
// Lets Hermes reconcile the
|
|
// supplier invoice against the PO
|
|
// line without a manual lookup.
|
|
//
|
|
// Why this exists: the operator's external agent (Hermes)
|
|
// reconciles supplier invoices against stock POs. Today, when
|
|
// the supplier renames a SKU on the invoice, Hermes has no field
|
|
// to record the supplier's sku on the PO line. The three fields
|
|
// are the minimal additive set for that workflow.
|
|
//
|
|
// The MCP `stock_purchase_items.update` tool's whitelist references
|
|
// these three fields. The whitelist also references `cost_price`,
|
|
// `description`, `notes`, and `quantity` — all of which already
|
|
// exist on the collection, so no migration is needed for them.
|
|
//
|
|
// Idempotent: re-running is a no-op when every field is already
|
|
// present. Partial states are surfaced clearly so the operator
|
|
// can re-run to add the missing field(s) one at a time.
|
|
|
|
import { getAdminPocketBase } from "@/lib/server/pocketbase-admin";
|
|
|
|
const COLLECTION_NAME = "stock_purchase_items";
|
|
const NEW_FIELDS = [
|
|
{ name: "sku", type: "text" },
|
|
{ name: "unit", type: "text" },
|
|
{ name: "supplier_sku", type: "text" },
|
|
] as const;
|
|
|
|
async function main() {
|
|
const pb = await getAdminPocketBase();
|
|
|
|
const collection = await pb.collections.getOne(COLLECTION_NAME);
|
|
const existing = (collection.fields ?? []) as Array<{ name: string }>;
|
|
const existingNames = new Set(existing.map((f) => f.name));
|
|
|
|
const missing = NEW_FIELDS.filter((f) => !existingNames.has(f.name));
|
|
if (missing.length === 0) {
|
|
console.log(
|
|
`[stock-purchase-items-catalogue-fields] all fields already present on ${COLLECTION_NAME}: ${NEW_FIELDS.map((f) => f.name).join(", ")}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Build the new field definitions. PB text fields need
|
|
// `maxSize` — the collection-wide convention here is 500 chars
|
|
// (matches the existing `description` and `notes` fields on the
|
|
// same collection).
|
|
const newFieldDefs = missing.map((f) => ({
|
|
name: f.name,
|
|
type: "text",
|
|
required: false,
|
|
presentable: false,
|
|
protected: false,
|
|
maxSize: 500,
|
|
pattern: "",
|
|
}));
|
|
|
|
await pb.collections.update(collection.id, {
|
|
fields: [...existing, ...newFieldDefs],
|
|
});
|
|
|
|
console.log(
|
|
`[stock-purchase-items-catalogue-fields] added ${missing.length} field(s) to ${COLLECTION_NAME}: ${missing.map((f) => f.name).join(", ")}`,
|
|
);
|
|
if (NEW_FIELDS.some((f) => !missing.find((m) => m.name === f.name))) {
|
|
const already = NEW_FIELDS.filter(
|
|
(f) => !missing.find((m) => m.name === f.name),
|
|
).map((f) => f.name);
|
|
console.log(
|
|
`[stock-purchase-items-catalogue-fields] skipped (already present): ${already.join(", ")}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(
|
|
"[stock-purchase-items-catalogue-fields] failed:",
|
|
err,
|
|
);
|
|
process.exit(1);
|
|
});
|