- CustomerList, CustomerForm, CustomerDetail: various updates - Orders: removed OrderDetail and OrderForm, updated OrderList and index - DeviceDetail: updates - index.css: added new styles - CRM_STATUS_SYSTEM_PLAN.md: new planning document - Added customer-status assets and CustomerDetail subfolder Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
18 KiB
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: boolhas_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: boolandhas_problem: boolfromCustomerCreateandCustomerUpdate. - Add
relationship_status: Optional[str] = "lead"toCustomerCreateandCustomerUpdate. - Add
technical_issues: List[dict] = []toCustomerCreateandCustomerUpdate. - Add
install_support: List[dict] = []toCustomerCreateandCustomerUpdate. - Add
transaction_history: List[dict] = []toCustomerCreateandCustomerUpdate. - Add proper Pydantic models for each of the above array item shapes:
TechnicalIssuemodelInstallSupportEntrymodelTransactionEntrymodel
- Update
OrderStatusenum with the new values:negotiating,awaiting_quotation,awaiting_customer_confirmation,awaiting_fulfilment,awaiting_payment,manufacturing,shipped,installed,declined,complete - Replace the flat
PaymentStatusenum onOrderCreate/OrderUpdatewith a newOrderPaymentStatusPydantic model matching the structure above. - Add
title: Optional[str],created_by: Optional[str],status_updated_date: Optional[str],status_updated_by: Optional[str], andtimeline: List[dict] = []toOrderCreateandOrderUpdate.
2B. backend/crm/customers_router.py
- Update any route that reads/writes
negotiatingorhas_problemto 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
negotiatingandhas_problemboolean 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
negotiatingandhas_problemverbose displays with the active order status (if any) in this format:"<Status Label> — <Date> — <Note>"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 + SurnameLine 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_statusdefaults 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_statusis 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-issuesorPOST /crm/customers/{id}/install-supportdepending 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 toindex.cssif not already present). Clicking a status immediately callsPATCH /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.
- Row 1 — Relationship Status selector: The 5 statuses (
- 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:
titleas primary headingstatuswith human-readable label and color coding (see Section 4)payment_statussummary: 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
negotiatingandhas_problemfields. - Add
relationship_statusdropdown (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
titleinput field (required). - Replace flat
payment_statusenum with the newpayment_statusobject 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
negotiatingandhas_problemfields 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:- Read all customer documents
- If
negotiating: true→ create an order in the customer'sorderssubcollection withstatus = "negotiating"and setrelationship_status = "active"on the customer - If
has_problem: true→ append one entry totechnical_issueswithactive: true,opened_date: customer.updated_at,note: "Migrated from legacy has_problem flag",opened_by: "system" - Remove
negotiatingandhas_problemfrom 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/