Zum Inhalt springen

Bestellnummer prüfen

Der Einkauf trägt bei der sachlichen Prüfung die Bestellnummer der Rechnung ein, ein Feld, das der Workflow selbst deklariert. Danach fragt das Skript das Bestellsystem über die Datenquelle bestellungen nach dieser Bestellung. Passt die Rechnung zur Bestellung (bis zu 2 % darüber), geht sie zur Freigabe, mit dem Betrag der Bestellung als Hinweis. Fehlt die Nummer, gibt es die Bestellung nicht, antwortet das Bestellsystem nicht oder liegt die Rechnung darüber, klärt der Einkauf sie zuerst. Liegt die Bestellung als Dokument in Acriva, geht es ohne Skript und ohne Datenquelle: Bestellabgleich.

Der Workflow startet für jedes Dokument vom Typ incoming-invoice. Seine Datei workflow.yaml:

$schema: 'https://docs.acriva.de/schemas/workflow.schema.json'
format: 1
schemaVersion: 4
sdk: '1.0.0'
key: order-number-check
name: Bestellnummer prüfen
template: custom
trigger:
documentType: incoming-invoice
fields:
- key: orderNumber
kind: text
labelDe: Bestellnummer
labelEn: Order number
documentTypes:
- incoming-invoice
start: review
steps:
- 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: rejected

Hook: onApproved · Eingaben: orderNumber, totalGross, Datenquelle bestellungen · Rückgaben: 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');
},
});

Ein Test führt jeden dieser Fälle in der Sandbox aus und vergleicht das Ergebnis.

  • Eingabe: orderNumber 4500012345, totalGross 1499.4, Ergebnis der letzten Aufgabe Approved
  • Datenquelle bestellungen: antwortet {"betrag":1190}
  • Ausgang: clarify
  • Änderungen: Hinweis an der nächsten Aufgabe: „Die Rechnung über 1.499,40 € liegt über der Bestellung 4500012345 über 1.190,00 €.“
  • Eingabe: orderNumber 4500012345, totalGross 1190, Ergebnis der letzten Aufgabe Approved
  • Datenquelle bestellungen: antwortet {"betrag":1190}
  • Ausgang: matches
  • Änderungen: Hinweis an der nächsten Aufgabe: „Passt zur Bestellung 4500012345 über 1.190,00 €.“
  • Eingabe: orderNumber null, totalGross 238, Ergebnis der letzten Aufgabe Approved
  • Ausgang: clarify
  • Änderungen: Hinweis an der nächsten Aufgabe: „Die Rechnung hat keine Bestellnummer. Bitte klären Sie, zu welcher Bestellung sie gehört.“
  • Eingabe: orderNumber 4500099999, totalGross 595, Ergebnis der letzten Aufgabe Approved
  • Datenquelle bestellungen: keine Daten: http-error (Status 404)
  • Ausgang: clarify
  • Änderungen: Hinweis an der nächsten Aufgabe: „Die Bestellung 4500099999 gibt es im Bestellsystem nicht.“