Skip to content

CRM Commerce (Inventory · Orders · Transactions · Monitor) ​

The connected commerce layer for the CRM: manage a product Inventory, place Orders against leads, record Transactions (payments) against orders, and watch team + sales activity in Monitor all in one linked view.

Relationships enforced end-to-end: Lead → Order → Transaction and Order → Inventory.


Concepts ​

  • Tenant boundary every commerce record is owned by a customer-admin's business and keyed on customerUID (never orgUID alone). orgUID is a secondary rollup tag written on create. This mirrors CRM Leads exactly, so two businesses in one org never see each other's inventory/orders/payments.
  • Inventory a shared product catalog for the business. Not owned by any one lead; an order references inventory items.
  • Order links a lead to one or more inventory line items. Carries a status (built-in + custom) and a server-computed total.
  • Transaction a payment linked to an order (and, via the order, to the lead). leadId/leadName are denormalized from the order.
  • Monitor read-only activity dashboard (Members / Orders / Payments) with a day/week/month filter. Customer dashboard only.
  • Custom fields businesses may add their own fields to inventory items and orders. Values live in a customData bag on the record; definitions live in a separate CrmCommerceCustomField collection keyed by {customerUID, entity}.

Architecture Overview ​

Data model & relationships ​

Request flow (per module same 4-layer stack) ​

Create-order flow (enforces Lead → Order → Inventory) ​


How It Works (Step-by-Step) ​

Inventory ​

  1. List/search GET /api/inventory (listInventory) → crmInventoryService.list → paginated, customerUID-scoped query with name/SKU/category/location search. GET /api/inventory/facets returns distinct category/location values for filter dropdowns.
  2. Create/Update POST /api/inventory, PATCH /api/inventory/:id. The service validates fields and the customData bag (≤50 keys / 64KB). SKU is unique per business (sparse) a clash returns DUPLICATE_SKU.
  3. Delete DELETE /api/inventory/:id. Orders that already reference the item keep their snapshot (line items are copied at order time), so history is never rewritten.
  4. Custom fields GET/POST/PATCH/DELETE /api/inventory/custom-fields[/:fieldId] manage definitions (entity = inventory).

Orders ​

  1. List + stats GET /api/orders (filter by status/lead/search) and GET /api/orders/stats → { total, byStatus, revenueByStatus, builtInStatuses } powering the status cards (built-in and custom statuses).
  2. Create POST /api/orders. Validates the lead exists (LEAD_NOT_FOUND), resolves each line item against inventory, snapshots name/SKU/price, and computes totals server-side.
  3. Update/Delete PATCH/DELETE /api/orders/:id. Status is validated by shape, so custom statuses are accepted. Only a manager may reassign (assignedToUID); a team member's changes stay within their own orders.
  4. Custom fields GET/POST/PATCH/DELETE /api/orders/custom-fields[/:fieldId] (manager-only for writes; entity = order).

Transactions ​

  1. List + stats GET /api/transactions, GET /api/transactions/stats → { total, totalAmount, byStatus:{count,amount}, builtInStatuses }.
  2. Create POST /api/transactions. Validates the order exists and is visible to the caller (ORDER_NOT_FOUND); copies leadId/leadName from the order (the client cannot set them).
  3. Detail + navigation GET /api/transactions/:id returns { transaction, order } so the UI links straight to the related order. GET /api/transactions/by-order/:orderId powers the "Payments" panel on the order detail page.

Monitor (day / week / month) ​

  1. Members GET /api/monitor/members?period= lists each team member with phone-to-phone calls, AI-agent calls, web calls, WhatsApp messages, call minutes, and last call. GET /api/monitor/members/:memberUID opens one member's detail.
  2. Orders / Payments GET /api/monitor/orders, GET /api/monitor/payments reuse the order/transaction aggregations over the selected window.
  3. Sources (verified): calls come from the Contact collection (crmLeadId != null, callType, callDuration/billingDuration, metadata.initiatedByUID); WhatsApp comes from CrmCommunicationLog (type:"sms" + metadata.channel:"whatsapp"). resolveWindow(period): day = midnight-UTC-today→now; week = rolling 7 days; month = rolling 1 month.

Access control ​

  • Feature gate commerce must be on the org plan (superadmin) and assigned to the customer (admin). Enforced by requireFeatureAccess(...FEATURE_MAP.manageCommerce).
  • RBAC resource a single commerce resource with view / create / edit / delete, checked by requirePermission("commerce", action). Owners/admins/superadmins bypass; team members are checked against their effective permissions. The Manager system role grants view/create/edit by default.
  • Visibility scope resolveCommerceScope sets req.commerceScope = { customerUID, orgUID, forcedAssignedToUID, canManageOrg }. A team member's forcedAssignedToUID limits orders (and, transitively, their transactions and Monitor view) to what's assigned to them; Monitor writes nothing. Managing shared order custom-field definitions requires requireCommerceManager.

File Reference ​

Backend (backend/client_backend/src) ​

FileRoleKey exports
middleware/commerceScope.middleware.tsTenant + visibility scope (shared by all 4 modules)resolveCommerceScope, requireCommerceManager, CommerceRequest, CommerceScope
services/commerce.types.tsShared result envelope + HTTP mappingOperationResult, ValidationError, CommerceErrorCode, commerceStatusForCode
models/CrmInventory.tsInventory schema + customData validationICrmInventory, validateCustomData, INVENTORY_CONSTANTS
models/CrmCommerceCustomField.tsShared custom-field definitions (entity: inventory|order)COMMERCE_CUSTOM_FIELD_TYPES
models/CrmOrder.tsOrder schema + line items + status shapeBUILT_IN_ORDER_STATUSES, isValidOrderStatusShape
models/CrmTransaction.tsTransaction schemaBUILT_IN_TRANSACTION_STATUSES, isValidTransactionStatusShape
repositories/CrmInventoryRepository.tsInventory CRUD, findManyByIds, decrementStock, facetscrmInventoryRepository, DuplicateSkuError
repositories/CrmOrderRepository.tsOrder CRUD + statusCounts / revenueByStatuscrmOrderRepository
repositories/CrmTransactionRepository.tsTransaction CRUD + statusSummarycrmTransactionRepository
repositories/CrmCommerceCustomFieldRepository.tsCustom-field definition CRUDcrmCommerceCustomFieldRepository
repositories/CrmMonitorRepository.tsMember call/WhatsApp aggregationscrmMonitorRepository
services/CrmInventoryService.tsInventory business logic + validationcrmInventoryService
services/CrmOrderService.tsOrder logic enforces Lead + Inventory links, computes totalscrmOrderService
services/CrmTransactionService.tsTransaction logic enforces Order linkcrmTransactionService
services/CrmMonitorService.tsAssembles member/order/payment views; resolveWindowcrmMonitorService, MonitorPeriod
controllers/crmInventory.controller.tsInventory + inventory-custom-field handlerstransformInventory
controllers/crmOrder.controller.tsOrder + order-custom-field handlerstransformOrder
controllers/crmTransaction.controller.tsTransaction handlers (embeds related order)
controllers/crmMonitor.controller.tsMonitor handlers
routes/crmInventory.routes.ts · crmOrder.routes.ts · crmTransaction.routes.ts · crmMonitor.routes.tsRoute definitions + middleware chainsmounted at / in app.ts
types/index.tscommerce feature + FEATURE_MAP.manageCommerce
types/permissions.types.tscommerce RBAC resource + meta
repositories/role.repository.tsManager template grants commerce view/create/edit

Frontend (app/src) ​

FileRole
services/inventory.service.ts · order.service.ts · transaction.service.ts · monitor.service.tsTyped API clients
services/commerce.shared.tscommerceErrorMessage, formatCurrency, humanizeStatus, statusBadgeClass, formatDate
components/commerce/CustomFieldInputs.tsxRenders custom-field inputs bound to customData
components/commerce/CommerceCustomFieldsManager.tsxShared dialog to manage field definitions (inventory & order)
pages/customer/InventoryPage.tsxInventory list/CRUD + custom fields
pages/customer/OrdersPage.tsx · OrderDetailPage.tsxOrders list (status cards) · detail (line items, lead link, payments)
pages/customer/TransactionsPage.tsx · TransactionDetailPage.tsxTransactions list · detail (navigates to related order/lead)
pages/customer/MonitorPage.tsx · MonitorMemberDetailPage.tsxMonitor tabs + period filter · per-member detail
layouts/CustomerLayout.tsx"Commerce" sidebar section (feature + resource gated)
routes.tsx/inventory, /orders[/:orderId], /transactions[/:transactionId], /monitor[/members/:memberUID]
lib/features.ts · types/permissions.tsCOMMERCE feature + commerce resource (frontend mirror)

API Reference (summary) ​

All endpoints are JWT-protected, feature-gated by commerce, and RBAC-checked against the commerce resource. List endpoints accept ?page&limit&sortBy&sortOrder&search.

MethodPathPermissionNotes
GET/api/inventoryview?category&location&maxQuantity
GET/api/inventory/facetsviewdistinct categories/locations
POST/PATCH/DELETE/api/inventory[/:id]create/edit/delete
GET/POST/PATCH/DELETE/api/inventory/custom-fields[/:fieldId]view/create/edit/deleteentity = inventory
GET/api/ordersview?status&leadId
GET/api/orders/statsviewstatus counts + revenue
POST/PATCH/DELETE/api/orders[/:id]create/edit/deletecreate validates lead + inventory
GET/POST/PATCH/DELETE/api/orders/custom-fields[/:fieldId]view / manager-only writesentity = order
GET/api/transactionsview?status&orderId&leadId
GET/api/transactions/statsviewtotals by status
GET/api/transactions/:idviewreturns { transaction, order }
GET/api/transactions/by-order/:orderIdviewpayments for an order
POST/PATCH/DELETE/api/transactions[/:id]create/edit/deletecreate validates order
GET/api/monitor/members[/:memberUID]view?period=day|week|month
GET/api/monitor/orders · /api/monitor/paymentsview?period=

Troubleshooting ​

SymptomCauseFix
Commerce sidebar/section not visiblecommerce feature not on the org plan or not assigned to the customerSuperAdmin adds commerce to the org plan; Admin assigns it to the customer
Team member gets 403 on a commerce actionRBAC commerce.<action> not granted to their roleOwner grants the action on the member's role (or use the Manager role)
LEAD_NOT_FOUND creating an orderleadId doesn't exist under this businessPick a lead that belongs to the same customerUID
ORDER_NOT_FOUND creating a transactionOrder missing or not visible to a scoped team memberEnsure the order exists and is assigned to the caller
DUPLICATE_SKU on inventory saveSKU already used by another item in the businessUse a unique SKU or leave it blank
Monitor member shows 0 callsCalls not attributed (metadata.initiatedByUID absent) or outside the windowVerify native CRM calls stamp initiatedByUID; widen the period

Terminology: RBAC = Role-Based Access Control (overview). customerUID = the business account that owns the data (the tenant boundary).

Verification status ​

Backend and frontend both typecheck clean (tsc --noEmit). Runtime/integration testing (live DB, endpoint smoke tests, Monitor aggregation against real call & WhatsApp data) is recommended before release and has not yet been performed.

Last updated: