Blueprint 004 · Process control

How do we stop quotes and discounts going out before someone signs off?

Not a reminder, not a checkbox, not a pipeline stage named "Approval" that anyone can drag past. A gate that holds the record until a decision is made — above a threshold only, routed to whoever currently manages the person who raised it.

Revised 2026-09-02 Verified against a live Coevera space Markdown twin ↓

The short answer

An approval process on the Quote entity, triggered on create-or-update and narrowed by an ordinary filter node — which is where the threshold lives. A quote total with the More operator and a value of 10000 is all "only above 10K" means. The same mechanism works on a discount percentage or a margin field.

The enforcement is recordLock, not a notification. Route to SalesUnitManager rather than named people so it survives staff changes. And check expirationResult before you ship — it can be set to Approve, meaning an approval nobody answers is granted automatically.

01The business problem

Salespeople discount to close. Most of the time that is exactly what you want them to do, and asking permission for every small concession would slow the whole team down for no benefit.

The problem is the tail. A handful of quotes each quarter carry a discount or a total that nobody senior would have agreed to, and they are discovered after the fact — at booking, or at invoicing, or when someone runs a margin report. By then the customer has the number in writing.

What the business asked for:

  • quotes above a value threshold cannot proceed until someone signs off;
  • everything below the threshold is completely unaffected — no extra step, no notification, nothing;
  • the approver is whoever currently manages the person who raised it, without maintaining a list;
  • an audit trail showing who approved what and when;
  • and the record genuinely held, not just flagged.

02Why the obvious approach fails

Four things get tried before anyone reaches for a real approval process, and each fails in the same fundamental way: it records an intention rather than preventing an action.

A required "approved by manager" field

Self-attested and unenforced. The person who wants the discount is the person who ticks the box, and nothing about the record changes when they do. It produces a field that is always true and means nothing.

An automation that emails the manager

This is a notification, not a gate. The quote is already saved, already printable and already sendable at the moment the email goes out. It tells you what happened; it does not stop it happening.

A pipeline step called "Approval"

A convention, not a control. Steps are moved by users, and a user who needs to get a quote out will move past it. It also silently misrepresents the data: the step says "approved" because someone dragged a card.

Naming the approvers individually

This one actually works — until the first reorganisation. Named approvers break when someone changes team, goes on leave, or leaves the company, and the failure mode is a queue of approvals waiting on a person who no longer exists. It also has to be maintained forever by whoever remembers it exists.

The real requirement is two things at once: a lock on the record while the decision is outstanding, and dynamic routing that resolves the approver from the org structure at the moment the approval is raised. Coevera's approval process provides both; nothing else in the platform provides either.

03Configuration — three calls, not one

Creating an approval process does not configure it, and configuring it does not enable it. These are three separate operations, and stopping after the first two leaves a process that looks present and does nothing.

StepCarries
1 · CreateOnly name, description and ownerId. No trigger, no filter, no approvers.
2 · ConfigureThe entire schema, as a JSON string: trigger, filter, settings, and the decision nodes.
3 · EnableA separate call. Until it runs, nothing fires.

The schema, by part

The configuration payload has four top-level members.

trigger

Names the entity and the event, and — usefully — who can trigger it. Beyond the entity type and an event such as create-or-update, the trigger carries an actor setting with lists of units and users. Left as any user it applies to everyone; narrowed, it lets you gate one team's quotes without touching another's.

filter — where the threshold lives

This is an ordinary filter node, the same structure used throughout the platform's automation. The captured example is named for what it does — sum over 10K — and contains a single rule: the quote total field, operator More, value 10000.

Two consequences worth drawing out. Below the threshold no approval is created at all — this is not an approval that auto-approves small deals, it simply does not fire. And because it is a general filter node, the threshold can be anything you can filter on: a discount percentage, a margin field, a product category, or several conditions combined.

Note that a numeric filter value on a money field is stored as a tuple carrying the amount alongside a currency reference — a bare number is not the whole story on a multi-currency field.

settings — who decides, and what is frozen

SettingWhat it does
approversA list of entries, each either a named user or a role type such as the sales unit manager.
allApproveWhether every approver must agree, or any one of them is sufficient.
canDelegateWhether an approver may hand the decision to someone else.
recordLockThe enforcement mechanism. Controls who may edit the record while the decision is pending.
expiration / expirationDays / expirationResultWhether a pending approval times out, after how long, and what verdict it takes on timeout.
salesProcessDependencyTies the approval to the record's sales-process position.

Use a role type for the approver, not a user id. A sales-unit-manager approver resolves from the record owner's unit when the approval is raised. It keeps working through reorganisations, leavers and holidays, and it needs no maintenance as the company changes shape. Named approvers are the single most common reason an approval process quietly stops working a year after it was built.

approveNodes and rejectNodes

Two arrays of automation nodes, run on the respective decision. They are the same node types used in ordinary automation, so anything automation can do, a decision can do — the captured example uses an update-record node to move the quote's pipeline step when approval is granted.

One shape detail that catches people: on an update-record node the payload lives in the entity's input as a JSON string, while the operations array only annotates which fields inside it are being written. Supplying operations against an empty input produces a generic, unhelpful error.

04What to actually set

For the requirement in §1, the shape is:

SettingValueWhy
Trigger entity / eventQuote · create-or-updateCatches both a quote raised above the threshold and one edited up to it later.
Trigger actorAny userGovernance that exempts some people is not governance.
FilterTotal · More · thresholdBelow it, nothing fires at all.
ApproversSales unit manager (role type)Survives staff and org changes with no maintenance.
allApprovefalse, for a single approverOnly meaningful with more than one approver; set deliberately if you add a second.
recordLockManagers may editBlocks the raiser, leaves an escalation path. See the caveat in §6.
expirationResultReject, not approveSee §6 — the alternative silently grants approval nobody gave.
Approve nodeMove the pipeline stepMakes the decision visible on the record, not only in the approval history.
Reject nodeMove the step, and notifyThe rejection path is the one most often left empty. See §6.

Naming the filter for its business meaning rather than its mechanics pays off later — the name is what an administrator sees when they are trying to work out why a quote is locked.

05Making the decision visible

The approval history records who decided what and when, and that satisfies audit. It does not satisfy the sales team, who need to see the state on the record itself.

That is what the decision nodes are for. On approval, move the quote to a step that reads as approved; on rejection, move it to one that reads as rejected and tell the owner. If a downstream automation needs to run — publishing a value, creating a task, writing back to a parent record — the decision node can trigger a further process rather than trying to do everything itself.

If the quote is a proxy standing in for something that cannot host an approval itself, the write-back is the whole point of the design — that pattern is Blueprint 001.

06Limits & trade-offs

The setting that turns governance into theatre

expirationResult can be set to approve. With expiry switched on and that verdict, an approval that nobody answers is granted automatically when the period elapses.

This is worse than having no approval process. It produces an audit trail that shows an approval which no human ever gave — and it will be discovered by whoever is auditing, not by you. If expiry is enabled at all, the result should be reject, with the escalation handled by the reject nodes. Check this value explicitly on every approval process you inherit.

The lock is not a freeze

A record-lock mode that permits managers to keep editing means exactly that. It is a sensible default — it leaves an escalation path when something urgent is stuck — but it means the record is not immutable while pending. Do not describe it to an auditor as though it were.

What an approval cannot be attached to

Approvals can only target Account, Contact, Lead, Opportunity or Quote. Custom entities are not supported as approval targets, and the approval record itself accepts no custom fields — so any approval metadata you need to report on has to live on the target record. Both constraints, and the proxy pattern that works around them, are in Blueprint 001.

The rejection path is usually missing

In every build we have reviewed, including our own, the approve branch was complete and the reject branch was empty or partial. The failure is quiet: a rejected record simply stays where it was, with no signal to its owner, and looks identical to one still waiting. Build the reject nodes at the same time as the approve nodes, or you will not build them at all.

Other things to know

  • Created is not enabled. A process can exist, be fully configured, report healthy, and never fire.
  • The configuration is a JSON string inside the request, which means it is not schema-validated the way ordinary arguments are. Type markers are required at the root and on nested nodes, and a payload missing them is rejected with a message that does not say so.
  • Money filter values carry a currency reference alongside the amount. A threshold on a multi-currency field is not just a number.
  • Only one captured configuration is verified here. Multi-approver behaviour — ordering, partial approval, what a delegation does to the audit trail — should be tested in your own space before you rely on it.

07Verification

An approval process is a control. A control that is believed to work and does not is the worst possible state, so the test plan matters more here than in most builds.

  • Confirm all three steps ran — created, configured, and enabled. Read the enabled state back rather than assuming the call succeeded.
  • Test below the threshold. The correct behaviour is that nothing happens at all — no approval record, no notification, no lock. An approval that fires and auto-approves is a different, wrong design.
  • Test above the threshold, and separately test editing a quote up across the threshold. The create-or-update event is what catches the second case, and it is the one people forget to try.
  • Verify the lock as the raiser, not as an administrator. Log in as a sales user and attempt the edit. Administrators frequently cannot reproduce the lock at all, which is why it gets signed off as working when it is not.
  • Exercise the expiry path deliberately. Set a short expiry in a test space, let it lapse, and confirm the verdict it takes is the one you intended.
  • Exercise rejection, not just approval. Confirm the record moves and the owner finds out.
  • Confirm approver resolution after an org change. Move a test user to a different unit and check the approval routes to the new manager.

What would signal a regression: approvals appearing on quotes below the threshold; a pending approval whose approver list is empty; records above the threshold reaching a closed state with no approval in their history; or an approval history showing decisions timestamped at exactly the expiry interval, which means the timeout is deciding instead of a person.

Published by Coevera · abstracted to the pattern, no client data Blueprint 004 · rev 2026-09-02