Case studiesReference integration: finance approval

Approval before ERP posting: a person says yes above a threshold

How a flow holds a large journal for a named approver before it is posted to an ERP, what happens to a rejection or a timeout, and how the switch from read-only to live posting is signed off by a second person in a protected environment.

Why a posting needs a gate

An integration that posts to an ERP can post a wrong amount as fast as a right one. Finance teams usually want a person to confirm the large ones before they reach the books, and want a record of who said yes. Two things go wrong with a hand-built gate: the held record is lost if the process restarts, and the switch that turns real posting on is flipped by whoever has access.

This reference flow holds the record in a durable inbox, takes a decision from someone with the right role, and sends a rejection or a timeout down an error path that tells the requester.

The flow

Nodes on the approval path, in order

  1. journals ingest
  2. approve above threshold
  3. http: post to ERP

Nodes on the rejection path, in order

  1. approve: error port
  2. http: tell the requester

A journal arrives over HTTP ingest with an id, an amount, a currency and a memo. Under the threshold it goes straight to the ERP. Above it, the approve node parks the record in the approvals inbox until someone with the role approves or rejects it. An approved record continues to the post node. A rejection, or a timeout, takes the error port to a second HTTP node that tells the requester the outcome.

biz-approval-posting, approve node (the threshold is the example’s default)
{
  "id": "approve", "type": "approve",
  "title": "Post journal ${journal_id}",
  "summary": "${memo}: ${amount} ${currency}",
  "when": "metadata.amount > 10000000",
  "role": "editor",
  "key": "${journal_id}",
  "amount": "amount",
  "timeout": "72h",
  "escalate_after": "4h"
}

The threshold is compared with the amount as sent. The example does no currency conversion, and the inbox shows a currency only when the record carries one.

  1. Parked. The node writes the record to a durable database (approvals.db) and settles the source: an ingest call returns done, and nothing is redelivered while a person decides. The key makes parking idempotent, so a redelivery of the same journal is absorbed.
  2. Decided. In Operations, Approvals, the REST API, an MCP tool or an app, someone who holds the node’s role or is named on it approves or rejects. A rejection needs a comment. A decision is taken once.
  3. Resumed. The record re-enters the flow at the approve node, which reads the decision from the database. Approved records continue with the approver, role, time and comment attached. Rejected or timed-out records leave by the error port.
  4. Escalated. After four hours without a decision the node raises one alert. After 72 hours it times out.

Tested cases

The repository’s tests exercise the approve node and the go-live gate through the real routes, using a payment flow built with the same node and the same checks. The example flow itself is deployed and its sample journal is dry-run by the example tests.

Test cases and results
CaseWhat happenedResult
Amount below the thresholdA payment of 500Flowed straight through, nothing parked
Amount above the thresholdPayments of 5,000 and 7,000Both parked and the source got 200. The inbox listed them oldest first
A viewer opens the inboxReads the queueSees the items with can_decide false
An operator tries to approveThe node needs an editor403 not_an_approver, naming the required role
A rejection without a reasonEditor rejects with no comment422
An editor approves with a commentThe record continues with the approval attachedPosted. A second decision on it got 409
An admin rejects with a reasonThe record leaves by the error portRouted, so no dead letter, and the flow settles
The process restarts with a record parkedA new process opened the same approvals databaseStill pending. Nothing had continued. Approving it after the restart resumed it
Direct switch to live in a protected environmentAn admin set the posting variable to live409 go_live_required, and the mode stayed read-only. Switching to sandbox_only was allowed
A go-live requestAn operator filed one, then an editor filed one with no reasonThe operator got 403 and the missing reason got 422

Turning live posting on

An http node posts for real unless its mode says otherwise. With mode set to a variable such as ${var:POST_MODE}, the instance decides per environment: live, read_only or sandbox_only. In a protected environment that variable cannot be set to live directly, not even by an admin. Two people are needed.

  1. An editor files a request with the variable, the value live and a reason.
  2. A designated approver who is not the requester approves it with a comment. The variable is set at that moment.
  3. The decision, with who asked, who approved, their role and the time, is stored, written to the audit log and shown on the posting node.

A request expires after 72 hours by default. Switching away from live needs no sign-off, so the safe direction is always one call. The example flow’s post node has no mode set, so it is not gated as shipped. To gate it, set its mode to a variable.

Scope

  • The example posts to a bundled mock ERP. A real ERP needs its base URL, its journal format and its authentication, set per installation.
  • The go-live gate guards variable-driven modes on http and SOAP nodes. A node whose mode is a literal live, or has no mode, is not gated. In a protected environment the flow document changes only through an approved promotion or a rollback.
  • Someone with shell access can still edit the variables file. The posting status flags a gated node that posts live with no approved request behind it.
  • If the connector stops while a record is past the approve node, the approval is marked interrupted. A person checks the target system, then resumes it.

Related

From download to first record in minutes.

Run one binary, connect a PLC, a CNC or a webhook, and watch the records arrive in the editor.

Builds for Linux (x86_64 and ARM64), Windows and macOS.

  • One binary
  • Raspberry Pi to rack server
  • Signed delivery with retries

Sizing questions or an air-gapped install? Email marketing@opexcg.com or read the quick start.