Skip to content

Financial Approvals Design

Ed Mozley edited this page Oct 31, 2026 · 1 revision

Financial approvals - design (#160)

Status: proposal - nothing built yet. This page sets out what I plan to build from the latest reply on issue #160, and why. It was written before any code so the approach can be checked first. @bigtrodat - please read it and answer the questions near the end on the issue. Anyone else with a view on how purchases get approved where you work is welcome to join in: the whole point of this design is that it should fit more than one organisation.

The request, from @bigtrodat on issue #160:

"Catalogue Item owner can approve, cost center responsible approves that there is enough budget, supervisor approves if employee is eligable to receive this item. Approval order should be adjustable by workflow design."

"When order value is higher than xyz as specific person (management board?) for approval."

The same reply also asks for cost centres in form lookups, for a main and substitute responsible person on each cost centre, and for an order value made from an item's price and a quantity.

In plain words

That request names four people: the catalogue item owner, the cost centre owner, the supervisor and the management board. Other organisations will have different people in a different order, and many have no cost centres at all. If FreeITSM built those four roles it would fit one way of working and get in the way of the rest.

So this design does not build four roles. It builds four general parts, and those four people become how one form happens to be set up.

Part What it does Size Useful on its own?
A. Cost centres in form lookups A form question that picks a cost centre, starting on the requester's own Small Yes
B. An owner and a deputy on each cost centre Who answers for a cost centre, set from the cost centre or the person, and kept in step with finance by import or API Small to medium Yes, as a record of who is responsible
C. Approval steps A form's approval becomes an ordered list. Each step says who decides and when the step applies Large Yes, this is the heart of it
D. Calculated answers A form works out a number, such as price times quantity, and any step can use it Medium Yes

Four rules run through all of it:

  1. Nothing changes on upgrade. A form with no steps set up behaves exactly as it does today.
  2. A request never approves itself. If FreeITSM cannot work out who decides, or cannot read the amount a step depends on, the request waits for a person. It is never waved through.
  3. FreeITSM records what was asked and who agreed. It is not a finance system. The cost centre owner decides whether there is budget. FreeITSM does not hold budgets, raise purchase orders, convert currencies or post anything to an ERP.
  4. Choosing, not typing. Every setting here is a dropdown, a tick box or a row in a list. There are no formulas or rule languages to type.

1. How the request maps on

Asked for How it is met
Cost centres in the forms lookup Part A
Main and substitute person, set from either side Part B
Catalogue item owner approves Part C: a step with A person or A team. No new "owner" field is needed
Cost centre owner approves Part C: a step with The owner of a cost centre
Supervisor approves Part C: a step with The requester's manager, using the Manager field FreeITSM already keeps and directory sync already fills
Order is adjustable Part C: drag the steps into any order, per form
Item value times quantity gives an order sum Part D
Above a certain value, somebody else approves Part C: a step with Only when Order total is more than 5,000, using the same conditions forms already offer for showing and hiding questions

The last row is the one that shows the shape. The threshold is not a feature of its own. It is a condition on a step, and the amount is an answer on the form.

2. The same parts in other organisations

This is the test of whether the design is general enough. Each row is set up with the same parts, and nothing below needs extra code.

Organisation Their approval How it is set up
Small firm, no cost centres The line manager agrees. The IT manager agrees as well over 500 Step 1 The requester's manager. Step 2 A person, only when the total is more than 500
School or charity One budget holder, chosen per request Part A question, step 1 The owner of a cost centre, deputy covers absence
Four-eyes finance Two of three budget holders must agree Step 1 A team, 2 approvals needed. This already works today
Managed service provider The customer's manager, then the provider's own account lead Step 1 The requester's manager, who signs in to the portal. Step 2 A person from the service desk
Free items need no sign-off Only spending is approved Every step only when the total is more than 0. A free request goes straight through
Access requests, no money at all The manager, then the security team Steps 1 and 2 with no calculated answer anywhere. Parts A, B and D are never touched

The last row matters most. Approval steps are not really a finance feature. Finance is the first thing to need them.

3. What FreeITSM has today

Already there Not there
Lookup questions, from one list of searchable sources Cost centres as a source
Cost centres per company, with a hierarchy, import, export and API sync Anybody responsible for a cost centre
Approval by a person, or a team with a set number of votes More than one step
Votes, Stuck, Move and Override, with a full record Anybody who is not staff approving anything
users.manager_id, filled by hand or by directory sync Anything routing a request along it
Number questions, table questions and conditions such as is more than A number worked out from other answers
Forms carrying their own actions through new versions
One money system: formats, favourites, a default currency Money on the portal, which does not load it yet

Anybody who is not staff approving anything is the gap that shapes the design, and section 6.3 explains why.

Forms are not multi-company yet, and this design does not need them to be. A form is shared by the whole install, so on a multi-company install every company gets the same steps, the same named people, the same thresholds and the same currency. The steps that depend on the requester still follow each company, because The requester's manager and The owner of a cost centre are worked out from the person asking. A company that needs different named approvers, thresholds or a different currency needs its own copy of the form, offered to its people through Who can request this form. Making Forms multi-company is a separate piece of work.


4. Part A - cost centres in form lookups

A new Cost centres choice under Search which records on a lookup question.

  • Finds by code or name, and shows both: 0010 - Logistics. Two companies can both have a 0010, so a code alone is not enough.
  • Offers active cost centres only. One that goes inactive later stays on old requests, the rule cost centres already follow.
  • Scoped to the company: the requester's own on the portal, the ones they can reach for staff. Somebody with no company gets the Default company's list, as lookups already do.
  • Off for customers until ticked, like every other lookup. A cost centre is safe to offer on the portal, but that stays the form owner's decision.
  • New tick box: Start on the requester's own cost centre. They can change it if the purchase belongs elsewhere.

The answer keeps both the id and the label, as every lookup does, so a renamed cost centre leaves old requests reading as they did.

5. Part B - an owner and a deputy

Two new boxes on a cost centre: Owner and Deputy. Both are optional. Each is a person from the people directory (Tickets -> Users), not an analyst and not a team. Decision 5 sets out why, and what that leaves out.

Where What
System -> Cost Centres Edit them, with an Owner column to sort and filter by
A person's page Responsible for lists their cost centres, and a cost centre can be added from there. Both screens write the same record, so they cannot disagree
Import and export Owner email and Deputy email columns
REST API owner_email and deputy_email, on POST /cost-centres/sync as well

Rules:

  • The owner and deputy must be in the cost centre's company. The same rule Part C needs, so nothing can route across companies.
  • People are matched by email address in import and sync, because that is what an ERP or HR system can supply. An address with no match is refused and listed in the preview, never guessed.
  • A column left out of a file changes nothing. An older import, or a sync that only knows codes, must not wipe the owners.
  • An owner who has left is kept and flagged, on the list and in the leaver's record, rather than removed without trace.

6. Part C - approval steps

6.1 What it looks like

Approval settings, the existing window on a form's shield icon, gains a list of steps. Today's choice between a person and a team becomes what one step holds, so an existing form opens showing one step, set up as it already was.

# Name Who decides Only when
β Ώ 1 Catalogue owner A person: Priya Shah Always
β Ώ 2 Cost centre owner The owner of: Cost centre Always
β Ώ 3 Line manager The requester's manager Always
β Ώ 4 Management board A team: Board, 2 approvals needed Order total is more than 5,000

Rows are dragged to reorder. The name is shown to the approver and the requester.

Who decides is a dropdown:

Choice Who that is Status
A person A named member of staff Today
A team Any member, with a set number of approvals and rejections Today
The requester's manager users.manager_id New
The owner of a cost centre The owner and deputy of the cost centre in a chosen question, or of the requester's own New

This list is built to grow. Each choice is one small function that answers "who may decide this?", so a later The owner of the chosen configuration item is one more row rather than a redesign.

Only when uses the condition builder forms already have: a question, a comparison and a value. All conditions, or any.

6.2 How a request moves

  • Steps run one at a time, in order. A step's approvers see the request when it reaches them.
  • Any rejection ends it. Later steps never run, the requester sees Declined, and the form's When rejected actions run.
  • When the last step passes, the request is approved, and everything that happens today happens then: the ticket, the When approved actions, the catalogue_request.approved event. Once.
  • "Only when" is decided at submission, because the answers cannot change after that. A step that does not apply stays on the record as Skipped, Order total was 3,000, so an auditor can see why it was skipped.
  • A condition that cannot be read means the step runs. If the amount is blank, a threshold step applies. Getting this backwards would mean a broken total skips the board.
  • The step list is fixed when the request is made. Changing the form affects new requests only, the rule approvals already follow. Who counts as the owner or the manager is checked at every decision, the rule team membership already follows, so a leaver cannot approve and their replacement can.
  • The owner and deputy are equals. Either can decide, and the first to do so settles the step.
  • The requester never approves their own step. If they own the cost centre, the deputy decides. If there is no deputy, it is Stuck.
  • Nobody found means Stuck, never skipped. No manager on the record, or no owner on the cost centre, marks the request Stuck. An administrator then Moves it or Overrides it, as today.
  • Move and Override act on the current step. Overriding one step does not wave through the rest.

6.3 Who can approve

Today only staff can approve, because approvers are analysts. Managers and budget holders are usually not staff in that sense. They are people in users, and FreeITSM stores their Manager field against that table. So a requester's manager step cannot work at all unless a person who is not a service desk analyst can decide. Giving every budget holder a service desk account is the wrong answer: it is a second password and a lot of screens for somebody who approves a few laptops a month.

So the self-service portal gains an Approvals page, beside Team tickets.

  • It shows requests waiting on the signed-in person, with every answer, and Approve and Reject.
  • Each read and each decision checks that this person may decide this step, right then. A request id in a web address grants nothing.
  • Decisions are recorded against the person, in the same request history as staff decisions.
  • Named staff and teams keep deciding in Forms -> Approvals. That page does not change for them.
  • A budget holder who is also staff approves from the portal, signed in as themselves. A person's right to approve comes from the sign-in, never from two records happening to share an email address.

This needs a person to be able to sign in to the portal. Approval settings warns when a cost centre owner has no email address. Directory and SSO sign-in already cover most people in an organisation large enough to have cost centres.

6.4 Telling people

Approvers are not told anything today unless somebody builds a workflow rule. One step could live with that. A chain cannot, because every step would wait silently for somebody who does not know it has arrived.

So reaching a step tells its approvers, through the portal bell and by email, with a link to the request. The email is sent from a mailbox chosen under Forms -> Settings. There is no approve button inside the email, because forwarding that message would hand over the decision.

The requester's Your requests panel shows where it has got to: Waiting for Cost centre owner, step 2 of 3.

6.5 Why not the workflow canvas?

The request says the order should be adjustable "by workflow design", and the steps do live on the form rather than as boxes on the Workflows canvas. Two reasons:

  • The workflow engine runs straight through. It has no way to stop, wait days for a person and carry on. Teaching it that is a large change to the most depended-on code in the product, and the approval engine already handles waiting, votes, Stuck, Move and Override.
  • Forms are versioned. A workflow pinned to a form stops matching when the form gets a new version. That is why a form's own actions already live on the form.

Workflows still see everything. The existing events stay, and a new A catalogue request passed a step event lets a rule do something between steps.

7. Part D - calculated answers

A catalogue item in FreeITSM is a form, so there are three shapes an order can take. All three are one new question type, Calculated, built by choosing rather than typing.

Shape Example How the total is worked out
The form is the item A New laptop form at a fixed price A fixed amount times the Quantity answer
The form offers a few items A dropdown of models The value on the chosen option times Quantity
The form is a basket A table question, one row per item A value column times a quantity column, added up down the rows

To make the second and third work, dropdown and choose-one options can carry a value. A new optional box appears beside each option: Standard laptop, 900.

The Calculated question has:

  • a list of lines, each chosen from dropdowns, added together;
  • a currency, chosen from the same list as everywhere else, starting on the install's default;
  • Show this to the person filling in the form, on by default. Some organisations would rather staff did not see prices. It is worked out either way.

Rules:

  • The server works it out. The browser shows a live total as a courtesy, but what the browser sends is ignored. Otherwise anybody could post a total of 1 and skip the board.
  • Hidden questions do not count. A question hidden by a condition adds nothing.
  • The total is frozen on the request, as a stored answer. Changing a price next month does not change last month's request.
  • It is an ordinary answer. The CSV, the PDF, conditions, and {{submission.fields.Order total}} in workflows all work with no extra code.
  • One currency per total. Amounts are never added across currencies or converted, the rule Projects already follows. A threshold on a step is in that total's currency.
  • Exact amounts. Amounts are stored as exact decimals to the currency's own places, and shown in each reader's own format, so 1.234,56 and 1,234.56 are the same number.

8. Worked example

A New laptop form has a dropdown of models (Standard 900, Pro 1,500), a Quantity, a Cost centre lookup, and an Order total in euros. It has the four steps in section 6.1.

  1. Tom asks for 2 Pro laptops on cost centre 0010. The form shows Order total: 3,000.00 €. FreeITSM recalculates it, keeps it, and marks step 4 as skipped: it is below 5,000.
  2. Priya, the catalogue owner, is staff. She approves in Forms -> Approvals. The bell and an email tell the next person.
  3. Cost centre 0010 has an owner and a deputy. The deputy gets to it first and approves in the portal.
  4. Tom's manager approves in the portal. That was the last step, so the ticket is raised with the answers, including the total and the cost centre.
  5. Tom watched it move through Your requests, and the request's record shows every decision, the skipped step and why it was skipped.

Had Tom asked for 4 Pro laptops, the total would have been 6,000 and step 4 would have run.

9. Not in this design

Left out Why
Holding budgets and checking the balance The owner knows the budget. A second copy of it in FreeITSM would be out of date and would look authoritative
Purchase orders, invoices, goods received That is the ERP's job
The CMDB as a price list The CMDB records what is really in the estate. A catalogue of things that could be bought does not belong in impact analysis. See decision 2
Out-of-office rules Owner and deputy are equals, so there is nothing to switch
Approving by replying to an email Forwarding a message must not forward the right to decide
Reminders and escalation after N days Worth doing later, once a real chain shows where things get stuck
Order cost, depreciation and service bookings Still on the issue's original list and still wanted, but a separate piece of work about assets rather than requests

10. Decisions still open

# Decision Where I am leaning
1 Do people who are not staff approve in the portal? Yes, as in section 6.3. Without it the manager and owner steps cannot work
2 Where do prices live? On the form, in a shared price list, or in the CMDB On the form first. A shared price list later if people change the same price on ten forms. Not the CMDB
3 If one person is both the manager and the cost centre owner, do they approve twice? Once, with the second step recorded as Already approved by. A per-form setting requires a different person at each step, for organisations that need it
4 A cost centre with no owner: Stuck, or climb to the parent's owner? A per-step choice, starting on Stuck. Cost centres already have a hierarchy, so the parent option is cheap
5 Is a cost centre's owner a person, an analyst or a team? A person from users, plus a deputy. See below

Decision 5: what kind of thing is an owner?

Owner is For Against
A person (users) Budget holders and managers are already here, with their company, manager and cost centre, and finance systems and directories can match them by email A member of staff who only has an analyst account needs a person record and a portal sign-in before they can be chosen
An analyst Forms -> Approvals already works for them Most budget holders are not service desk staff, so they would each need an analyst account
A team The vote counting already exists, and it suits a finance team sharing the job Teams hold analysts only, so it has the analyst problem. A team has no single person to answer for the money, which is what "responsible" means here

I am leaning towards a person, with a deputy, because a cost centre needs one named person who answers for it. A step set to A team already covers a finance team deciding together, for every request on that form. What a person-only owner cannot do is send cost centre 0010 to one team and 0020 to another. If anyone needs that, a Team box can be added beside Owner later, without changing what is built here.

11. Questions for @bigtrodat

  1. What does a catalogue item look like for you: one form per thing, one form offering several things, or one form as a basket? Part D handles all three, but it tells me which to build first.
  2. Is a form-level price enough? Or do you keep prices that change often and appear on many forms?
  3. After approval, what should the cost centre do? Today it would be an answer on the request and on the ticket's text. Tickets have no cost centre of their own. Should an asset bought this way pick it up, or is the record of the approval enough?
  4. Is your manager and budget holder data in Active Directory or another directory? If so, the Manager field fills itself, and the portal sign-in is ready for them.
  5. Should the deputy decide at any time, or only when the owner cannot? This design says any time.
  6. Does the fixed order work for you, one step at a time? Or do some of your approvals need to run side by side?
  7. Who owns your cost centres: always one named person, or sometimes a team? Are any of them people who only have an analyst account?

12. Build order

Each piece ships and is useful alone.

Order Piece Depends on
1 A: cost centre lookup Nothing
2 B: owner and deputy, screens, import, API Nothing
3 C1: steps with A person and A team, Only when, telling staff approvers Nothing. Already gives "IT manager, then finance"
4 C2: the portal Approvals page, then the manager and owner choices C1, and B for the owner choice
5 D: option values and Calculated answers Nothing, but it is what makes a money threshold possible. A plain number question works until then

For developers

The shape, so the size can be challenged. Exact names are settled in the build.

Schema

Where What Why
cost_centres owner_user_id, deputy_user_id, foreign keys to users People, not analysts. See section 6.3
forms approval_steps JSON, NULL by default NULL means today's single policy, so no migration and nothing half-migrated. The lesson of submission_actions. JSON so a new Who decides needs no schema change
form_submissions approval_steps snapshot, approval_step_no, approver_user_id, approval_decided_by_user_id Exactly one of each analyst or user pair is set, as submitted_by and submitted_by_user_id already work
form_submission_votes analyst_id nullable, new user_id, a second unique key ⚠️ TRAP: a unique key treats NULLs as different. With analyst_id NULL, the existing key stops nothing, so user votes need their own unique key on submission, round and user

Reusing the engine rather than building a second one

πŸ”‘ The existing snapshot columns always hold the current step's policy. Passing a step copies the next step into approver_id, approval_team_id and the two numbers, and increments approval_round. Votes are already counted per round, so catalogueApprovalProgress(), Stuck, Move, Override and the row lock in catalogueLockSubmission() all keep working with no second code path. The developer guide already called approval_round the starting point for this.

catalogueFinishDecision() then splits: the last step finishes as it does today, and any other step advances. The advance happens inside the same transaction and lock, so two final votes cannot advance twice.

Traps to carry into the build

Trap Rule
Settings that Save deletes approval_steps must go through copyApprovalPolicy() and createVersion(), and every new field setting through the form copy. This has bitten twice
Half a trigger The new step event needs both its dispatch() and its availableTriggers() entry
formLogicTestRule() on a blank is more than on a blank answer is false, which would skip a threshold step. Step conditions need a wrapper that treats an unreadable answer as "applies"
Losing an option's value Option values live in config, keyed so that renaming an option does not lose its value, because conditions compare on the option's text
Five type lists A new field type goes in the service, the AI generator's list and prompt, the JS evaluator and the builder. tests/forms-lookup/run.php says which was missed. It also needs both renderers, forms/fill.php and self-service/catalogue.php, and the task form embed
Lookup registry Cost centres need two new abilities in LOOKUP_SOURCES: a fixed filter (active only) and a label from two columns. The filter must apply to the search and the submit check, or the list offers something the submit refuses
Company on submit The cost centre chosen must be re-checked against the requester's own company on the server, and must be active, because it now decides who approves
Two identities, one person A person with both a staff and a portal account must not vote twice in a team step. Compare email addresses to refuse a second vote, never to grant one
Money on the portal Money::scriptTag() and money.js are not loaded on any portal page yet
The analyst lifecycle map Any new column pointing at analysts must go on the map in includes/analyst_lifecycle.php, and the leaver flow should show cost centres somebody owns

Things this makes stale

When this is built: Catalogue Request Approvals (What this version does not do), Portal Request Catalogue (limitations), Cost centres, Forms, the in-app help for Forms and System -> Cost Centres, and REST API: Cost centres.

See also

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally