Files
bellsystems-cp/CRM_STATUS_SYSTEM_PLAN.md
bonamin 5d8ef96d4c 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>
2026-03-25 10:39:38 +02:00

18 KiB
Raw Permalink Blame History

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 11002000px, 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/