Add complete stock purchase order workflow including: - Status tracking with sending/mail_failed states and UI labels - Server-side PDF rendering and document storage - File proxy API support for stock PO attachments - Send outcome webhook callback for email notifications Add driver app support: - Unified stop list API for both customer deliveries and stock pickups - Status update endpoint for collected quantities and problem reporting - Collection map page support for stock pickup locations Include matching PocketBase migrations, type definitions, and stock inventory fulfillment logic.
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
// scripts/migrations/2026-07-16-add-stock-purchase-documents-history.ts
|
|
//
|
|
// Adds a `documents_history` JSON field to `stock_purchases` so the
|
|
// stock-PO PDF route can append per-regeneration audit entries (file
|
|
// name, version, generatedAt, generatedBy, reason) — same pattern as
|
|
// the customer-PO `purchase_orders.documents_history` field added by
|
|
// 2026-07-06.
|
|
//
|
|
// We need this so the stock-PO PDF route can keep parallel structure
|
|
// with the customer-PO PDF route (the file proxy + download path
|
|
// reads from `documents_history` to find the versioned PDF). Stock
|
|
// POs are simpler in v1 — typically generated once + sent — but
|
|
// adding the field now means the route code stays parallel and we
|
|
// don't have to migrate again when stock-PO cost edits / re-sends
|
|
// become a thing.
|
|
//
|
|
// Idempotent: re-running is a no-op when the field is already
|
|
// present.
|
|
|
|
/* eslint-disable */
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import PocketBase from "pocketbase";
|
|
|
|
function loadEnvFile(name: string) {
|
|
try {
|
|
const raw = readFileSync(resolve(process.cwd(), name), "utf8");
|
|
for (const line of raw.split("\n")) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
const m = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(.+)$/i);
|
|
if (m && !process.env[m[1]]) {
|
|
process.env[m[1]] = m[2].replace(/^["']|["']$/g, "").trim();
|
|
}
|
|
}
|
|
} catch {
|
|
// .env.local doesn't exist — fall through; PB env vars may be
|
|
// set in the shell.
|
|
}
|
|
}
|
|
|
|
loadEnvFile(".env.local");
|
|
|
|
const COLLECTION_NAME = "stock_purchases";
|
|
const FIELD_NAME = "documents_history";
|
|
|
|
async function main() {
|
|
const url = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
|
const email = process.env.PB_ADMIN_EMAIL;
|
|
const password = process.env.PB_ADMIN_PASSWORD;
|
|
|
|
if (!url || !email || !password) {
|
|
throw new Error(
|
|
"NEXT_PUBLIC_POCKETBASE_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD must be set.",
|
|
);
|
|
}
|
|
|
|
const pb = new PocketBase(url);
|
|
await pb.collection("_superusers").authWithPassword(email, password);
|
|
|
|
const collection = await pb.collections.getOne(COLLECTION_NAME);
|
|
const existing = (collection.fields ?? []) as Array<{ name: string }>;
|
|
if (existing.some((f) => f.name === FIELD_NAME)) {
|
|
console.log(
|
|
`[stock-purchase-documents-history] field already present on ${COLLECTION_NAME}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Same shape as the customer-PO `documents_history` field:
|
|
// { version: number, file: string, token: string,
|
|
// generatedAt: string, generatedBy: string, reason: string,
|
|
// status: "current" | "superseded" }
|
|
// The route writes a fresh entry on each regen + flips older
|
|
// "current" entries to "superseded".
|
|
const newField = {
|
|
name: FIELD_NAME,
|
|
type: "json",
|
|
required: false,
|
|
presentable: false,
|
|
maxSize: 0, // no PB-side cap
|
|
};
|
|
|
|
await pb.collections.update(collection.id, {
|
|
fields: [...existing, newField],
|
|
});
|
|
|
|
console.log(
|
|
`[stock-purchase-documents-history] created documents_history (json) on ${COLLECTION_NAME}`,
|
|
);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[stock-purchase-documents-history] failed:", err);
|
|
process.exit(1);
|
|
});
|