# CRM Customer Status System — Implementation Plan ## Context This project is a Vue/React + FastAPI + Firestore admin console located at `C:\development\bellsystems-cp`. The frontend lives in `frontend/src/` and the backend in `backend/`. The CRM module is at `frontend/src/crm/` and `backend/crm/`. Currently, customers have two flat boolean flags on their Firestore document: - `negotiating: bool` - `has_problem: bool` These need to be replaced with a richer, structured system as described below. --- ## 1. Target Data Model ### 1A. On the Customer Document (`customers/{id}`) Remove `negotiating` and `has_problem`. Add the following: ``` relationship_status: string — one of: "lead" | "prospect" | "active" | "inactive" | "churned" — default: "lead" technical_issues: array of { active: bool, opened_date: Firestore Timestamp, resolved_date: Firestore Timestamp | null, note: string, opened_by: string, ← display name or user ID of staff member resolved_by: string | null } install_support: array of { active: bool, opened_date: Firestore Timestamp, resolved_date: Firestore Timestamp | null, note: string, opened_by: string, resolved_by: string | null } transaction_history: array of { date: Firestore Timestamp, flow: string, ← "invoice" | "payment" | "refund" | "credit" payment_type: string | null, ← "cash" | "bank_transfer" | "card" | "paypal" — null for invoices category: string, ← "full_payment" | "advance" | "installment" amount: number, currency: string, ← default "EUR" invoice_ref: string | null, order_ref: string | null, ← references an order document ID, nullable recorded_by: string, note: string } ``` ### 1B. Orders Subcollection (`customers/{id}/orders/{order_id}`) Orders live **exclusively** as a subcollection under each customer. There is no top-level `orders` collection. The existing top-level `orders` collection in Firestore and its corresponding backend routes should be **removed entirely** and replaced with subcollection-based routes under `/crm/customers/{customer_id}/orders/`. If cross-customer order querying is ever needed in the future, use Firestore's native `collectionGroup("orders")` query — no top-level mirror collection is required. Each order document carries the following fields: ``` order_number: string ← e.g. "ORD-2026-041" (already exists — keep) title: string ← NEW: human-readable name e.g. "3x Wall Mount Units - Athens Office" created_by: string ← NEW: staff user ID or display name status: string ← REPLACE existing OrderStatus enum with new values: — "negotiating" | "awaiting_quotation" | "awaiting_customer_confirmation" | "awaiting_fulfilment" | "awaiting_payment" | "manufacturing" | "shipped" | "installed" | "declined" | "complete" status_updated_date: Firestore Timestamp ← NEW status_updated_by: string ← NEW payment_status: object { ← NEW — replaces the flat PaymentStatus enum required_amount: number, received_amount: number, ← computed from transaction_history where order_ref matches balance_due: number, ← computed: required_amount - received_amount advance_required: bool, advance_amount: number | null, payment_complete: bool } timeline: array of { ← NEW — order event log date: Firestore Timestamp, type: string, ← "quote_request" | "quote_sent" | "quote_accepted" | "quote_declined" | "mfg_started" | "mfg_complete" | "order_shipped" | "installed" | "payment_received" | "invoice_sent" | "note" note: string, updated_by: string } ``` --- ## 2. Backend Changes ### 2A. `backend/crm/models.py` - **Remove** `negotiating: bool` and `has_problem: bool` from `CustomerCreate` and `CustomerUpdate`. - **Add** `relationship_status: Optional[str] = "lead"` to `CustomerCreate` and `CustomerUpdate`. - **Add** `technical_issues: List[dict] = []` to `CustomerCreate` and `CustomerUpdate`. - **Add** `install_support: List[dict] = []` to `CustomerCreate` and `CustomerUpdate`. - **Add** `transaction_history: List[dict] = []` to `CustomerCreate` and `CustomerUpdate`. - **Add** proper Pydantic models for each of the above array item shapes: - `TechnicalIssue` model - `InstallSupportEntry` model - `TransactionEntry` model - **Update** `OrderStatus` enum with the new values: `negotiating`, `awaiting_quotation`, `awaiting_customer_confirmation`, `awaiting_fulfilment`, `awaiting_payment`, `manufacturing`, `shipped`, `installed`, `declined`, `complete` - **Replace** the flat `PaymentStatus` enum on `OrderCreate` / `OrderUpdate` with a new `OrderPaymentStatus` Pydantic model matching the structure above. - **Add** `title: Optional[str]`, `created_by: Optional[str]`, `status_updated_date: Optional[str]`, `status_updated_by: Optional[str]`, and `timeline: List[dict] = []` to `OrderCreate` and `OrderUpdate`. ### 2B. `backend/crm/customers_router.py` - Update any route that reads/writes `negotiating` or `has_problem` to use the new fields. - Add new dedicated endpoints: ``` POST /crm/customers/{id}/technical-issues — body: { note: str, opened_by: str } — appends a new active issue to the array PATCH /crm/customers/{id}/technical-issues/{index}/resolve — body: { resolved_by: str } — sets active=false and resolved_date=now on the item at that index POST /crm/customers/{id}/install-support — same pattern as technical-issues above PATCH /crm/customers/{id}/install-support/{index}/resolve — same as technical-issues resolve POST /crm/customers/{id}/transactions — body: TransactionEntry (see model above) — appends to transaction_history PATCH /crm/customers/{id}/relationship-status — body: { status: str } — updates relationship_status field ``` ### 2C. `backend/crm/orders_router.py` - **Remove** all top-level `/crm/orders/` routes entirely. - Re-implement all order CRUD under `/crm/customers/{customer_id}/orders/`: ``` GET /crm/customers/{customer_id}/orders/ POST /crm/customers/{customer_id}/orders/ GET /crm/customers/{customer_id}/orders/{order_id} PATCH /crm/customers/{customer_id}/orders/{order_id} DELETE /crm/customers/{customer_id}/orders/{order_id} ``` - Add endpoint to append a timeline event: ``` POST /crm/customers/{customer_id}/orders/{order_id}/timeline — body: { type: str, note: str, updated_by: str } — appends to the timeline array and updates status_updated_date + status_updated_by ``` - Add endpoint to update payment status: ``` PATCH /crm/customers/{customer_id}/orders/{order_id}/payment-status — body: OrderPaymentStatus fields (partial update allowed) ``` - Add a dedicated "Init Negotiations" endpoint: ``` POST /crm/customers/{customer_id}/orders/init-negotiations — body: { title: str, note: str, date: datetime, created_by: str } — creates a new order with status="negotiating", auto-fills all other fields — simultaneously updates the customer's relationship_status to "active" (only if currently "lead" or "prospect" — do not downgrade an already "active" customer) — returns the newly created order document ``` --- ## 3. Frontend Changes ### 3A. `frontend/src/crm/customers/CustomerList.jsx` - When Notes: Quick filter is set, replace the `negotiating` and `has_problem` boolean badge display in the Status column with: - A **relationship status chip** (color-coded pill: lead=grey, prospect=blue, active=green, inactive=amber, churned=soft red) - A small **red dot / warning icon** if `technical_issues.some(i => i.active)` is true, under a new "Support" column. Add this column to the list of arrangeable and toggleable columns. - A small **amber dot / support icon** if `install_support.some(i => i.active)` is true, under the same "Support" column. - These are derived from the arrays — do not store a separate boolean on the document. - When Notes: Expanded filter is set, replace the `negotiating` and `has_problem` verbose displays with the active order status (if any) in this format: `""` e.g. `"Negotiating — 24.03.26 — Customer requested a more affordable quotation"` ### 3B. `frontend/src/crm/customers/CustomerDetail.jsx` The customer detail page currently has a tab structure: Overview, Orders, Quotations, Communication, Files & Media, Devices. Make the following changes: #### Whole page - On the top of the page where we display the name, organization and full address, change it to: Line 1: `Full Title + Name + Surname` Line 2: `Organization · City` (city only, not full address) - Remove the horizontal separation line after the title and before the tabs. - On the top right side, there is an Edit Customer button. To its left, add **3 new buttons** in this order (left → right): **Init Negotiations**, **Record Issue/Support**, **Record Payment**, then the existing Edit button. All 4 buttons are the same size. Add solid single-color icons to each. **"Init Negotiations" button** (blue/indigo accent): - Opens a mini modal. - Fields: Date (defaults to NOW), Title (text input, required), Note (textarea, optional). - Auto-filled server-side: `status = "negotiating"`, `created_by` = current user, `status_updated_date` = now, `status_updated_by` = current user, `payment_status` defaults to zeroed object. - On confirm: calls `POST /crm/customers/{id}/orders/init-negotiations`. - After success: refreshes customer data and orders list. The customer's `relationship_status` is set to `"active"` server-side — no separate frontend call needed. - This is a fast-entry shortcut only. All subsequent edits to this order happen via the Orders tab. **"Record Issue/Support" button** (amber/orange accent): - Opens a mini modal. - At the top: a **2-button toggle selector** (not a dropdown) to choose: `Technical Issue` | `Install Support`. - Fields: Date (defaults to NOW), Note (textarea, required). - On confirm: calls `POST /crm/customers/{id}/technical-issues` or `POST /crm/customers/{id}/install-support` depending on selection. **"Record Payment" button** (green accent): - Opens a mini modal. - Fields: Date (defaults to NOW), Payment Type (cash | bank transfer | card | paypal), Category (full payment | advance | installment), Amount (number), Currency (defaults to EUR), Invoice Ref (searchable over the customer's invoices, optional), Order Ref (searchable/selectable from the customer's orders, optional), Note (textarea, optional). - On confirm: calls `POST /crm/customers/{id}/transactions`. #### Overview Tab - The main hero section gets a complete overhaul — start fresh: - **Row 1 — Relationship Status selector**: The 5 statuses (`lead | prospect | active | inactive | churned`) as styled pill/tab buttons in a row. Current status is highlighted with a glow effect. Color-code using global CSS variables (add to `index.css` if not already present). Clicking a status immediately calls `PATCH /crm/customers/{id}/relationship-status`. - **Row 2 — Customer info**: All fields except Name and Organization (shown in page header). Include language, religion, tags, etc. - **Row 3 — Contacts**: All contact entries (phone, email, WhatsApp, etc.). - **Row 4 — Notes**: Responsive column grid. 1 column below 1100px, 2 columns 1100–2000px, 3 columns above 2000px. Masonry/wrap layout with no gaps between note cards. - Move the Latest Orders section to just below the hero section, before Latest Communications. Hide this section entirely if no orders exist for this customer. - For all other sections (Latest Communications, Latest Quotations, Devices): hide each section entirely if it has no data. Show dynamically when data exists. #### New "Support" Tab (add to TABS array, after Overview) Two full-width section cards: **Technical Issues Card** - Header shows active count badge (e.g. "2 active") - All issues listed, newest first (active and resolved) - Each row: colored status dot, opened date, note, opened_by — "Resolve" button if active - If more than 5 items: list is scrollable (fixed max-height), does not expand the page - "Report New Issue" button → small inline form with note field + submit **Install Support Card** - Identical structure to Technical Issues card - Same scrollable behavior if more than 5 items #### New "Financials" Tab (add to TABS array, after Support) Two sections: **Active Order Payment Status** (shown only if an active order exists) - required_amount, received_amount, balance_due - Advance required indicator + advance amount if applicable - Payment complete indicator **Transaction History** - Ledger table: Date | Flow | Amount | Currency | Method | Category | Order Ref | Invoice Ref | Note | Recorded By | Actions - "Add Transaction" button → modal with all TransactionEntry fields - Totals row: Total Invoiced vs Total Paid vs Outstanding Balance - Each row: right-aligned **Actions** button (consistent with other tables in the project) with options: **Edit** (opens edit form) and **Delete** (requires confirmation dialog) #### Orders Tab (existing — update in place) - Each order card/row shows: - `title` as primary heading - `status` with human-readable label and color coding (see Section 4) - `payment_status` summary: required / received / balance due - **"View Timeline"** toggle: expands a vertical event log below the order card - **"Add Timeline Event"** button: small inline form with type dropdown + note field - Update all API calls to use `/crm/customers/{customer_id}/orders/` routes. ### 3C. `frontend/src/crm/customers/CustomerForm.jsx` - Remove `negotiating` and `has_problem` fields. - Add `relationship_status` dropdown (default: `"lead"`). - No issue/transaction forms needed here — managed from the detail page. ### 3D. `frontend/src/crm/orders/OrderForm.jsx` and `OrderDetail.jsx` - Update status dropdown with new values and labels: - `negotiating` → "Negotiating" - `awaiting_quotation` → "Awaiting Quotation" - `awaiting_customer_confirmation` → "Awaiting Customer Confirmation" - `awaiting_fulfilment` → "Awaiting Fulfilment" - `awaiting_payment` → "Awaiting Payment" - `manufacturing` → "Manufacturing" - `shipped` → "Shipped" - `installed` → "Installed" - `declined` → "Declined" - `complete` → "Complete" - Add `title` input field (required). - Replace flat `payment_status` enum with the new `payment_status` object fields. - Add Timeline section to `OrderDetail.jsx`: vertical event log + add-entry inline form. - Update all API calls to use `/crm/customers/{customer_id}/orders/` routes. --- ## 4. Status Color Coding Reference Define all as CSS variables in `index.css` and use consistently across all views: ### Relationship Status | Status | Color | |---|---| | lead | grey / muted | | prospect | blue | | active | green | | inactive | amber | | churned | dark or soft red | ### Order Status | Status | Color | |---|---| | negotiating | blue | | awaiting_quotation | purple | | awaiting_customer_confirmation | indigo | | awaiting_fulfilment | amber | | awaiting_payment | orange | | manufacturing | cyan | | shipped | teal | | installed | green | | declined | red | | complete | muted/grey | ### Issue / Support Flags | State | Color | |---|---| | active issue | red | | active support | amber | | resolved | muted/grey | --- ## 5. Migration Notes - The old `negotiating` and `has_problem` fields will remain in Firestore until the migration script is run. The backend should **read both old and new fields** during the transition period, preferring the new structure if present. - A one-time migration script (`backend/migrate_customer_flags.py`) should: 1. Read all customer documents 2. If `negotiating: true` → create an order in the customer's `orders` subcollection with `status = "negotiating"` and set `relationship_status = "active"` on the customer 3. If `has_problem: true` → append one entry to `technical_issues` with `active: true`, `opened_date: customer.updated_at`, `note: "Migrated from legacy has_problem flag"`, `opened_by: "system"` 4. Remove `negotiating` and `has_problem` from the customer document - Do **not** run the migration script until all frontend and backend changes are deployed and tested. --- ## 6. File Summary — What to Touch ``` backend/crm/models.py ← model updates (primary changes) backend/crm/customers_router.py ← new endpoints + field updates backend/crm/orders_router.py ← remove top-level routes, re-implement as subcollection, add timeline + payment-status + init-negotiations endpoints backend/migrate_customer_flags.py ← NEW one-time migration script frontend/src/index.css ← add CSS variables for all new status colors frontend/src/crm/customers/CustomerList.jsx ← relationship status chip + support flag dots column frontend/src/crm/customers/CustomerDetail.jsx ← page header, 3 new quick-entry buttons + modals, Overview tab overhaul, new Support tab, new Financials tab, Orders tab updates frontend/src/crm/customers/CustomerForm.jsx ← remove old flags, add relationship_status frontend/src/crm/orders/OrderForm.jsx ← new status values, title field, payment_status, updated API route paths frontend/src/crm/orders/OrderDetail.jsx ← timeline section, updated status/payment, updated API route paths ``` --- ## 7. Do NOT Change (out of scope) - Quotations system — leave as-is - Communications / inbox — leave as-is - Files & Media tab — leave as-is - Devices tab — leave as-is - Any other module outside `crm/`