WFDASH-WEBAPP/scripts/migrations/2026-07-16-add-stock-purchase-send-statuses.ts
SJCoetz d306b75bdb feat(stock-purchases,driver-app): add stock purchase workflow and driver pickup support
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.
2026-07-18 22:43:50 +02:00

104 lines
3.5 KiB
TypeScript

// scripts/migrations/2026-07-16-add-stock-purchase-send-statuses.ts
//
// Extends the `stock_purchases.status` select enum to include the
// two intermediate states the new outbound-email flow needs:
// - "sending" — operator hit "Send to supplier", n8n is processing
// - "mail_failed" — SMTP attempt failed, retryable
//
// These mirror the `purchase_orders` status enum so the same n8n
// workflow can drive both flows (the parameterized version reads
// `status` from the body / record regardless of collection name).
//
// Idempotent: re-running is a no-op when the values are 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.
}
}
loadEnvFile(".env.local");
const COLLECTION_NAME = "stock_purchases";
const STATUS_FIELD = "status";
const EXISTING_VALUES = ["draft", "sent", "received", "cancelled"];
const NEW_VALUES = ["sending", "mail_failed"];
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 fields = (collection.fields ?? []) as Array<{ name: string; values?: string[] }>;
const statusField = fields.find((f) => f.name === STATUS_FIELD);
if (!statusField || statusField.type !== "select") {
throw new Error(
`Expected ${COLLECTION_NAME}.${STATUS_FIELD} to be a select field. Got ${
statusField?.type || "missing"
}.`,
);
}
const currentValues = statusField.values ?? [];
const merged = Array.from(new Set([...currentValues, ...NEW_VALUES]));
if (currentValues.includes(NEW_VALUES[0]) && currentValues.includes(NEW_VALUES[1])) {
console.log(
`[stock-purchase-send-statuses] both "sending" and "mail_failed" already present on ${COLLECTION_NAME}.${STATUS_FIELD}`,
);
return;
}
// Replace the field with the same config + extended values. PB
// requires us to re-submit the field type / config block when
// updating values.
const updatedFields = fields.map((f) =>
f.name === STATUS_FIELD
? {
...f,
values: merged,
// `maxSelect` isn't relevant for a single-value status but
// we set it explicitly to 1 to mirror the customer-PO
// status field. PB preserves the existing value if
// omitted, but the type check is stricter on the way in.
maxSelect: 1,
}
: f
);
await pb.collections.update(collection.id, { fields: updatedFields });
console.log(
`[stock-purchase-send-statuses] extended ${COLLECTION_NAME}.${STATUS_FIELD} values: ${merged.join(", ")}`,
);
}
main().catch((err) => {
console.error("[stock-purchase-send-statuses] failed:", err);
process.exit(1);
});