Deals & Pipelines
Deal boards, pipelines, stages, deals, labels, and checklists: the core of the Sales plugin.
Module layout
Backend code lives under backend/plugins/sales_api/src/modules/sales/:
| Area | Path | Responsibility |
|---|---|---|
| Models | db/definitions, db/models | Mongoose schemas for boards, pipelines, stages, deals, labels, checklists |
| GraphQL | graphql/schemas, graphql/resolvers | Queries, mutations, custom resolvers, loaders |
| tRPC | trpc/deal.ts, trpc/document.ts | Cross-service procedures |
| Documents | documents/dealContent.ts, documents/replaceBlocks.ts | Deal document-template content replacement |
| Meta | meta/automations, meta/segments, meta/references, meta/activity-log, meta/payments | Automation constants/handlers, segment definitions, record references |
The frontend counterpart is frontend/plugins/sales_ui/src/modules/deals/ (boards, pipelines, deal detail, product/payment UI).
Data model
The hierarchy is Board → Pipeline → Stage → Deal, each collection scoped per tenant:
- Board (
Boards):name,order,type(defaultdeal). - Pipeline (
Pipelines):name,boardId,visibility,memberIds,watchedUserIds,departmentIds,branchIds,paymentTypes(may carryscoreCampaignId),propertyIds, plus card-numbering and date-check settings. - Stage (
Stages):name,pipelineId,probability,status,visibility,memberIds/canMoveMemberIds/canEditMemberIds,formId,order. - Deal (
Deals):name,stageId,order,assignedUserIds,watchedUserIds,labelIds,tagIds,branchIds/departmentIds,priority,status(active/archived,SALES_STATUSES),startDate/closeDate/stageChangedDate,productsData(per-product quantity, prices,tickUsed,discountInfos),paymentsData,totalAmount/unUsedTotalAmount/bothTotalAmount,mobileAmount/mobileAmounts,propertiesData,customFieldsData,number,relations(Gantt links),timeTrack. - Labels and checklists: pipeline-scoped
SalesPipelineLabelrecords and deal-attachedSalesChecklist/SalesChecklistItemrecords.
A deal stores no customerId/companyId. Customer and company links are Core relation records read by the platform, not fields on the deal.
Pipeline property selection
Pipelines.propertyIds stores ids of Core sales:deal fields. salesPipelinesAdd/salesPipelinesEdit validate every id through Core's fields.find tRPC procedure before writing (modules/sales/utils/pipelineProperties.ts). Deal detail renders only the selected properties; a separate isPropertySelectionConfigured flag preserves legacy show-all behavior for pipelines saved before the feature existed.
Queries and mutations
Schema: modules/sales/graphql/schemas/deal.ts.
| Operation | Kind | Purpose |
|---|---|---|
deals(stageId, filter…) / dealDetail(_id) | query | Cursor-paginated deal list and detail |
dealsTotalCount / dealsTotalAmounts / archivedDeals | query | Counts, per-currency totals, archive browsing |
dealsAdd / dealsEdit / dealsRemove / dealsCopy | mutation | Deal CRUD |
dealsChange(itemId, destinationStageId, aboveItemId) | mutation | Move a card between stages or reorder |
dealsWatch(_id, isAdd) / dealsArchive(stageId) | mutation | Watch a deal; archive a whole stage |
dealsCreateProductsData / dealsEditProductData / dealsDeleteProductData | mutation | Product rows on a deal |
checkDiscount(_id, products, couponCode, voucherId) | query | Coupon/voucher price check for deal products; provided by the Enterprise Edition |
salesBoards / salesBoardDetail / salesBoardsAdd / salesBoardsEdit / salesBoardsRemove | query/mutation | Boards |
salesPipelines / salesPipelineDetail / salesPipelinesAdd / salesPipelinesEdit / salesPipelinesUpdateOrder / salesPipelinesWatch / salesPipelinesRemove / salesPipelinesArchive / salesPipelinesCopied | query/mutation | Pipelines, including propertyIds |
salesStages / salesStageDetail / salesStagesEdit / salesStagesUpdateOrder / salesStagesSortItems / salesArchivedStages | query/mutation | Stages and card ordering |
salesPipelineLabels / salesPipelineLabelsAdd / salesPipelineLabelsLabel | query/mutation | Labels and applying them to deals |
salesChecklists / salesChecklistsAdd / salesChecklistItemsAdd / salesChecklistItemsOrder | query/mutation | Checklists and items |
Most list/read operations also have cp* variants (cpDeals, cpDealDetail, cpSalesStages, …) marked forClientPortal for storefront use; see Ecommerce and Client Portal.
Subscriptions: salesDealChanged(_id) and salesDealListChanged(pipelineId, userId, filter) push live board updates.
Example: add a deal (signature from graphql/schemas/deal.ts):
mutation {
dealsAdd(
name: "Q3 hardware renewal"
customerIds: ["cust_1"]
stageId: "stage_open"
assignedUserIds: ["user_1"]
closeDate: "2026-12-31"
productsData: [{ productId: "prod_7", quantity: 4, unitPrice: 250, tickUsed: true }]
) {
_id
name
}
}
tRPC
src/trpc/init-trpc.ts merges the deal, pos (with orders), documents, and fields routers. Deal-side procedures include deal.findOne, deal.find, deal.count, deal.aggregate, deal.tag, deal.createItem/editItem/removeItem, deal.generateAmounts, deal.generateProducts, deal.replaceContent, deal.contentIds, deal.subscriptionWrapper, deal.create/updateOne (system-user), stage.findOne/stage.find, pipeline.findOne, and documents.editorAttributes / fields.getFieldList.
Always pass a limit to deal.find. It forwards skip/limit/sort to MongoDB, and an unbounded deal.find over a large tenant can exhaust the service.
Automations, segments, documents
- Automations (
meta/automations.ts+modules/sales/meta/automations/): triggers are "Sales pipeline" (segment enrollment), "Deal reaches stage probability", and "Deal stage changed"; actions are "Create deal" and "Create sales checklist".setPropertyTargetslet workflows write customer/company deal relations. - Segments: the
sales:sales.dealscontent type with filterable fields, member listing/counting, and membership writes;customer.dealsandcompany.dealsrelations join through Core relation records. Stage-derived fields (pipelineId,boardId,stageProbability) rewrite their conditions tostageIds before evaluating. - Documents:
sales:dealis a document content type.documents.editorAttributesreturns merge fields (fixed attributes, schema fields, and Core custom fields ascustomFieldsData.<fieldId>);deal.replaceContentrenders one processed document per selected deal in the caller's order. - References (
meta/references.ts): deal display names, links, labels, product amount helpers, andexcludeLoyaltyAmount(deal total minus payments through types carryingscoreCampaignId; used by the Enterprise Edition).
Permissions
meta/permissions.ts registers modules deal (showDeals, dealsAdd, dealsEdit, dealsProductsEdit, dealsRemove, dealsWatch, dealsArchive), board (boardsAdd/Edit/Remove, updateTimeTracking), pipeline (pipelinesAdd/Edit/Watch), stage (stagesEdit, stagesRemove, itemsSort), checklist, pipelineLabel, and pipelineTemplate, with own/group/all scopes on deals. Default groups: sales:admin, sales:user, sales:viewer.
Troubleshooting
- Pipeline edit rejects
propertyIds: every id must be a Coresales:dealfield; validation calls Corefields.findand fails closed. - Deal list is slow or unsorted: the unscoped list relies on the
parentId/order/_id/statuscompound index; filters outside it can force a collection scan. - A segment never updates for deal writes: the content type must be declared as
sales:sales.deals; the event dispatcher matches on that string.