Opportunities

Active sales deals — the heart of the pipeline, with 7 stages, line items and discount approvals.

Opportunities

An opportunity is an active deal — a potential revenue event with a known account, amount, close date and stage. This is the record sales reps work on every day.

The 7 sales stages

Every opportunity moves through these stages. The system tracks where you are and updates the win probability automatically.

StageDefault win probabilityWhen you're in this stage
⚪ Prospecting10%First contact, qualifying budget and need
🟠 Qualification25%You've identified the decision-maker
🟡 Needs Analysis40%Requirements have been documented
🔵 Proposal60%Quote has been sent
🟣 Negotiation80%Pricing and terms are being negotiated
🟢 Closed Won100%Deal won (final stage)
🔴 Closed Lost0%Deal lost (final stage)

You can move from any open stage directly to Closed Lost (deals can be lost at any point), but you can't skip forward — you have to walk through the funnel one stage at a time. The system blocks invalid jumps to keep your forecast honest.

Closing a deal — the reason is required

Closing an opportunity takes one extra field, and the system will not save the record without it:

Closing asYou must pick
🟢 Closed WonWin Reason — Better Product, Better Price, Existing Relationship, Better Support, Best Fit / Features, or Other
🔴 Closed LostLoss Reason — Price Too High, Lost to Competitor, No Budget, No Decision, Bad Timing, Missing Features, or Other

Loss/Win Details is the free-text box next to it — use it for the sentence a teammate would actually need six months later ("marketing is locked into a 2-year HubSpot contract, revisit at renewal").

Two things to know:

  • This is checked on the server, not just in the form. An import, an API call or a bulk update that closes a deal without the applicable reason is rejected the same way a rep's form is.
  • Closed deals are locked. Once a deal is closed, only the narrative fields (description, next steps) stay editable — so the reason cannot be filled in afterwards. Pick it when you close.

If the deal closes because the customer accepted a quote, HotCRM closes it for you and records the win reason as Quote Accepted — see Quotes.

Those two fields are what the Sales dashboard's win rate and Why We Lose widgets are built from; see Dashboards.

What an opportunity record stores

Three things organise crm_opportunity's fields — the object's own field groups, the opportunity detail screen, and the opportunity form. The detail screen follows the field groups; the form does not, so it is worth knowing which one you are being shown.

The object's field groups

The nine names below are crm_opportunity's field groups (fieldGroups in src/sales/objects/opportunity.object.ts), the object's own filing scheme for its fields. The detail screen's Details tab is built from them; the form declares tabs of its own, listed below. This is the one list that accounts for every field the object has:

Field groupFields
Basic InformationOpportunity Owner, Opportunity Name, Account, Primary Contact
FinancialsAmount, Expected Revenue
Sales ProcessStage, Probability (%), Close Date, Stage Entry Date, Customer Initiation Date, Expected Tender Date, Expected Signing Date, Expected Tender Amount, Expected Signing Amount, Approval Status, Approved Date, Requested Status, Status Change Approval
QualificationWill Bid, Controllability, Priority, Deal Level, Involves Subcontracting, Subcontracting Note, Request Qualification Approval, Qualification Approval
Deal NarrativeCustomer Background, Project Background, Risk Analysis, Payment Terms
ClassificationOpportunity Type, Business Line, Lead Source, Win Reason, Loss Reason, Loss/Win Details
Campaigns (collapsed by default)Campaign
Notes & Next StepsDescription, Next Steps
Forecast & Metrics (collapsed by default)Days in Current Stage, Private, Forecast Category

Four of the original seven rows used to be written differently on this page, and the differences were not cosmetic: Probability (%) is in Sales Process, not Financials; Sales Process also holds Approval Status and Approved Date, and an opportunity has no created date field at all; the campaign field's label is Campaign, not Source campaign; and Forecast & Metrics holds Days in Current Stage, Private and Forecast Category — not line item totals, which is not a field on this object, and not Approval Status, which is one row further up.

The detail screen

The Details tab of the opportunity detail page (src/sales/pages/opportunity_detail.page.ts) declares no sections of its own: each of its sections is one of the field groups above, with that group's name, fields and collapsed state, in this order — Basic Information, Financials, Classification, Campaigns, Sales Process, Qualification, Deal Narrative, Forecast & Metrics, Notes & Next Steps. A field added to a group therefore reaches the tab by itself.

The tab is shorter than that list suggests, and that is deliberate. Amount, Close Date, Probability (%), Expected Revenue, Opportunity Owner and Account are already on the Key Information strip above the tabs, and Opportunity Name is the page title — so the tab repeats none of them, and Financials, left with nothing to show, does not appear. Classification, Campaigns, Qualification, Deal Narrative and Notes & Next Steps stay on screen even when every field in them is empty, as labelled rows waiting to be filled in; the other sections appear once one of their fields has a value.

The form

The opportunity form (src/sales/views/opportunity.view.ts) is tabbed, and its four sections are its tabs:

SectionFields
OverviewOpportunity Name, Account, Primary Contact, Stage, Amount, Probability (%), Close Date, Opportunity Owner
ForecastExpected Revenue, Forecast Category, Opportunity Type, Lead Source, Campaign, Stage Entry Date, Days in Current Stage, Private, Customer Initiation Date, Expected Tender Date, Expected Tender Amount, Expected Signing Date, Expected Signing Amount
Sales StrategyNext Steps
Win / Loss (collapsed by default)Win Reason, Loss Reason, Loss/Win Details

Opportunity types

TypeWhat it means
New BusinessA brand-new logo / customer
Existing - UpgradeExisting customer moving up tiers
Existing - RenewalExisting contract renewing
Existing - ExpansionExisting customer adding new product lines

Splitting deals by type is essential for accurate revenue forecasting — new business and renewals have very different win rates.

What happens automatically

You don't have to remember any of this — the system does it for you:

  • Probability is set from the stage whenever you move the deal (you can override it manually if you have a special situation, but it'll reset next time the stage changes).
  • Expected revenue = amount × probability ÷ 100, recalculated whenever amount or probability changes.
  • Stage entry date is stamped the moment a deal is created and re-stamped every time you move it to a new stage. Days in current stage counts from it, so it is always up to date — that is what surfaces stagnant deals.
  • When you mark a deal Closed Won and the amount is $100K or more, the deal owner gets an email — the owner alone, not their manager.
  • When the amount exceeds $500K, the deal enters the Discount Approval process automatically.

Line items — what's actually being sold

Each opportunity can have line items — one per product being sold. Each line stores:

  • The product (from the catalog)
  • Quantity
  • Unit price (defaults to the product's list price, can be overridden)
  • Discount %
  • Line total

The sum of all line items rolls up into the opportunity Amount. If you'd rather just type the amount manually without configuring line items, you can — both work.

You add one from the Products panel on the Related tab. Alongside the lines a deal already has and the View all link into the full list, the panel carries an Add button. A related list can carry a button in exactly two ways, an actions list or an add picker, and this one declares the second and still not the first: there is no Add Product action anywhere in the app. The picker offers the product catalog filtered to active products, so a retired product cannot be sold onto a new deal, and the row it creates arrives complete and priced — quantity 1, the product's list price as the unit price, no discount — for you to adjust from there. Its rows read the way the catalog names a product, PRD-0001 - Widget. There is still no line-item entry in the sidebar to fall back on: crm_opportunity_line_item has no list view of its own. Permission was never what was missing — a rep's profile grants create on the object — and an import or the API still writes these rows; they are simply no longer the only route. If itemising is not how your team works, type the Amount by hand: that is a supported route, not a workaround.

Discount approval for high-value deals

If a deal's amount goes over $500,000, the opportunity is automatically routed for approval:

  1. The opportunity is locked — nobody can edit it.
  2. Sales Manager reviews first.
  3. If approved, Sales Director signs off.
  4. On final approval, the stage is automatically set to Closed Won.
  5. If rejected at any step, the deal is returned to the rep to re-justify.

The approval status is shown as a banner at the top of the opportunity detail screen. See Revenue › Approvals for the full process.

Sharing — who can see which deal

By default opportunities are private — only the owner. Positions are flat and nothing rolls up, so no one sees a deal by sitting above its owner in an org chart; every other reader is named by one of the rules below.

On top of that:

  • Open opportunities of $100K or more are shared read-only with the sales_director position — and, because nothing rolls up, a second rule shares the same deals with executive. This way leadership sees the big deals without having to follow up.
  • High-value deals in approval are visible to the approvers regardless of ownership.

Standard list views

Opportunities opens on Open Deals, and the switcher along the top of the list carries ten saved views — these, and nothing else:

ViewWhat it shows
Open Deals (the landing view)Every deal that is not Closed Won or Closed Lost, soonest close date first, with rows tinted by stage. Amount and Expected Revenue carry column totals.
My Open DealsThe same open-deal filter, narrowed to the deals you own.
Sales PipelineThe kanban board — one column per stage, open deals only, drag a card to move the deal. Also reachable from Pipeline in the sidebar.
All OpportunitiesThe unfiltered book including closed business, latest close date first. Where win/loss reviews and audits start.
Forecast CalendarEvery deal placed on a month calendar by its close date, coloured by stage.
Deal TimelineOne bar per deal, from the day it was created to its close date, grouped by owner on a month scale.
Deal CardsGallery cards — account, amount, stage, probability, close date, owner — for pipeline and executive reviews.
⚠️ Stale Opportunities · Longest in Stage FirstOpen deals ordered by how long they have sat in their current stage, longest first. Nothing is cut off by age — Days in Current Stage is a column, so you read the wait off the row. The daily Stalled Deal Alert is what acts on it, nudging the owner once a deal passes 14 days in one stage.
Closing This QuarterOpen Commit and Best Case deals whose close date falls inside the current quarter — see below.
Tender This QuarterOpen deals whose Expected Tender Date — the customer's own tender date, not our close date — falls inside the current quarter, soonest tender first — see below.

Closing This Quarter

This is the view a sales manager works at quarter-end, and it is scoped on three things at once:

  • Forecast category — only Commit and Best Case deals.
  • Still open — Closed Won and Closed Lost never appear.
  • Close date inside the current quarter — both ends of the quarter are computed each time the list runs, so it rolls into the new quarter by itself and never needs re-saving.

That third filter is what makes the name true: summing the Amount column gives you this quarter's commit, not the total of every open deal that will one day close. A deal slated for next March is not here — it waits on Open Deals until its quarter comes round. Rows are ordered by close date, soonest first.

An empty list here is an answer, not a broken view. When nothing matches, the view says so in place of the grid — No Deals Closing This Quarter, with a pointer back to Open Deals — because a quarter whose commit has slipped out entirely is exactly the state a manager needs to see. A freshly seeded demo org meets the same screen in the closing days of a quarter, when every sample deal lands in the next one.

Tender This Quarter

Where Closing This Quarter reads our forecast, this view reads the customer's calendar. It lists open deals whose Expected Tender Date falls inside the current quarter, with the tender amount and the expected signing date beside it, and it never looks at the close date — a deal we expect to sign next year can still have a tender this quarter, and this is where it shows up. Both ends of the quarter are computed each time the list runs.

It is empty until somebody records a tender date. No sample deal carries one, so a fresh install opens this view on No Tenders Expected This Quarter. See Opportunity Qualification for the customer-side dates and how they differ from the close date.

The opportunity detail layout

When you open an opportunity, you'll see:

  • Header — the deal's Opportunity Name as the title, its Account as the subtitle, a breadcrumb, and five action buttons: Generate Quote, Clone Opportunity, Log a Call, Log a Meeting and Schedule a Meeting. What is not on it matters just as much: there is no stage badge, no amount, no close date and no owner on the header — those belong to the two components below it. Generate Quote is the CPQ entry point, and the header is where it actually sits.
  • Key Information — the highlights strip immediately under the header, and where this deal's numbers really are: Amount, Close Date, Probability (%), Expected Revenue, Opportunity Owner and Account, side by side. The account therefore appears twice on the screen — once as the header subtitle, once here.
  • Sales Path — the stage strip below the highlights: all seven stages in funnel order, the current one marked and the ones already passed ticked. It is a read-only indicator, not a control — the stages are not clickable, so there is no one-click progression, and clicking a stage does not move the deal to it. You move a deal by editing its Stage field, on the Details tab or in the edit form, and the transition rules in The 7 sales stages above are what decide whether the move is allowed.
  • Three tabs:
    • Details — the tab a deal opens on. Its sections are the object's field groups, described under What an opportunity record stores above; the fields already on the Key Information strip are deliberately not repeated here.
    • Related — three panels in an accordion, and only the first is open when you arrive. Quotes — this deal's quotes; the Generate Quote button that runs the Quote Generation flow is not on this list, it is one of the header buttons above. Products — the deal's line items; the panel lists them, links to the full list, and carries an Add button. That button is an add picker over the product catalog, not an action: there is no Add Product action in this app (see Line items — what's actually being sold above). Open Tasks — this deal's crm_task records through Related Opportunity, filtered to those whose status is not Completed, ten at a time.
    • Activity — a unified feed of comments, logged calls (sys_activity), outbound emails (sys_email), task completions, and field-history audit entries. Every tracked change on the opportunity (stage, amount, close-date, owner) shows up here automatically; there is no separate "history" tab.
  • Competitors & Notes — there is no such panel. The page (src/sales/pages/opportunity_detail.page.ts) declares three regions: a header, the main column carrying the tab strip, and one narrow side column (aside) whose only occupant is the reference rail described below. Nothing on the page renders talking points, and there is no competitors field left to render either: the opportunity's Competitors multi-select offered three placeholder options (Competitor A, Competitor B, Competitor C), no screen in the app ever read the value back, and #1061 retired it. The only competitor signal the app records is the Lost to Competitor closed-lost reason, with the free-text Loss/Win Details beside it. The closest thing to notes on the page itself is the Details tab's collapsible Description section, which carries Description and Next Steps.
  • AI Reference Rail — the rail is real, but there is not one suggestion in it, and no component on this page is AI-driven. It is a record:reference_rail in that side column, and each of its three cards is a snapshot of records already linked to this deal: crm_quote through its Opportunity lookup, this deal's crm_opportunity_line_item rows, and crm_task through Related Opportunity. A card carries the total related count as a badge, at most three records, and a View all link into the full list; cards that come back empty fold into a single + N empty chip. Each card is headed by its object's label in the language you are reading the app in — Quote, Opportunity Line Item and Task in English — because the rail entries declare no heading of their own; the heading is looked up in the translation bundle, the same place the rest of the app gets an object's name. Two caveats. A rail entry cannot filter, and none of these declares one: the rail queries on the relationship alone, so the task card counts and lists this deal's tasks whatever their status. That is exactly why the card is headed Task rather than Open Tasks — the view that really filters to not-Completed, ten at a time, is Open Tasks on the Related tab. And there is no Customer 360, no revenue forecast and no "related signal" in the rail or anywhere else on this page: those skills are reached by asking — see How the AI Copilot helps below.

How the AI Copilot helps

When you open an opportunity, ask:

  • "What's at risk here?" — Revenue Forecasting skill examines stage age, activity gaps and close-date slippage.
  • "Draft a follow-up email" — Email Drafting skill writes the next outbound message.
  • "Summarise this account" — Customer 360 skill gives you a one-screen briefing on the account history.

Tips for sales reps

  • ✅ Move stages promptly — the probability is wrong if your stage is wrong.
  • ✅ Add line items for proper revenue forecasting; the rolled-up amount is more accurate.
  • ✅ Fill in Next Steps — it's the single most useful field for handoffs and standups.
  • ✅ Record why you won or lost at close time — Win Reason, Loss Reason and Loss/Win Details are the only competitive data the app keeps, and they only exist if you fill them in.
  • ❌ Don't push the close date out to keep a deal looking on track. An honest date is what makes Closing This Quarter add up — and a deal that has stopped moving surfaces on Stale Opportunities by its time in stage, whatever its close date says.

Tips for admins

  • The stage list, the default probabilities and the legal transitions live in three places in the source, not on a Setup screen: the shared OPPORTUNITY_STAGE_OPTIONS picklist (src/sales/objects/_picklists.ts), which the stage field renders; the STAGE_PROBABILITY map inside the opportunity_lifecycle hook (src/sales/objects/opportunity.hook.ts), which re-syncs Probability on every save; and the opportunity_stage_progression state-machine validation rule on the opportunity object (src/sales/objects/opportunity.object.ts). Change the three in lockstep. See Customization › Extending Objects.
  • The $500K discount approval threshold can be changed in the approval process configuration.
  • The $100K large-deal notification threshold is not a Setup field either: it is the CEL start condition of the opportunity_won_alert flow — record.amount >= 100000 in src/sales/flows/opportunity-won-alert.flow.ts. Who gets the alert is that flow's notify node, whose recipients list is the single entry {record.owner_id} — the deal owner alone.

On this page