Quotes
Generate pricing proposals from opportunities, with line items, discounts and automatic expiration.
Quotes
A quote is a formal pricing proposal sent to a customer. Quotes always belong to an opportunity and capture the pricing at the moment they're presented. Once a quote is accepted, it becomes the basis of a contract.
The 6 quote statuses
| Status | What it means |
|---|---|
| โช Draft | Being prepared internally (default for new quotes) |
| ๐ In Review | Pending internal review (e.g., discount approval) |
| ๐ต Presented | Sent to the customer |
| ๐ข Accepted | Customer agreed โ automatically triggers contract creation |
| ๐ด Rejected | Customer declined |
| โช Expired | Validity period passed without a decision |
A typical journey: Draft โ Presented โ Accepted โ (Contract).
What a quote record stores
The detail screen has 5 sections:
| Section | Fields |
|---|---|
| Quote Information | Quote number, name, status, account, contact, opportunity, owner |
| Pricing | Subtotal, discount %, discount amount, tax, shipping & handling, total |
| Terms & Validity | Quote date, expiration date, payment terms, shipping terms |
| Addresses | Billing address, shipping address |
| System | Description (customer-facing), internal notes (your eyes only) |
Payment terms
Net 15 ยท Net 30 (default) ยท Net 60 ยท Net 90 ยท Due on Receipt.
How prices are calculated
The system does the math for you:
Subtotal = sum of all line items
Discount = Subtotal ร Discount % รท 100
Total Price = Subtotal โ Discount + Tax + Shipping & HandlingAnything you change in the line items, discount, tax or shipping fields, the totals update automatically.
Line items
A quote can have any number of line items โ one per product being sold. Each line stores:
- Product (from the catalog)
- Quantity
- Unit price (defaults to the product's list price)
- Per-line discount %
- Line total
The sum of line items rolls up into the quote subtotal.
On the quote's Related tab, the line items panel shows each line's product, quantity, sales price, subtotal and total. The subtotal is the line after its own discount and before tax; the total adds the line's tax rate on top.
Built-in rules
- Expiration date must be after the quote date โ no expired-on-creation quotes.
- Discount cannot exceed 60%. This is a hard ceiling, not a warning: a quote whose Discount % is above 60 is refused on save, with "Discount cannot exceed 60%". There is no approval that unblocks it โ see Discount ceiling below for what to do instead. (A discount above 100% was never possible; the field itself only accepts 0โ100.)
- No single line may exceed 60% either. The same ceiling, the same 60, applied to each line item's own Discount %: a line above it is refused on save with "Line discount cannot exceed 60%". Both numbers are needed, because a quote's total runs them one after the other โ each line is discounted first, then the quote's percentage comes off the sum โ so a quote sitting innocently at 0% with every line at 90% off used to price 90% below list and clear the quote rule outright.
- A quote cannot be Presented or Accepted without a contact. A Draft or In Review quote needs no recipient โ you often price a deal before knowing who signs it. But the write that moves it to Presented (or straight to Accepted) is refused unless Contact is filled in, and the message points at that field. Quotes that are never sent are unaffected: Rejected straight out of review, and Expired (which the nightly expiry sweep sets where it runs), both still go through without a contact.
- A quote cannot be Accepted while an approval holds its opportunity. Accepting a quote closes its deal as Closed Won, so the save that sets Accepted is refused while that deal is waiting on its large-deal approval (Approval Status is Pending) โ or, where your administrator has switched them on, while its status-change or qualification approval is Pending or Rejected (Status Change Approval, Qualification Approval). The message names the approval that holds the deal. Nothing else moves: the quote stays Presented, the deal keeps its stage and no contract is drafted. Record the acceptance once that approval has been granted.
What happens automatically
- Daily expiration sweep โ where your deployment runs scheduled work, every night at 1 AM the system marks any quote whose expiration has passed as Expired (unless it's already accepted or rejected), which keeps your active list clean. Scheduled work is off until the deployment switches it on (see Administration โบ Automation). Without it nothing expires a quote: one past its expiration date keeps its status until someone changes it.
- On acceptance โ marking a quote Accepted is what drafts the contract. The system inserts a contract in Draft status, typed Subscription, on a 12-month term starting today, carrying the quote's account, primary contact, related opportunity, owner and total price. The draft is the system's own write, so it is made whoever records the acceptance โ a sales rep included, although a rep cannot create a contract by hand. It then advances the linked opportunity to Closed Won, dated today, and โ only when the rep recorded no win reason โ stamps Quote Accepted as one. An opportunity already closed, won or lost, is left where it is.
Two things readers often expect at this point happen later, when someone activates that contract, not when the quote is accepted:
- The account is promoted at activation, not at acceptance. Moving an account from Prospect to Customer is a write that runs on contract activation, and what acceptance produces is a Draft. Until a Sales Manager or an admin activates that contract, the account is still a Prospect.
- The renewal reminder is not scheduled here, and it is not a 60-day one. Renewal reminders come from the daily renewal sweep, a scheduled flow that runs only where scheduled work is switched on. It reads Activated contracts only and uses each contract's own Renewal Notice (Days) โ 30 by default, and set per contract. A contract still sitting in Draft is invisible to that sweep, so it has no reminder at all.
Both are described in full on the Contracts page.
The links are copied across, not invented: the drafted contract requires a Primary Contact and takes it from the quote, and the close-won step acts on the quote's related opportunity. What the quote does not carry, acceptance cannot pass on.
The contact half of that is now enforced rather than advised โ you cannot present a quote without one, so by the time it can be accepted the recipient is already on it. The related opportunity is still genuinely optional: a quote with no opportunity is accepted normally and drafts its contract, there is simply no deal for the system to close-won. If you want the acceptance to win the deal, put the opportunity on the quote first.
Generating a quote
You don't manually create quotes โ instead, open an opportunity that is still open and click Generate Quote. The button is not offered on a Closed Won or Closed Lost deal. A short form pops up:
- Quote name
- Valid for (days) โ default 30
- Discount % โ default 0; the label shows the 60% ceiling
If your organisation requires qualification approval (็ซ้กน) for new deals, a deal is quoted only once it is approved. While the deal's Qualification Approval is Pending or Rejected, Generate Quote opens no form: you are told that qualification approval comes first, and nothing is created. Tick Request Qualification Approval on the deal; once it is approved, the button works as described here. Out of the box this gate is off โ every deal reads Not Required โ and none of this applies (see Opportunity Qualification).
When you submit it, the system:
- Creates the quote in Draft status with the opportunity's amount as the subtotal. The quote takes the opportunity's account and primary contact, makes you its owner, is dated today, expires the number of days you entered from today, and has payment terms of Net 30. The opportunity's products are not copied: the quote starts with no line items.
- Applies the discount and calculates the discount amount and the total, to the cent.
- Moves the opportunity to Proposal, but only from Prospecting, Qualification or Needs Analysis. A deal already at Proposal or Negotiation keeps its stage. So does a deal waiting on an approval decision, whose Approval Status is Pending (see Revenue โบ Approvals). In both cases you still get the quote.
- Notifies you that the quote was created: a message in your in-app inbox that links to the new quote, and an email. The email reaches a mailbox only if your deployment has outgoing mail set up. Out of the box it has none, and the email is not sent (see Email & Calendar).
Generating a quote stamps no activity date. The opportunity has no last-activity field, and the account's Last Activity Date stays as it was.
The rep then reviews the draft, attaches the quote document (a PDF) to the quote, and changes the status to Presented when it goes to the customer.
Sending a quote to the customer
HotCRM does not send the quote for you. The quote record has no send button, and the app neither produces a quote PDF nor ships an email template for one. You send the document to the customer yourself, then record that it went out: on the quote, click Edit, set Status to Presented and click Update. That save is refused unless Contact is filled in (see Built-in rules), and it emails no one.
To write the covering email inside HotCRM, use Send Email on the customer's contact record. You type the subject and body yourself, nothing is attached, and the message leaves only if your deployment has outgoing mail set up (see Email & Calendar). The AI assistant can draft that text for you โ "Draft a cover note for this quote" โ and hands it back for you to paste; it does not send anything.
Recording the customer's answer
There are no accept or reject buttons either. The customer's decision is recorded the same way: Edit the quote, set Status, click Update.
- Accepted โ the save that makes a quote Accepted is what drafts the contract and close-wins the deal, as described in What happens automatically. While an approval holds the deal, that save is refused instead (see Built-in rules). From then on the quote is frozen: a change to any field except Internal Notes is refused, Status included.
- Rejected โ nothing else happens. No contract is drafted and the opportunity keeps its stage, so you can quote again: set the rejected quote back to Draft, revise it and present it again, or click Generate Quote on the opportunity for a fresh one.
The row of statuses across the top of the quote shows where it stands. It is a display, not a set of buttons.
Discount ceiling
A quote's discount may never exceed 60% โ and neither may any one line's. The rule is an invariant, not a checkpoint: it is evaluated on every save, so there is no state in which a deeper discount is allowed to sit. A save that would put the quote's Discount % above 60 comes back refused with "Discount cannot exceed 60%", a save that would put a line's Discount % above 60 comes back refused with "Line discount cannot exceed 60%", and in both cases nothing is written. The two messages differ so that a refused save tells you which of the two numbers to bring down.
Both are needed because a quote's total applies them in sequence โ each line is discounted, the lines are summed, and the quote's percentage comes off that sum. Ceiling one and the other is the way around it: a quote at 0% with every line at 90% off is 90% below list, and the quote-level rule has nothing to object to. That is the case this pair of rules closes.
โ ๏ธ 60% + 60% is not 60%. A quote at the ceiling whose lines are also at the ceiling is roughly 84% below list, because the two percentages compound. Each rule caps one multiplier; neither caps the combination. What the ceiling should mean for the effective discount is an open product question, so if the combined number matters for your business, watch Total Price rather than relying on the two percentages to add up to a bound.
What to do when a deal genuinely needs to go deeper: restructure the number rather than the percentage โ a lower Unit Price on the line items, or a shorter term, reaches the same total price without a discount the system refuses. Record why in Internal Notes either way.
โ ๏ธ There is no approval that unblocks the ceiling. Approval in HotCRM routes on the opportunity's Amount โ a manager at $100K or more, a director above $500K (see Revenue โบ Approvals) โ and nothing routes on a discount. That is a deliberate gap, not an oversight: discount-triggered approval routing is a planned addition, and until it ships the ceiling is absolute. A quote that needs a deeper discount than the ceiling allows is a conversation to have off-system, not a request to submit.
The line sits at 60% because a rule that blocks with no way around it has to refuse only what is genuinely wrong. Aggressive commercial pricing โ the 20โ30% range HotCRM's own demo data uses โ is untouched; the ceiling exists for the 90%-off quote that no one reviewed.
Standard list views
Quotes opens on All Quotes, and the switcher carries three saved views โ these, and nothing else:
| View | What it shows |
|---|---|
| All Quotes (the landing view) | Every quote, newest Quote Date first, with the quote number pinned to the left as the link into the record. Subtotal, Tax and Total Price carry column totals, so whatever is on screen adds itself up; account, opportunity, status, expiration date, discount and owner sit on the row. 25 rows a page. |
| Quote Pipeline | The same quotes as a board, one column per status โ Draft, In Review, Presented, Accepted, Rejected, Expired โ each column totalling Total Price. This is where "what is stuck in review?" and "how much have we presented?" get answered, and the Accepted column is your won-quote list. |
| Quote Calendar | Each quote drawn on a month calendar as a band running from its Quote Date to its Expiration Date, labelled with the quote number and coloured by status. What is about to lapse is the band ending nearest today. |
None of the three is filtered โ no owner cut, no date window, no "active only". Two things make that workable:
- Status is the filter you already have. The board separates Draft / In Review / Presented from the closed statuses, and where the nightly expiry sweep runs (see What happens automatically) it moves anything past its expiration date into Expired on its own, so the open columns stay honest without anyone curating a list. Where it does not run, a lapsed quote stays in its open column until someone changes its status, and the sort below is how to find it.
- Expiration Date is sortable on All Quotes. Sorting on it ascending is the "expiring soon" answer, and it works on the whole register rather than a fixed week.
A brand-new org has no quotes at all โ they are generated from opportunities, never created from scratch โ so the list is legitimately empty until the first Generate Quote. These views declare no empty-state message, so what you get in that case is an empty grid, not an explanation.
Tips for sales reps
- โ Always include a discount reason in Internal Notes if you're discounting heavily.
- โ Use the Generate Quote button from an opportunity rather than creating quotes from scratch โ it fills in the account, primary contact, dates, payment terms and opening price for you. The line items are yours to add.
- โ Check the validity (expiration date) matches your business cycle. Quarterly buyers need quotes valid till quarter-end.
- โ Don't change pricing on a Presented quote โ it confuses the customer. Create a new revision instead.
Tips for admins
- The daily expiration sweep runs at 1 AM, and only on a deployment that has switched scheduled work on: a default install boots with the
quote_expirationflow unbound, and the switch is described in Administration โบ Automation. The time lives in source, not in Setup: it is theschedule: '0 1 * * *'on the start node of thequote_expirationflow (src/revenue/flows/quote-expiration.flow.ts). Changing it is a code change plus a redeploy โ there is no setting to adjust on the quote object. - There is no quote PDF template to configure. HotCRM does not produce the quote document (the PDF a rep sends is made outside the app), and the one quote email template it ships is Quote Created, the notification a rep receives after Generate Quote.
- The Contact requirement is checked when a quote moves into Presented or Accepted, not on every save. A quote that was already sitting in Presented without a contact before this rule shipped keeps working normally โ you can read and edit it, and filling the Contact in is an ordinary edit. It is the only case that can still reach Accepted without a recipient, and there it behaves as it always did: the contract cannot be drafted, and the reason lands in the server log. HotCRM's own demo data contains no such quote.
- The Discount ceiling is checked on every save, not only on a status change โ the opposite boundary to the Contact requirement above, and deliberately so. "The discount may never exceed 60%" is an invariant; "a quote must have a recipient by the time it is presented" is a condition on a transition. The consequence worth knowing: a quote stored above the ceiling before this rule shipped would be frozen to every edit except lowering its discount, which is always allowed. HotCRM's own demo data contains no such quote โ the deepest discount it seeds is 20%.
- The line-item ceiling behaves the same way and reaches the same legacy case: a line stored above 60 before the rule shipped is refused on its next edit until its discount comes down. One boundary is worth knowing: a validation is evaluated against the record being saved, so an over-ceiling line freezes that line, not its parent quote โ the quote can still change status. Nothing HotCRM ships is in that state (the deepest line discount it seeds is 20%), and after this rule a line above the ceiling cannot be written at all.
- The ceiling value lives in source, not in Setup: it is
QUOTE_DISCOUNT_CEILINGinsrc/sales/objects/_thresholds.ts, interpolated into the condition and the message of both rules โ the one on the quote object and the one on the quote line item. There is only ever one number to change, and changing it is a code change plus a redeploy. - To route quotes for approval on their discount (for example, every quote over 20% off to the manager), you would author a new record-change flow on the quote object that flips it to In Review and notifies them. Nothing built in does this today โ the shipped approval flow keys on the opportunity's Amount and never reads a discount. That is a source change under
src/revenue/flows/, where the quote object's flows live โ not something assembled in Setup. See Administration โบ Automation for how the built-in flows are put together.