> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ravenna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Purchase approvals

> One purchase request template that routes itself by amount, using skip conditions, dynamic approvers, and parallel rounds instead of a form per threshold.

export const Checkbox = ({items = []}) => {
  const [checked, setChecked] = useState(Array(items.length).fill(false));
  const toggle = index => {
    setChecked(prev => {
      const copy = [...prev];
      copy[index] = !copy[index];
      return copy;
    });
  };
  return <div className="custom-checkbox-list">
      {items.map((text, index) => <div key={index} className="custom-checkbox-item flex items-center mb-2">
          <input type="checkbox" checked={checked[index]} onChange={() => toggle(index)} className="mr-2" />
          <span>{text}</span>
        </div>)}
    </div>;
};

Most spend approval processes are documented in a policy nobody reads and enforced by whoever happens to be on the email thread. The goal here is one request form where the approval path is decided by the request itself.

<Info icon="list-check">
  **Prerequisites:** a Finance workspace with a channel, and the custom fields listed in the [Finance overview](/guides/playbooks/finance/overview). **Amount** must be a number field, not text, or none of the threshold routing works.
</Info>

## What good looks like

<Checkbox
  items={[
"One form, whatever the amount",
"A 400 software renewal takes one approval, a 400,000 contract takes four",
"Approvers are resolved from your org chart, not from a list Finance maintains",
"The audit trail is the ticket, not a reconstructed email thread"
]}
/>

<Steps>
  <Step title="Design the thresholds before you touch the product">
    Write the tiers down as a table and get them signed off. Everything else is data entry.

    A shape that works for most mid-size companies:

    | Amount            | Approvals required                        |
    | ----------------- | ----------------------------------------- |
    | Under 1,000       | Manager only                              |
    | 1,000 to 10,000   | Manager, then cost center owner           |
    | 10,000 to 100,000 | Manager, cost center owner, then Finance  |
    | Over 100,000      | All of the above, plus executive sign-off |

    Note that these are nested, not alternatives. Each tier adds a round rather than replacing the path. That is what makes a single template possible.
  </Step>

  <Step title="Build one template with conditional rounds">
    Create one **Purchase request** approval template holding every round, then use **skip conditions** so the rounds that do not apply are skipped when the template is applied.

    Skip conditions skip a round when they **match**. So you express the condition for when the round is *not* needed:

    | Round              | Policy          | Approvers                      | Skip condition              |
    | ------------------ | --------------- | ------------------------------ | --------------------------- |
    | Manager review     | Any can approve | Requester's manager            | None, always runs           |
    | Cost center owner  | Any can approve | The relevant group             | Amount is less than 1,000   |
    | Finance review     | Any can approve | Finance group                  | Amount is less than 10,000  |
    | Executive sign-off | Any can approve | Requester's skip-level manager | Amount is less than 100,000 |

    <Warning>
      Skip conditions are evaluated once, at the moment the template is applied. Editing the amount on a ticket afterwards does not add or remove a round. If the amount changes materially, reset the rounds rather than editing the field and hoping.
    </Warning>

    <Callout icon="link" color="#6B7280">Learn more about [skip conditions](/documentation/tickets/approvals/templates#skip-conditions)</Callout>
  </Step>

  <Step title="Use dynamic approvers for the human rounds">
    **Requester's manager** resolves from your HRIS or identity provider at apply time, cascading through each configured source until one returns a manager. **Requester's skip-level manager** goes strictly two levels up, which is what you want for executive sign-off.

    If either role resolves to nobody, Ravenna substitutes the workspace admins so the round is never left empty. That is a safety net, not a routing strategy: a request that keeps landing with Finance admins usually means the org chart in your HRIS is incomplete.

    <Callout icon="link" color="#6B7280">Learn more about [dynamic approvers](/documentation/tickets/approvals/templates#dynamic-approvers)</Callout>
  </Step>

  <Step title="Run the independent reviews in parallel">
    Rounds that share a stage run at the same time. Finance review and a security review of a new vendor do not depend on each other, so putting them in one stage takes a day out of the cycle. Drag one round onto another in the template to group them.

    Keep manager review alone in the first stage. It is the cheapest signal and it should filter before anyone else spends time.

    <Callout icon="link" color="#6B7280">Learn more about [parallel rounds](/documentation/tickets/approvals/rounds#run-rounds-in-parallel)</Callout>
  </Step>

  <Step title="Nudge, then escalate">
    Approvals stall. Two mechanisms handle it:

    * **Reminder policies** nudge pending approvers on a schedule without you building anything.
    * A workflow on the **Reminder Expired** trigger can post a private note listing who is still pending, or notify the approver's manager on the second nudge.

    Those two are the whole escalation path. **Wait for Approval** has no timeout branch: a wait that expires without a decision matches neither **On Approved** nor **On Declined**, so the run simply ends. Escalation that self-heals comes from the Reminder Expired workflow, not from the wait step.

    <Callout icon="link" color="#6B7280">Learn more in [approval flows](/guides/playbooks/approval-flows)</Callout>
  </Step>

  <Step title="Report on it from week two">
    Once **Amount**, **Vendor**, and **Cost center** are real fields, the analytics come free: spend requested by cost center, cycle time from submission to final approval, and approval volume per approver. The last one tends to surface the bottleneck nobody had named.

    <Callout icon="link" color="#6B7280">Learn more in [reporting and briefs](/guides/playbooks/reporting-and-briefs)</Callout>
  </Step>
</Steps>

## Where teams get this wrong

<AccordionGroup>
  <Accordion title="Amount as a text field">
    No threshold routing, no spend reporting. Fix this before anything else.
  </Accordion>

  <Accordion title="A form per threshold">
    Requesters guess wrong, and every policy change means editing four forms.
  </Accordion>

  <Accordion title="Naming approvers directly">
    The template rots at the first reorg.
  </Accordion>

  <Accordion title="All must approve everywhere">
    Reserve consensus for the rounds where a single yes genuinely is not enough. Everywhere else it just adds a week.
  </Accordion>

  <Accordion title="Forgetting currency">
    A threshold of 10,000 means something different in three markets.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={2}>
  <Card title="Expenses and invoices" icon="file-text" href="/guides/playbooks/finance/expenses-and-invoices" horizontal>
    The other half of the Finance desk.
  </Card>

  <Card title="Approval flows" icon="check-check" href="/guides/playbooks/approval-flows" horizontal>
    The full mechanics of rounds and policies.
  </Card>
</CardGroup>


## Related topics

- [Approval flows](/guides/playbooks/approval-flows.md)
- [Expenses and invoices](/guides/playbooks/finance/expenses-and-invoices.md)
- [Finance](/guides/playbooks/finance/overview.md)
- [Playbooks](/guides/playbooks/overview.md)
- [Guides](/guides/overview.md)
