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/:

AreaPathResponsibility
Modelsdb/definitions, db/modelsMongoose schemas for boards, pipelines, stages, deals, labels, checklists
GraphQLgraphql/schemas, graphql/resolversQueries, mutations, custom resolvers, loaders
tRPCtrpc/deal.ts, trpc/document.tsCross-service procedures
Documentsdocuments/dealContent.ts, documents/replaceBlocks.tsDeal document-template content replacement
Metameta/automations, meta/segments, meta/references, meta/activity-log, meta/paymentsAutomation 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 (default deal).
  • Pipeline (Pipelines): name, boardId, visibility, memberIds, watchedUserIds, departmentIds, branchIds, paymentTypes (may carry scoreCampaignId), 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 SalesPipelineLabel records and deal-attached SalesChecklist/SalesChecklistItem records.

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.

OperationKindPurpose
deals(stageId, filter…) / dealDetail(_id)queryCursor-paginated deal list and detail
dealsTotalCount / dealsTotalAmounts / archivedDealsqueryCounts, per-currency totals, archive browsing
dealsAdd / dealsEdit / dealsRemove / dealsCopymutationDeal CRUD
dealsChange(itemId, destinationStageId, aboveItemId)mutationMove a card between stages or reorder
dealsWatch(_id, isAdd) / dealsArchive(stageId)mutationWatch a deal; archive a whole stage
dealsCreateProductsData / dealsEditProductData / dealsDeleteProductDatamutationProduct rows on a deal
checkDiscount(_id, products, couponCode, voucherId)queryCoupon/voucher price check for deal products; provided by the Enterprise Edition
salesBoards / salesBoardDetail / salesBoardsAdd / salesBoardsEdit / salesBoardsRemovequery/mutationBoards
salesPipelines / salesPipelineDetail / salesPipelinesAdd / salesPipelinesEdit / salesPipelinesUpdateOrder / salesPipelinesWatch / salesPipelinesRemove / salesPipelinesArchive / salesPipelinesCopiedquery/mutationPipelines, including propertyIds
salesStages / salesStageDetail / salesStagesEdit / salesStagesUpdateOrder / salesStagesSortItems / salesArchivedStagesquery/mutationStages and card ordering
salesPipelineLabels / salesPipelineLabelsAdd / salesPipelineLabelsLabelquery/mutationLabels and applying them to deals
salesChecklists / salesChecklistsAdd / salesChecklistItemsAdd / salesChecklistItemsOrderquery/mutationChecklists 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". setPropertyTargets let workflows write customer/company deal relations.
  • Segments: the sales:sales.deals content type with filterable fields, member listing/counting, and membership writes; customer.deals and company.deals relations join through Core relation records. Stage-derived fields (pipelineId, boardId, stageProbability) rewrite their conditions to stageIds before evaluating.
  • Documents: sales:deal is a document content type. documents.editorAttributes returns merge fields (fixed attributes, schema fields, and Core custom fields as customFieldsData.<fieldId>); deal.replaceContent renders one processed document per selected deal in the caller's order.
  • References (meta/references.ts): deal display names, links, labels, product amount helpers, and excludeLoyaltyAmount (deal total minus payments through types carrying scoreCampaignId; 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 Core sales:deal field; validation calls Core fields.find and fails closed.
  • Deal list is slow or unsorted: the unscoped list relies on the parentId/order/_id/status compound 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.
Was this helpful?