The Catalogue tab's search box used to OR across just sku,
description, and brand. Now it hits:
- sku, description, brand, supplier, category, subcategory
(PB's case-insensitive `~` operator, each indexed)
- search_text (a denormalised text blob)
The denormalised column is the key part: PB can't substring-
match a JSON field, so without this, synonyms from
`specifications.synonyms` ("slugger cutter", "turbo tough",
etc.) would never match in the search box. The n8n workflow's
Code node now flattens every searchable bit — including the
synonyms array — into search_text on every POST. The 46 pre-
existing rows were backfilled once with the same formula.
The `price_list.supplier` field also stored the PB id on the
46 pre-existing rows (n8n was setting the resolved name, but
the original LLM tests that seeded the table didn't). Fixed
that in the same backfill — supplier now reads 'JEISA -
WESTSCOTT GROUP' instead of 'q6hpwbxsicyoiuu', which makes
the supplier filter dropdown work AND makes
'supplier ~ "jeisa"' match.
Migration `2026-06-30-add-price-list-search-text`:
- Adds `search_text` (text) column
- Adds `idx_pl_search_text` non-unique index
- Idempotent — re-runs are safe and self-heal the index
Updates:
- `getPriceLists` helper ORs across all 6 columns + search_text
- `PriceList` TS type gains `search_text: string`
- `pricelist-extraction.md` Code node snippet shows how the
n8n workflow should populate `search_text` on every POST
- Search input placeholder reads 'Search anything — SKU,
description, brand, supplier, synonyms…'
Tests: 229/230 (same pre-existing failure, unrelated).
TypeScript clean.
Adds a second tab on /pricelist that shows the structured
`price_list` collection (AI-extracted line items) instead of the
raw PDF uploads. Operators can now:
- Search across SKU, description, and brand
- Filter by category (twist_drill, hss_cutter, countersink, ...)
- Filter by brand (JEI, TurboSteel, TurboTough, ...)
- See cost vs selling price side-by-side in ZAR
- Click a row's 'Source' link to jump back to the raw PDF
The distinct filter values are pulled from a one-shot getList(1, 500)
on mount, with hardcoded fallbacks (FALLBACK_CATEGORIES /
FALLBACK_BRANDS) so the dropdowns are useful even before any
extractions have run.
Layout: Tabs (Raw Uploads | Catalogue) wrap both views; the
header + page chrome stay shared. The DeleteDialog stays outside
the Tabs since it's a modal.
Also ships an idempotent migration to add `source_file` (text)
to `price_list` — used by the n8n workflow to track which raw
PDF each extracted row came from, and by the verify-after-POST
step the workflow uses to decide whether to flip
`raw_price_list.status` to Completed vs Failed.
Price type is in PriceListRow (shared subset) and PriceList
(full record). Supplier names are resolved client-side via
getAllSuppliers() since the live schema has supplier as a text
field rather than a relation.
Tests: 229/230 (same pre-existing failure, unrelated). TypeScript
clean.
Operators can now type per-upload instructions for the AI extraction
step in the Pricelist upload form. The notes go into a new
`raw_price_list.notes` field that the n8n workflow reads and
injects into the LLM user message as per-upload context.
Common uses:
- "Brand is JEI" → AI stamps every item with brand="JEI"
- "Apply 30% markup" → Code node regexes this out for selling_price
- "All prices in ZAR excl. VAT" → AI uses ZAR
- "Page 4 has discount codes" → AI skips those rows
Also auto-populates `raw_price_list.file_name` (the original
browser-side filename) on upload — PB slugifies the upload to a
token stored inside supplier_file, so without this field the
operator saw the slugged version. Existing rows backfilled by the
admin script.
The `log_summary` field is now reserved for the AI's write-back
summary (set by the n8n workflow after extraction). It was
previously operator-editable on the form, which was wrong —
overwriting it clobbered the AI's output.
New migration: scripts/migrations/2026-06-30-add-pricelist-notes.ts
(idempotent, only adds `notes` if missing — `file_name` was
added manually via the PB admin UI before this commit).
doc/pricelist-extraction.md updated with the n8n LLM user message
shape, the markup regex, and the notes field's role in the pipeline.
Adds /pricelist under the Management section. Operators drag a supplier
price-list file (PDF / CSV / text / image) into the upload zone — or
use the explicit Choose file button — and it lands in PocketBase as an
attachment on the raw_price_list collection. The table below shows the
queue: file, supplier (relation to suppliers), status badge, log
summary preview, upload date, and a delete action per row.
Layout matches the rest of the app: Card for the upload zone, DataTable
for the queue, DeleteDialog for the destructive action. Live updates
via the PB subscribe channel so other tabs / the n8n ingest workflow
show new rows without a manual refresh.
PB schema is sourced from docs/pocketbase/raw_price_list.collection.json.
The migration script is idempotent: it creates the collection from the
schema file on a fresh deploy, and on a pre-existing one (the live PB
already had it, with PB's default null/superuser-only rules) it relaxes
the auth rules to @request.auth.id != '' so any logged-in user can
CRUD — matching every other collection in the app.
Field names match the live PB collection (discovered via admin API):
- supplier_file (the file)
- supplier_name (relation to suppliers)
- status (select: Pending / Processing / Completed / Failed)
- log_summary (free text)
This commit overhauls the order management system to use item-level status tracking instead of a single order-level status:
- Remove deprecated `orders.status` field and add `invoiced` and quote match audit fields
- Add `isOrderComplete` utility with tests to validate fully delivered orders
- Add purchase order PDF rendering support and tooling
- Add geo coordinate fields for companies/customers with map link UI
- Add REST management APIs for customers, companies, and suppliers
- Update customer/company services to use relation fields instead of free text
- Add status pill and tab filter components for item status visualization
- Add quote-to-order matching logic and unit tests
- Update all pages, components, and tests to match the new schema
- Include all required PocketBase migrations and schema updates
Add complete quote template functionality, enhanced outbound email sending, new quote statuses, and supporting infrastructure:
- Add new quote statuses (template, sending, mail_failed) with tailored UI styling for status pills
- Implement full template workflows: save real quotes as templates, create new quotes from existing templates
- Add support for custom quote dates and extended validity periods on generated PDF documents
- Build out improved email pipeline with persisted compose fields and retry functionality for failed sends
- Add attachment direction tracking to categorize inbound, outbound, and generated quote attachments
- Fix tooltip component to properly handle Radix `asChild` prop to avoid React DOM attribute errors
- Add comprehensive test coverage, schema migrations, and update sample test and production data
This commit adds full quote PDF generation functionality with per-company letterhead branding:
- Server-side PDF rendering using @react-pdf/renderer matching the sample quote layout
- In-app modal PDF viewer with print, download, and new-tab controls
- Letterhead service to fetch and apply company-specific branding (CNL/ISHERWOOD)
- ZAR currency and date formatting utilities for South African quotes
- Updated QuoteAttachment type to handle PocketBase's array-based file tokens
- Fixed PocketBase attachment URL generation in quote email card
- Removed deprecated `included_in_quote` field from QuoteItem and quotes service
- Updated quote item creation test to validate draft item logic
- Added comprehensive unit tests for PDF rendering and formatting helpers
- Added dev scripts for debugging PDF and image rendering issues
- Added sample generated PDF files for visual verification
- Updated tsconfig to exclude development scripts directory
- Added @react-pdf/renderer production dependency
- Removed JEI as a valid letterhead option per requirements
- Add `user_edited` field to QuoteItem type to gate AI rewrites on user-edited content
- Add idempotent migration script to add and backfill the `user_edited` field across quote items
- Replace plain Select components with searchable SupplierSearch combobox across order and quote pages
- Add working days calculation utility and aged quote warning badges for overdue quotes
- Implement expandable row previews in the quotes list view
- Add full AI rewrite workflow: backend API endpoint, frontend modal, and n8n workflow definition
- Update package scripts for the new migration
Add comprehensive quote management and UI improvements:
- Add `email_received_at` field to quotes collection, TypeScript types, and setup script
- Update quote attachments schema to mark `kind` optional for legacy attachments, add `uploaded_at` field, and improve documentation comments
- Create `/api/v1/users/lookup` server endpoint to resolve user IDs to display names
- Add stable user ID → color mapping for visual grouping of claimed quotes
- Enhance quotes list page: reorder tab filters, show claimer names, color-code rows per claimant
- Update quote detail page to display claimer info with user icon
- Expand DataTable component to support per-row custom class and style props
- Overhaul QuoteEmailCard: add proper HTML/plain text body sanitization/linkification, inline PDF/image previews, improved metadata display, and fixed auth-aware attachment previews via blob URLs
This commit implements the full inbound quote email processing workflow and atomic quote numbering:
- Add PocketBase collections for monthly atomic quote counters, email attachments, and threaded replies
- Add idempotent migration script to set up new collections and add deduplication field to existing quotes table
- Implement `/api/v1/quotes/next-number` endpoint for properly formatted Q-YYYY-MM-NNN quote numbers
- Add MCP tools for idempotent quote creation, reply threading, and message ID lookup
- Add end-to-end and concurrent smoke tests for pipeline validation
- Update TypeScript types for new quote-related entities
- Update project documentation and package.json scripts
- Mark quote email intake as complete in the parity backlog
- Add n8n v2 workflows for automated email monitoring and error alerts
Fix inconsistent line endings across all project files for cross-environment consistency.
Add complete unit test suite covering core utilities, API routes, and server services.
Add Playwright E2E test setup with smoke, auth, and agent API tests.
Update all project configuration files to match standardized formatting.
Fix MCP API error handling to properly propagate HTTP status codes and use correct JSON-RPC error codes.
Add VSCode workspace configuration, extensions, and example MCP config files.
Update .gitignore to exclude new test and build artifacts.
Introduce PocketBase workflow support, migration tooling, server APIs and documentation. Adds a set of PocketBase collection JSONs (order_items, order_items_v2, collection_pool, delivery_notes, delivery_note_items, repairs) and a large import manifest for workflow collections; includes docs describing MCP/API layer, migration audit, order-item schema and rollout plans. Adds server-side agent/MCP endpoints and services (api/mcp, api/v1/orders, collection-pool, order-items status, health) plus helper modules (pocketbase-admin, orders-service, order-item-status, agent-auth) and a migration script to create order_items_v2/collection_pool. UI: new pages for collection-pool, delivery-notes, repairs and several updates to orders/quotes pages, sidebar, tooltip and pb helpers/types. Also updates package.json (migration script and related changes) and global styles. Required env vars and suggested migration command are documented (e.g. NEXT_PUBLIC_POCKETBASE_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, ORDER_ITEMS_COLLECTION; run npm run pb:migrate:orders-v2).
- Introduced a new Separator component for visual separation in UI.
- Added Sheet component for modal-like functionality with triggers and overlays.
- Implemented Toaster component for notifications using Sonner library.
- Created Switch component for toggle functionality.
- Developed Table component with headers, body, footer, and rows for structured data display.
- Added Tabs component for organizing content into tabbed interfaces.
- Implemented Textarea component for multi-line text input.
- Created Tooltip component for displaying additional information on hover.
feat: add PocketBase helpers and types
- Implemented various helper functions for interacting with PocketBase collections (customers, companies, suppliers, quotes, orders).
- Added type definitions for entities such as Customer, Company, Supplier, Quote, Order, and their respective items.
- Included utility functions for generating unique identifiers for quotes, orders, and purchase orders.
- Added functions for calculating totals and formatting currency and dates.
chore: add TypeScript configuration
- Added tsconfig.json for TypeScript support with strict type checking and module resolution.