Overhaul the app's color system by replacing all hardcoded Tailwind color classes with semantic theme variables to ensure consistent theming across all components, pages, and tests. Additional changes: - Add assigned date support for order item status updates: add `assignedDate` field to the order item status PATCH API, implement server-side normalization/validation, and add corresponding tests - Refactor purchase order PDF workflows to use the consolidated `purchase_orders.documents` field instead of the legacy separate `purchase_order_pdfs` collection - Fix quote number parsing and formatting to support both dashed (Q-YYYY-MM-NNN) and dashless (QYYYYMMNNN) formats - Rename `driver_pool` item status to `collection_pool` across the codebase - Add null handling for order item quantities and pricing to display "—" for missing values - Improve filter tab styling: make mobile tabs scrollable, prevent text wrapping, hide scrollbar - Add new utility scripts, API routes, migrations, and polish small UI/UX fixes including tsconfig updates and theme hook improvements
115 lines
No EOL
3.9 KiB
TypeScript
115 lines
No EOL
3.9 KiB
TypeScript
/**
|
|
* Migration: 2026-07-06 — quote_attachments: add quote_item relation + kind field
|
|
*
|
|
* What it does:
|
|
* 1. Adds `quote_item` relation to `quote_attachments` (nullable, single,
|
|
* collectionId = quote_items). Lets a per-line attachment live alongside
|
|
* per-quote attachments on the same collection instead of needing a
|
|
* separate `quote_item_attachments` table.
|
|
*
|
|
* 2. Adds `kind` select field (pdf / image / doc / other) to
|
|
* `quote_attachments`. The TypeScript interface in `src/lib/types.ts`
|
|
* already declares `kind` (and `QuoteAttachmentKind`) — the v5 attachment
|
|
* workflow writes it on upload. Live PB never got the migration that adds
|
|
* the field, so existing rows have `kind = null`. The migration is
|
|
* idempotent: it skips when the field already exists.
|
|
*
|
|
* Why both at once:
|
|
* The order process page surfaces matched quote + per-line attachments so
|
|
* the buyer can verify they're ordering the right SKU from the right
|
|
* supplier. Without `quote_item` we can't link an attachment to a specific
|
|
* line; without `kind` we can't pick the right thumbnail (PDF vs image vs
|
|
* doc icon). Both are needed for the inline preview UI to be usable.
|
|
*
|
|
* Idempotency:
|
|
* Both field adds are skipped if a field with that name already exists.
|
|
* Tested collection ids are discovered by name from PB on every run, so
|
|
* this migration works across environments.
|
|
*
|
|
* Run:
|
|
* set -a && source .env.local && set +a && \
|
|
* npx tsx scripts/migrations/2026-07-06-quote-attachments-quote-item-and-kind.ts
|
|
*/
|
|
|
|
import { getAdminPocketBase } from "@/lib/server/pocketbase-admin";
|
|
|
|
type PbField = {
|
|
id?: string;
|
|
name: string;
|
|
type: string;
|
|
collectionId?: string;
|
|
values?: string[];
|
|
required?: boolean;
|
|
maxSelect?: number;
|
|
minSelect?: number;
|
|
cascadeDelete?: boolean;
|
|
[k: string]: unknown;
|
|
};
|
|
|
|
async function main() {
|
|
const pb = await getAdminPocketBase();
|
|
|
|
// Resolve by name — works across PB instances even if the id shifts.
|
|
const coll = await pb.collections.getFirstListItem(`name="quote_attachments"`);
|
|
const quoteItemsColl = await pb.collections.getFirstListItem(`name="quote_items"`);
|
|
|
|
const fields = ((coll as unknown as { fields?: PbField[] }).fields ?? []) as PbField[];
|
|
const existingNames = new Set(fields.map((f) => f.name));
|
|
|
|
const planned: string[] = [];
|
|
|
|
if (existingNames.has("quote_item")) {
|
|
planned.push(`field "quote_item" already present — skip`);
|
|
} else {
|
|
fields.push({
|
|
hidden: false,
|
|
name: "quote_item",
|
|
presentable: false,
|
|
required: false,
|
|
system: false,
|
|
type: "relation",
|
|
collectionId: quoteItemsColl.id,
|
|
cascadeDelete: false,
|
|
minSelect: 0,
|
|
maxSelect: 1,
|
|
});
|
|
planned.push(`add "quote_item" relation field (→ quote_items, nullable)`);
|
|
}
|
|
|
|
if (existingNames.has("kind")) {
|
|
planned.push(`field "kind" already present — skip`);
|
|
} else {
|
|
fields.push({
|
|
hidden: false,
|
|
name: "kind",
|
|
presentable: false,
|
|
required: false,
|
|
system: false,
|
|
type: "select",
|
|
maxSelect: 1,
|
|
values: ["pdf", "image", "doc", "other"],
|
|
});
|
|
planned.push(`add "kind" select field (pdf | image | doc | other)`);
|
|
}
|
|
|
|
const willChange = planned.some((p) => !p.includes("skip"));
|
|
console.log("Planned schema changes:");
|
|
for (const p of planned) console.log(` • ${p}`);
|
|
|
|
if (willChange) {
|
|
await pb.collections.update(coll.id, { fields });
|
|
console.log("\n[done] schema updated.");
|
|
} else {
|
|
console.log("\n[done] schema already up to date — no changes written.");
|
|
}
|
|
|
|
// Read back to confirm what landed.
|
|
const after = (await pb.collections.getOne(coll.id)) as { fields?: PbField[] };
|
|
const afterNames = (after.fields ?? []).map((f) => f.name).join(", ");
|
|
console.log(`\nquote_attachments fields now: ${afterNames}`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[fatal]", err);
|
|
process.exit(1);
|
|
}); |