WFDASH-WEBAPP/APP_CAPABILITIES.md

306 lines
17 KiB
Markdown
Raw Normal View History

# 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.