update: CRM customers, orders, device detail, and status system changes
- 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>
This commit is contained in:
404
CRM_STATUS_SYSTEM_PLAN.md
Normal file
404
CRM_STATUS_SYSTEM_PLAN.md
Normal file
@@ -0,0 +1,404 @@
|
||||
# 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:
|
||||
`"<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 + 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/`
|
||||
Reference in New Issue
Block a user