This commit includes the temporary Next.js build cache files, public brand logos, and project configuration added in this change.
306 lines
No EOL
17 KiB
Markdown
306 lines
No EOL
17 KiB
Markdown
# WFDASH — Westcott Fasteners Dashboard
|
|
|
|
## Overview
|
|
|
|
WFDASH is a Next.js (App Router) + PocketBase application for **Westcott Fasteners**, a fastener supply business. It manages the complete **quote-to-cash lifecycle** for industrial fastener orders — from inbound RFQ emails through pricing, supplier procurement, collection, delivery, and invoicing.
|
|
|
|
The system is operator-centric, built for the office team who process dozens of quotes and orders daily. It replaces manual spreadsheet/email workflows with structured pipelines, real-time dashboards, and automation via n8n workflows.
|
|
|
|
---
|
|
|
|
## Core Domains
|
|
|
|
### 1. **Quotes Pipeline** (`/quotes`, `/sprint`)
|
|
**Inbound RFQ → Processing → Approval → Send → Follow-up**
|
|
|
|
| Feature | Description |
|
|
|---------|-------------|
|
|
| **Email Intake** | n8n webhook receives inbound RFQ emails, extracts items via AI, creates quotes in PocketBase. Idempotent on `Message-ID`. |
|
|
| **Sprint (/sprint)** | Item-level work queue. Operators **claim individual line items** (30-min soft TTL), price them, and save — the item then drops off the list. Replaces "claim whole quote" bottleneck. |
|
|
| **Categories** | Admin-managed categories (JEI, TOOLS, FASTENERS, OIL, HUSQVARNA, UNKNOWN) with brand/supplier/keyword match rules. Items auto-bucket or land in UNKNOWN for review. |
|
|
| **AI Pricing** | n8n AI agent reads supplier price lists, matches SKUs, writes `cost_price` + `selling_price` with `pricing_source="ai"` or `"catalogue"`. Operator reviews before saving. |
|
|
| **Quote Statuses** | `new → claimed → processed → approval_pending → approved → emailed → complete` (also `rejected`, `problem`, `cancelled`, `mail_failed`, `follow_up`) |
|
|
| **Approvals** | Dedicated `/quotes/approvals` page for quotes needing sign-off. |
|
|
| **PDF Generation** | Branded quote PDFs (CNL / ISHERWOOD letterhead) with supplier authorization letters. |
|
|
| **Email Send** | Modal with CC/BCC, custom subject/body, attachment picker. Status flips to `sending` → n8n sends via SMTP → callback records `emailed` or `mail_failed`. Retry supported. |
|
|
| **Follow-ups** | Per-quote `follow_up_due` date; dashboard widget flags overdue. |
|
|
| **Customer Replies** | Inbound replies tracked via `quote_replies` collection; shows "Replied" chip on quote row. |
|
|
| **Templates** | Reusable quote scaffolding; "Create from template" spawns a real quote. |
|
|
|
|
---
|
|
|
|
### 2. **Orders** (`/orders`, `/orders/new`, `/orders/[id]`)
|
|
**Quote → Order → Line Items → Delivery → Invoice**
|
|
|
|
| Feature | Description |
|
|
|---------|-------------|
|
|
| **Create from Quote** | One-click converts a quote to an order, copying all line items. Atomic order number generation (`ORD-YYYY-MM-NNN`) via server-side counter — continues sequence across month boundaries, concurrency-safe. |
|
|
| **Order List** | Collapsible cards grouped by **item status tabs**: New, Processing, Collection Pool, Collected, Delivered, Cancelled. Search spans order #, company, customer, item description, or price. |
|
|
| **Item Status Lifecycle** | `pending → processed → po_generated → collection_pool → assigned → collected → stock_verified → picking → ready_delivery → delivered` (terminal). Also `ready_invoice`, `invoiced`, `stock_booked`, `cancelled`. |
|
|
| **Quote Matching** | Auto-links order to quote via exact match on quote_number / reference / RFQ / external_number. Suggests a "potential quote" when fuzzy. |
|
|
| **Repair Orders** | `is_repair_order` flag for non-standard jobs. |
|
|
|
|
---
|
|
|
|
### 3. **Operations / Collection** (`/collection-pool`, `/collection-map`, `/collection-confirm`)
|
|
**Supplier Collection → Driver Assignment → Confirmation**
|
|
|
|
| Page | Purpose |
|
|
|------|---------|
|
|
| **Collection Pool** | Supplier-grouped view of items in `collection_pool` or `assigned` status. Per-row "Assign driver" + per-supplier bulk assign. Shows PO #, supplier, age, ready-for-collection flag. |
|
|
| **Collection Map** | Leaflet map with supplier markers clustered by proximity. Click a cluster → see items → assign driver inline. |
|
|
| **Collection Confirm** | Driver-facing (mobile) — scan/confirm collection, marks items `collected`. |
|
|
|
|
---
|
|
|
|
### 4. **Invoicing** (`/invoicing`)
|
|
**Delivery Note → QuickBooks Invoice → Print**
|
|
|
|
| Feature | Description |
|
|
|---------|-------------|
|
|
| **To Invoice Tab** | Orders grouped by order number where at least one item is `ready_invoice`. Per-line qty/price overrides for the QB payload. |
|
|
| **QuickBooks Integration** | Modal picks QB company (CNL / ISHERWOOD), searches QB customers via n8n webhook, builds invoice payload, POSTs to n8n → QB. Returns PDF URL stored on `order_items.qb_invoice_pdf`. |
|
|
| **Delivery Notes** | Parallel flow: mark lines `delivery_note` status, auto-generates `DN{YYMMDD}NN` number. Same line editor (include/exclude, qty/price override). |
|
|
| **Invoiced Tab** | Two sections: **Today's Unprinted** (grouped by invoice #, print badges) + **All Invoiced** (searchable history). Print tracking (printedAt, printedBy, printCount) stored in `order_item_details.print` JSON. |
|
|
| **PDF Print** | Server-rendered React-PDF → streamed back. One-click print for single or bulk (today's batch). |
|
|
|
|
---
|
|
|
|
### 5. **Stock Management** (`/stock`, `/stock/[id]`, `/stock-orders`)
|
|
**Inventory → Surplus Booking → Stock Orders → Ledger**
|
|
|
|
| Feature | Description |
|
|
|---------|-------------|
|
|
| **Stock Items** | SKU, name, unit, location, current_qty, min_qty, supplier. Status badge: OK / Low / Critical / Inactive. |
|
|
| **Stock Ledger** | Immutable append-only ledger (`stock_ledger`) with signed `change_qty` and `reason` (`purchase_surplus`, `stock_order`, `customer_fulfillment`, `adjustment`, `return`). `current_qty` denormalized for fast reads. |
|
|
| **Surplus Booking** | When a supplier delivers more than ordered, operator splits the line: customer qty → order, surplus qty → stock. Creates `stock_ledger` entry (`reason=purchase_surplus`) + links `order_item.surplus_qty` + `stock_booked_from`. |
|
|
| **Stock Orders (v2)** | Pure stock replenishment: `SP-YYYYMMDDNN` number, multi-line, status `draft → sent → received → cancelled`. Receive flow books ledger + updates `current_qty` per line. |
|
|
| **Fulfilment from Stock** | `/api/v1/stock/fulfill` — books out qty from stock for a customer order, creates ledger entry (`reason=customer_fulfillment`). |
|
|
|
|
---
|
|
|
|
### 6. **Pricelist / Catalogue** (`/pricelist`)
|
|
**Raw Uploads → AI Extraction → Structured Catalogue**
|
|
|
|
| Tab | Purpose |
|
|
|-----|---------|
|
|
| **Raw Uploads** | Drag-drop PDF/CSV/TXT/images (≤50 MB). Optional supplier link, status, **free-text notes for AI** (e.g. "Brand is JEI. Apply 30% markup. ZAR excl. VAT."). Upload creates `raw_price_list` row; n8n workflow picks up, extracts, writes to `price_list`. |
|
|
| **Catalogue** | Searchable `price_list` table: SKU, normalised SKU, description, category, brand, supplier, cost/selling/markup, stock_qty, rich `specifications` JSON (diameter, length, material, synonyms). `search_text` column enables fuzzy/synonym search. |
|
|
|
|
---
|
|
|
|
### 7. **Purchase Orders** (embedded in Orders flow)
|
|
**PO Generation → Email → Supplier Reply Tracking**
|
|
|
|
- Created from order items grouped by supplier.
|
|
- Versioned PDFs stored on `purchase_orders.documents` array + `documents_history`.
|
|
- Email modal (to/cc/bcc, HTML+text body, attachment picker) → n8n sends → callback records `email_message_id`, `last_reply_at`, `supplier_reply_preview`.
|
|
- Stale PO detection (`purchase-orders/stale` endpoint).
|
|
|
|
---
|
|
|
|
### 8. **Delivery Notes** (`/delivery-notes`)
|
|
**Generated from delivered order items**
|
|
|
|
- Auto-numbered `DN{YYMMDD}NN`.
|
|
- Status: `draft → ready → delivered → cancelled`.
|
|
- Linked to order + company + customer.
|
|
- Printable PDF (shares invoice print pipeline).
|
|
|
|
---
|
|
|
|
### 9. **Repairs** (`/repairs`)
|
|
**Lightweight repair job tracker**
|
|
|
|
- Repair number auto-generated.
|
|
- Status: `new → assessing → quoted → approved → in_progress → completed → cancelled`.
|
|
- Links to customer / company / order (optional).
|
|
- Item description + fault description + notes.
|
|
|
|
---
|
|
|
|
### 10. **Companies / Customers / Suppliers** (`/companies`, `/customers`, `/suppliers`)
|
|
**Master data management**
|
|
|
|
- **Companies**: name, address, email, tel, VAT, reg number, **geo-coordinates** (for map).
|
|
- **Customers**: name, emails (primary/alt/buyer), company relation, rep (user), delivery coordinates, description.
|
|
- **Suppliers**: name, email, number, address, supply_tags (for category matching), coordinates, closing time.
|
|
|
|
---
|
|
|
|
### 11. **Dashboard** (`/dashboard`)
|
|
**Real-time KPIs & Charts**
|
|
|
|
| Widget | Data |
|
|
|--------|------|
|
|
| KPI Cards | Customers, Active Quotes, Orders This Month, Revenue (sparkline per card) |
|
|
| Concentric Rings | Quote pipeline depth (New / Claimed / Approved / Processed) |
|
|
| Orders Trend | New vs Fulfilled (8 weeks, stacked bars) |
|
|
| Status Mix | Quote distribution (progress bars) |
|
|
| Growth Trends | Customers vs Orders (12 weeks, area chart) |
|
|
| Approval Rate | Radial gauge (Approved / Active) |
|
|
| Activity Pulse | Multi-line (Customers / Quotes / Orders velocity) |
|
|
| Quote Pipeline | Donut chart (all non-template quotes) |
|
|
| Top Customers | Horizontal bars by quote volume |
|
|
| Follow-ups Due | Clickable cards → quote detail |
|
|
|
|
**Real-time**: PocketBase subscriptions on `customers`, `quotes`, `orders` → auto-refresh.
|
|
|
|
---
|
|
|
|
### 12. **Settings** (`/settings`)
|
|
- Theme picker (light/dark/system) with CSS variables.
|
|
- Developer identity switcher (for local multi-user testing).
|
|
|
|
---
|
|
|
|
## Automation & Integrations (n8n)
|
|
|
|
| Workflow | Trigger | Action |
|
|
|----------|---------|--------|
|
|
| **Email Intake** | Inbound email (IMAP/webhook) | Parse → AI extract items → Create quote + items + attachments |
|
|
| **Quote Outbound** | Quote status = `sending` | Render PDF → SMTP send → Callback `recordSendOutcome` |
|
|
| **Supplier RFQ** | Sprint bulk "Email suppliers" | Group by supplier → Send RFQ → Track `outbound_message_id` |
|
|
| **Supplier Reply Monitor** | Inbound email with `In-Reply-To` matching PO/quote | Update `supplier_replied_at`, `supplier_reply_count` |
|
|
| **Price List Ingest** | New `raw_price_list` row | AI extract → Write `price_list` rows → Update `log_summary` |
|
|
| **QuickBooks Invoice** | Invoicing modal submit | Build QB payload → QB API → Return PDF URL |
|
|
| **PO Stale Alert** | Scheduled | Find POs without reply > N days → Notify |
|
|
|
|
---
|
|
|
|
## Technical Architecture
|
|
|
|
| Layer | Technology |
|
|
|-------|------------|
|
|
| **Frontend** | Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS |
|
|
| **Backend** | PocketBase (Go) — auth, real-time, file storage, admin UI |
|
|
| **Automation** | n8n (self-hosted) — workflows, AI agents, webhooks |
|
|
| **Charts** | Custom SVG (no Chart.js/Recharts) — sparklines, bars, donuts, radial, area, concentric |
|
|
| **PDF** | `@react-pdf/renderer` (server-side in API routes) |
|
|
| **Maps** | Leaflet + OpenStreetMap |
|
|
| **Auth** | PocketBase auth (email/password), cookie-based session via middleware |
|
|
| **Real-time** | PocketBase `subscribe("*", callback)` on collections |
|
|
| **API** | Next.js Route Handlers (`/api/v1/...`) + PocketBase SDK (server) |
|
|
|
|
---
|
|
|
|
## Key Design Patterns
|
|
|
|
1. **Item-Level Workflows** — Not quote/order level. Sprint, Collection Pool, Invoicing all operate on `order_items` / `quote_items` rows.
|
|
2. **Optimistic UI + Real-time** — Local state updates immediately; PB subscriptions reconcile.
|
|
3. **Server-Side Number Generation** — Atomic counters (`quote_counters`, `order_counters`) prevent collisions.
|
|
4. **Idempotency** — Email intake dedups on `Message-ID`; quote/PO sends record outbound `Message-ID` for bounce matching.
|
|
5. **Human-in-the-Loop AI** — AI writes `pricing_source="ai"` + zero prices; operator must claim, review, save.
|
|
6. **JSON Audit Fields** — `order_item_details`, `quote_item.pricing_notes`, `print` metadata stored as JSON on the row — no extra collections for append-only logs.
|
|
7. **Per-User Color Coding** — `claimed_by` / `rep` → deterministic hue (OKLCH) for row tinting + avatar chips.
|
|
8. **Working-Day SLA** — `isOlderThanWorkingDays()` excludes weekends; dashboard ages quotes in working days.
|
|
|
|
---
|
|
|
|
## Data Model Highlights (PocketBase Collections)
|
|
|
|
| Collection | Purpose |
|
|
|------------|---------|
|
|
| `quotes` | Quote header (number, customer, company, status, email thread, PDF, follow-up) |
|
|
| `quote_items` | Line items (description, qty, cost/sell/markup, supplier, claim_status, pricing_source, category) |
|
|
| `quote_attachments` | Inbound RFQ files + generated PDFs + outbound attachments |
|
|
| `quote_replies` | Customer email replies (thread) |
|
|
| `orders` | Order header (number, company, customer, quote link, rep, invoiced flag) |
|
|
| `order_items` | Line items (status lifecycle, supplier, PO link, in_stock, surplus_qty, pricing audit) |
|
|
| `purchase_orders` | Supplier POs (versioned PDFs, email payload, supplier replies) |
|
|
| `collection_pool` | Driver assignment queue (links order_item + supplier + driver) |
|
|
| `stock_items` | Inventory master (SKU, qty, min, location, supplier) |
|
|
| `stock_ledger` | Immutable movements (signed qty, reason, reference) |
|
|
| `stock_purchases` | Stock replenishment orders |
|
|
| `price_list` | Structured catalogue (AI-extracted) |
|
|
| `raw_price_list` | Uploaded supplier files awaiting extraction |
|
|
| `categories` | Admin-managed buckets for sprint (match_brands, match_supplier_tags, match_keywords) |
|
|
| `delivery_notes` | Delivery note header |
|
|
| `delivery_note_items` | Line items on delivery note |
|
|
| `repairs` | Repair jobs |
|
|
| `companies` / `customers` / `suppliers` | Master data |
|
|
| `users` (auth) | Operators + drivers |
|
|
|
|
---
|
|
|
|
## Pages / Routes Summary
|
|
|
|
| Route | Purpose |
|
|
|-------|---------|
|
|
| `/dashboard` | KPI dashboard |
|
|
| `/quotes` | Quote list (tabs: New, Claimed, Pending Approval, Completed, Email Failed, Problem, All, Templates) |
|
|
| `/quotes/[id]` | Quote detail (items, pricing, PDF, email, approvals) |
|
|
| `/quotes/[id]/process` | Sprint-style item processing for a single quote |
|
|
| `/quotes/approvals` | Approval queue |
|
|
| `/sprint` | Global item-level pricing queue (by category) |
|
|
| `/orders` | Order list (item-status tabs) |
|
|
| `/orders/new` | Create order (from quote or blank) |
|
|
| `/orders/[id]` | Order detail (items, status transitions, PO, delivery, invoice) |
|
|
| `/orders/assign` | Legacy driver assign (hidden from nav) |
|
|
| `/invoicing` | To Invoice / Invoiced (QB + delivery notes + print) |
|
|
| `/collection-pool` | Supplier collection queue + bulk assign |
|
|
| `/collection-map` | Map view of suppliers |
|
|
| `/collection-confirm` | Mobile driver confirmation |
|
|
| `/stock` | Stock list + low-stock filter |
|
|
| `/stock/[id]` | Stock detail + ledger + adjustments |
|
|
| `/stock-orders` | Stock replenishment orders |
|
|
| `/pricelist` | Raw uploads + Catalogue tabs |
|
|
| `/suppliers` | Supplier CRUD |
|
|
| `/companies` | Company CRUD |
|
|
| `/customers` | Customer CRUD |
|
|
| `/repairs` | Repair job tracker |
|
|
| `/delivery-notes` | Delivery note list |
|
|
| `/settings` | Theme, dev identity |
|
|
|
|
---
|
|
|
|
## API Endpoints (Key)
|
|
|
|
| Endpoint | Method | Purpose |
|
|
|----------|--------|---------|
|
|
| `/api/v1/quotes/[id]/pdf` | GET | Render quote PDF |
|
|
| `/api/v1/quotes/[id]/send-email` | POST | Validate + queue outbound send |
|
|
| `/api/v1/quotes/[id]/retry-email` | POST | Retry failed send |
|
|
| `/api/v1/quotes/[id]/create-from-template` | POST | Spawn quote from template |
|
|
| `/api/v1/orders/from-quote` | POST | Convert quote → order |
|
|
| `/api/v1/order-items/[id]/status` | PATCH | Transition item status |
|
|
| `/api/v1/invoicing/customers` | GET/POST | QB customer search/create |
|
|
| `/api/v1/stock/items` | GET/POST | Stock CRUD |
|
|
| `/api/v1/stock/items/[id]/adjust` | POST | Manual qty adjustment + ledger |
|
|
| `/api/v1/stock/fulfill` | POST | Book out stock for customer order |
|
|
| `/api/v1/stock/surplus-book` | POST | Book surplus from collection |
|
|
| `/api/v1/stock/purchases` | GET/POST | Stock orders CRUD |
|
|
| `/api/v1/stock/purchases/[id]/receive` | POST | Receive stock order lines |
|
|
| `/api/v1/sprint/items/[id]/claim` | POST | Race-safe 30-min claim |
|
|
| `/api/v1/sprint/items/[id]/attachments/upload` | POST | Upload pricing proof |
|
|
| `/api/v1/purchase-orders/[id]/send-email` | POST | Send PO to supplier |
|
|
| `/api/v1/purchase-orders/[id]/pdf` | GET | Render PO PDF |
|
|
| `/api/v1/files/[collection]/[recordId]/[filename]` | GET | Stream PocketBase file |
|
|
|
|
---
|
|
|
|
## Deployment Notes
|
|
|
|
- **PocketBase** runs separately (Docker/binary). App connects via `NEXT_PUBLIC_POCKETBASE_URL`.
|
|
- **n8n** runs separately; webhooks configured in n8n point to `/api/v1/...` routes.
|
|
- **Files** served via PocketBase `/api/files/...` — proxied through Next.js route for auth if needed.
|
|
- **Environment**: `NEXT_PUBLIC_POCKETBASE_URL`, `POCKETBASE_ADMIN_EMAIL`, `POCKETBASE_ADMIN_PASSWORD` (for server SDK).
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
WFDASH is a **complete operational ERP** for a fastener distributor, covering:
|
|
|
|
1. **Quote intake** (email → AI → structured quote)
|
|
2. **Item-level pricing** (Sprint claim/price/save)
|
|
3. **Order management** (quote → order → multi-status item lifecycle)
|
|
4. **Supplier procurement** (PO generation, email, reply tracking)
|
|
5. **Collection logistics** (pool → map → driver assign → confirm)
|
|
6. **Inventory** (stock items, ledger, surplus booking, stock orders)
|
|
7. **Invoicing** (delivery notes + QuickBooks + PDF print)
|
|
8. **Catalogue** (raw uploads → AI extraction → searchable price list)
|
|
9. **Repairs** (lightweight job tracker)
|
|
10. **Real-time dashboard** (KPIs, charts, follow-ups)
|
|
|
|
Built for **speed, auditability, and operator ergonomics** — every screen is a work queue, not a CRUD form. |