Check the order number
Purchasing enters the invoice’s order number during the review, a field the workflow declares itself. Then the script asks the order system for that order through the data source bestellungen. When the invoice matches the order (up to 2 % above it), it goes to the approval, with the order’s amount as a hint. When the number is missing, the order doesn’t exist, the order system doesn’t answer, or the invoice lies above it, purchasing clarifies it first. When the order is a document in Acriva, it needs no script and no data source: Order match.
The workflow
Section titled “The workflow”The workflow starts for every document of the type incoming-invoice. Its file workflow.yaml:
$schema: 'https://docs.acriva.de/schemas/workflow.schema.json'format: 1schemaVersion: 4sdk: '1.0.0'key: order-number-checkname: Bestellnummer prüfentemplate: customtrigger: documentType: incoming-invoicefields: - key: orderNumber kind: text labelDe: Bestellnummer labelEn: Order number documentTypes: - incoming-invoicestart: reviewsteps: - kind: humanTask name: review label: Sachliche Prüfung taskKind: review assignees: - when: null assignee: kind: member member: einkauf@example.com fallback: kind: role role: Buchhaltung options: excludeRequester: false excludeActorsOfSteps: [] pickFile: false next: check onRejected: rejected - kind: script name: check label: Bestellung prüfen script: check.ts hook: onApproved inputs: - kind: field key: orderNumber type: text - kind: field key: totalGross type: money - kind: source key: bestellungen returns: - key: result type: enumeration values: - matches - clarify branch: result exits: - value: matches next: approve - value: clarify next: clarify next: approve - kind: humanTask name: clarify label: Bestellung klären taskKind: fix assignees: - when: null assignee: kind: member member: einkauf@example.com fallback: kind: role role: Buchhaltung options: excludeRequester: false excludeActorsOfSteps: [] pickFile: false next: approve - kind: humanTask name: approve label: Freigabe taskKind: approve assignees: - when: null assignee: kind: costCenterResponsible fallback: kind: role role: Buchhaltung options: excludeRequester: true excludeActorsOfSteps: [] pickFile: false next: approved onRejected: rejected - kind: end name: approved label: Freigegeben outcome: approved - kind: end name: rejected label: Zurückgewiesen outcome: rejectedThe script check.ts
Section titled “The script check.ts”Hook: onApproved · Inputs: orderNumber, totalGross, data source bestellungen · Return values: result (matches, clarify)
import { defineScript, money } from '@acriva/workflow-sdk';
/** How far an invoice may lie above its order, as a share of the order's amount. */const TOLERANCE = 0.02;
/** What the order system answers to `GET bestellungen/<Bestellnummer>`: the order's gross amount in euros. */interface Order { betrag: number;}
/** A source's answer is JSON of any shape until the script has checked it. */const isOrder = (data: unknown): data is Order => typeof data === 'object' && data !== null && typeof (data as Order).betrag === 'number';
export default defineScript({ hook: 'onApproved', outputs: { result: ['matches', 'clarify'] }, async run(ctx) { const { orderNumber, totalGross } = ctx.document.fields; const number = orderNumber?.trim(); if (!number) { ctx.hint( 'Die Rechnung hat keine Bestellnummer. Bitte klären Sie, zu welcher Bestellung sie gehört.', ); return ctx.branch('clarify'); } const result = await ctx.sources.get('bestellungen', { path: number }); if (!result.ok) { ctx.hint( result.status === 404 ? `Die Bestellung ${number} gibt es im Bestellsystem nicht.` : `Das Bestellsystem hat nicht geantwortet (${result.error}). Bitte prüfen Sie die Bestellung ${number} von Hand.`, ); return ctx.branch('clarify'); } if (!isOrder(result.data)) { ctx.hint(`Das Bestellsystem hat zur Bestellung ${number} keinen Betrag geliefert.`); return ctx.branch('clarify'); } const ordered = result.data.betrag; if (totalGross != null && totalGross > ordered * (1 + TOLERANCE)) { ctx.hint( `Die Rechnung über ${money.format(totalGross)} liegt über der Bestellung ${number} über ${money.format(ordered)}.`, ); return ctx.branch('clarify'); } ctx.hint(`Passt zur Bestellung ${number} über ${money.format(ordered)}.`); return ctx.branch('matches'); },});What it does
Section titled “What it does”A test runs each of these cases in the sandbox and compares the result.
An invoice above its order
Section titled “An invoice above its order”- Input:
orderNumber4500012345,totalGross1499.4, last task outcomeApproved - Data source
bestellungen: answers{"betrag":1190} - Exit:
clarify - Changes: hint on the next task: “Die Rechnung über 1.499,40 € liegt über der Bestellung 4500012345 über 1.190,00 €.”
An invoice that matches its order
Section titled “An invoice that matches its order”- Input:
orderNumber4500012345,totalGross1190, last task outcomeApproved - Data source
bestellungen: answers{"betrag":1190} - Exit:
matches - Changes: hint on the next task: “Passt zur Bestellung 4500012345 über 1.190,00 €.”
An invoice without an order number
Section titled “An invoice without an order number”- Input:
orderNumbernull,totalGross238, last task outcomeApproved - Exit:
clarify - Changes: hint on the next task: “Die Rechnung hat keine Bestellnummer. Bitte klären Sie, zu welcher Bestellung sie gehört.”
An order number that doesn’t exist
Section titled “An order number that doesn’t exist”- Input:
orderNumber4500099999,totalGross595, last task outcomeApproved - Data source
bestellungen: no data:http-error(status 404) - Exit:
clarify - Changes: hint on the next task: “Die Bestellung 4500099999 gibt es im Bestellsystem nicht.”