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(neverorgUIDalone).orgUIDis 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/leadNameare 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
customDatabag on the record; definitions live in a separateCrmCommerceCustomFieldcollection 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
- List/search
GET /api/inventory(listInventory) →crmInventoryService.list→ paginated,customerUID-scoped query with name/SKU/category/location search.GET /api/inventory/facetsreturns distinct category/location values for filter dropdowns. - Create/Update
POST /api/inventory,PATCH /api/inventory/:id. The service validates fields and thecustomDatabag (≤50 keys / 64KB). SKU is unique per business (sparse) a clash returnsDUPLICATE_SKU. - 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. - Custom fields
GET/POST/PATCH/DELETE /api/inventory/custom-fields[/:fieldId]manage definitions (entity =inventory).
Orders
- List + stats
GET /api/orders(filter by status/lead/search) andGET /api/orders/stats→{ total, byStatus, revenueByStatus, builtInStatuses }powering the status cards (built-in and custom statuses). - 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. - 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. - Custom fields
GET/POST/PATCH/DELETE /api/orders/custom-fields[/:fieldId](manager-only for writes; entity =order).
Transactions
- List + stats
GET /api/transactions,GET /api/transactions/stats→{ total, totalAmount, byStatus:{count,amount}, builtInStatuses }. - Create
POST /api/transactions. Validates the order exists and is visible to the caller (ORDER_NOT_FOUND); copiesleadId/leadNamefrom the order (the client cannot set them). - Detail + navigation
GET /api/transactions/:idreturns{ transaction, order }so the UI links straight to the related order.GET /api/transactions/by-order/:orderIdpowers the "Payments" panel on the order detail page.
Monitor (day / week / month)
- 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/:memberUIDopens one member's detail. - Orders / Payments
GET /api/monitor/orders,GET /api/monitor/paymentsreuse the order/transaction aggregations over the selected window. - Sources (verified): calls come from the
Contactcollection (crmLeadId != null,callType,callDuration/billingDuration,metadata.initiatedByUID); WhatsApp comes fromCrmCommunicationLog(type:"sms"+metadata.channel:"whatsapp").resolveWindow(period): day = midnight-UTC-today→now; week = rolling 7 days; month = rolling 1 month.
Access control
- Feature gate
commercemust be on the org plan (superadmin) and assigned to the customer (admin). Enforced byrequireFeatureAccess(...FEATURE_MAP.manageCommerce). - RBAC resource a single
commerceresource withview / create / edit / delete, checked byrequirePermission("commerce", action). Owners/admins/superadmins bypass; team members are checked against their effective permissions. The Manager system role grantsview/create/editby default. - Visibility scope
resolveCommerceScopesetsreq.commerceScope = { customerUID, orgUID, forcedAssignedToUID, canManageOrg }. A team member'sforcedAssignedToUIDlimits orders (and, transitively, their transactions and Monitor view) to what's assigned to them; Monitor writes nothing. Managing shared order custom-field definitions requiresrequireCommerceManager.
File Reference
Backend (backend/client_backend/src)
| File | Role | Key exports |
|---|---|---|
middleware/commerceScope.middleware.ts | Tenant + visibility scope (shared by all 4 modules) | resolveCommerceScope, requireCommerceManager, CommerceRequest, CommerceScope |
services/commerce.types.ts | Shared result envelope + HTTP mapping | OperationResult, ValidationError, CommerceErrorCode, commerceStatusForCode |
models/CrmInventory.ts | Inventory schema + customData validation | ICrmInventory, validateCustomData, INVENTORY_CONSTANTS |
models/CrmCommerceCustomField.ts | Shared custom-field definitions (entity: inventory|order) | COMMERCE_CUSTOM_FIELD_TYPES |
models/CrmOrder.ts | Order schema + line items + status shape | BUILT_IN_ORDER_STATUSES, isValidOrderStatusShape |
models/CrmTransaction.ts | Transaction schema | BUILT_IN_TRANSACTION_STATUSES, isValidTransactionStatusShape |
repositories/CrmInventoryRepository.ts | Inventory CRUD, findManyByIds, decrementStock, facets | crmInventoryRepository, DuplicateSkuError |
repositories/CrmOrderRepository.ts | Order CRUD + statusCounts / revenueByStatus | crmOrderRepository |
repositories/CrmTransactionRepository.ts | Transaction CRUD + statusSummary | crmTransactionRepository |
repositories/CrmCommerceCustomFieldRepository.ts | Custom-field definition CRUD | crmCommerceCustomFieldRepository |
repositories/CrmMonitorRepository.ts | Member call/WhatsApp aggregations | crmMonitorRepository |
services/CrmInventoryService.ts | Inventory business logic + validation | crmInventoryService |
services/CrmOrderService.ts | Order logic enforces Lead + Inventory links, computes totals | crmOrderService |
services/CrmTransactionService.ts | Transaction logic enforces Order link | crmTransactionService |
services/CrmMonitorService.ts | Assembles member/order/payment views; resolveWindow | crmMonitorService, MonitorPeriod |
controllers/crmInventory.controller.ts | Inventory + inventory-custom-field handlers | transformInventory |
controllers/crmOrder.controller.ts | Order + order-custom-field handlers | transformOrder |
controllers/crmTransaction.controller.ts | Transaction handlers (embeds related order) | |
controllers/crmMonitor.controller.ts | Monitor handlers | |
routes/crmInventory.routes.ts · crmOrder.routes.ts · crmTransaction.routes.ts · crmMonitor.routes.ts | Route definitions + middleware chains | mounted at / in app.ts |
types/index.ts | commerce feature + FEATURE_MAP.manageCommerce | |
types/permissions.types.ts | commerce RBAC resource + meta | |
repositories/role.repository.ts | Manager template grants commerce view/create/edit |
Frontend (app/src)
| File | Role |
|---|---|
services/inventory.service.ts · order.service.ts · transaction.service.ts · monitor.service.ts | Typed API clients |
services/commerce.shared.ts | commerceErrorMessage, formatCurrency, humanizeStatus, statusBadgeClass, formatDate |
components/commerce/CustomFieldInputs.tsx | Renders custom-field inputs bound to customData |
components/commerce/CommerceCustomFieldsManager.tsx | Shared dialog to manage field definitions (inventory & order) |
pages/customer/InventoryPage.tsx | Inventory list/CRUD + custom fields |
pages/customer/OrdersPage.tsx · OrderDetailPage.tsx | Orders list (status cards) · detail (line items, lead link, payments) |
pages/customer/TransactionsPage.tsx · TransactionDetailPage.tsx | Transactions list · detail (navigates to related order/lead) |
pages/customer/MonitorPage.tsx · MonitorMemberDetailPage.tsx | Monitor 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.ts | COMMERCE 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.
| Method | Path | Permission | Notes |
|---|---|---|---|
| GET | /api/inventory | view | ?category&location&maxQuantity |
| GET | /api/inventory/facets | view | distinct categories/locations |
| POST/PATCH/DELETE | /api/inventory[/:id] | create/edit/delete | |
| GET/POST/PATCH/DELETE | /api/inventory/custom-fields[/:fieldId] | view/create/edit/delete | entity = inventory |
| GET | /api/orders | view | ?status&leadId |
| GET | /api/orders/stats | view | status counts + revenue |
| POST/PATCH/DELETE | /api/orders[/:id] | create/edit/delete | create validates lead + inventory |
| GET/POST/PATCH/DELETE | /api/orders/custom-fields[/:fieldId] | view / manager-only writes | entity = order |
| GET | /api/transactions | view | ?status&orderId&leadId |
| GET | /api/transactions/stats | view | totals by status |
| GET | /api/transactions/:id | view | returns { transaction, order } |
| GET | /api/transactions/by-order/:orderId | view | payments for an order |
| POST/PATCH/DELETE | /api/transactions[/:id] | create/edit/delete | create validates order |
| GET | /api/monitor/members[/:memberUID] | view | ?period=day|week|month |
| GET | /api/monitor/orders · /api/monitor/payments | view | ?period= |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Commerce sidebar/section not visible | commerce feature not on the org plan or not assigned to the customer | SuperAdmin adds commerce to the org plan; Admin assigns it to the customer |
| Team member gets 403 on a commerce action | RBAC commerce.<action> not granted to their role | Owner grants the action on the member's role (or use the Manager role) |
LEAD_NOT_FOUND creating an order | leadId doesn't exist under this business | Pick a lead that belongs to the same customerUID |
ORDER_NOT_FOUND creating a transaction | Order missing or not visible to a scoped team member | Ensure the order exists and is assigned to the caller |
DUPLICATE_SKU on inventory save | SKU already used by another item in the business | Use a unique SKU or leave it blank |
| Monitor member shows 0 calls | Calls not attributed (metadata.initiatedByUID absent) or outside the window | Verify 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.