Add full integration for stock supplier pickup lines into the collection pool workflow: - Add two idempotent database migrations for stock line drv_id and state tracking - Update stock purchase number format to POyyMMddNNSTK convention - Extend collection pool to load both customer order items and stock purchase items - Add stock-specific UI, driver assignment flow, and mark collected action - Update type definitions and service logic for stock purchases and line items - Add comprehensive test coverage for all new workflows
95 lines
3 KiB
TypeScript
95 lines
3 KiB
TypeScript
// scripts/migrations/2026-07-15-add-stock-line-drv-id.ts
|
|
//
|
|
// One-shot schema migration: add a `drv_id` relation field to
|
|
// `stock_purchase_items`, pointing at the `users` auth collection.
|
|
// Mirrors `order_items.drv_id` so the collection-pool page can
|
|
// assign a driver to a stock line the same way it does for a
|
|
// customer line. The field is optional because stock lines in
|
|
// `collection_pool` (unassigned) don't have a driver yet.
|
|
//
|
|
// Idempotent: re-running is a no-op when the field is already
|
|
// present.
|
|
//
|
|
// NOTE: We construct the PB client INLINE (instead of importing
|
|
// `getAdminPocketBase` from `@/lib/server/pocketbase-admin`) so
|
|
// the env-file loader below can run BEFORE the PB client reads
|
|
// `process.env.NEXT_PUBLIC_POCKETBASE_URL` at module-load time.
|
|
// Otherwise the client connects to the localhost fallback.
|
|
|
|
/* 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_purchase_items";
|
|
const FIELD_NAME = "drv_id";
|
|
const USERS_COLLECTION_NAME = "users";
|
|
|
|
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 (via .env.local or shell).",
|
|
);
|
|
}
|
|
|
|
const pb = new PocketBase(url);
|
|
await pb.collection("_superusers").authWithPassword(email, password);
|
|
|
|
// Resolve the auth collection id dynamically — matches the
|
|
// pattern in 2026-07-14-create-stock.ts.
|
|
const usersCol = await pb.collections.getOne(USERS_COLLECTION_NAME);
|
|
|
|
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-line-drv-id] field already present on ${COLLECTION_NAME}`);
|
|
return;
|
|
}
|
|
|
|
const newField = {
|
|
name: FIELD_NAME,
|
|
type: "relation",
|
|
required: false,
|
|
presentable: false,
|
|
collectionId: usersCol.id,
|
|
cascadeDelete: false,
|
|
minSelect: 0,
|
|
maxSelect: 1,
|
|
};
|
|
|
|
await pb.collections.update(collection.id, {
|
|
fields: [...existing, newField],
|
|
});
|
|
|
|
console.log(
|
|
`[stock-line-drv-id] created drv_id (relation → users) on ${COLLECTION_NAME}`,
|
|
);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[stock-line-drv-id] failed:", err);
|
|
process.exit(1);
|
|
});
|