Purchase Order, Receipt and Invoice Reconciliation
A three-way reconciliation checks whether the purchase order, warehouse receipt and supplier invoice agree before an invoice is cleared for payment.
Executed DuckDB reconciliation results generated from the retained BPI event log. It is not a screenshot of an Accounts Payable platform.
Before an invoice is paid
Should this supplier invoice be paid?
A buyer confirms what was ordered. The warehouse records what arrived. Accounts Payable records what the supplier billed. This build compares those events for every purchase order item, clears supported cases and holds the rest for the team that can resolve them.
The equation comes from the complete BPI Challenge 2019 build. Each item has one final class. Consignment items are kept outside the invoice decision rather than forced through a rule that does not apply. The retained reconciliation residual is 0 purchase items.
The purchase item has the required order, receipt and invoice evidence for its rule.
Consignment items remain visible but do not enter the standard supplier-invoice release.
A missing document, reversal or value difference keeps the item with a named team.
217,055 items are supported overall; 34,679 remain held before payment.
Primary retained evidence
The release equation, exception rows and trace come from the rebuilt outputs.
This evidence view is rendered from the retained DuckDB exports after the complete event log has been classified. It is not an Accounts Payable product screenshot; the executable matching SQL and source limits remain inspectable below.
251,734 purchase items reconcile to 217,055 supported and 34,679 held, with a zero-item residual. The dedicated mobile image keeps two complete exception rows readable.
Invoices that still need a decision
The difference stays beside the team that can investigate it.
The queue below is exported from DuckDB. It shows one retained item from several unresolved classes, including a missing invoice, a missing receipt, a reversal and a recorded value difference.
| Purchase item | Reason | Purchase value | Receipt value | Invoice value | Age at event-log close | Responsible team | Decision |
|---|---|---|---|---|---|---|---|
4507000280_00010 | Purchase item was cancelled or deleted | 397 | 0 | 0 | 702 days | Buyer | payment hold |
4507000473_00001 | Invoice is missing | 92 | 76,675 | 0 | 702 days | Accounts Payable | payment hold |
4507075965_00020 | A receipt or invoice was cancelled | 265 | 0 | 265 | 686 days | Finance reviewer | payment hold |
4507000855_00080 | Recorded values differ | 88 | 177 | 88 | 672 days | Buyer | payment hold |
4507004049_00030 | Warehouse receipt is missing | 92 | 0 | 92 | 658 days | Warehouse receiving | payment hold |
Values are the publisher's translated monetary fields. They preserve comparisons but do not represent disclosed currency amounts.
Exception reason Pareto
Most held items are missing an invoice or need a buyer decision.
- Invoice is missingAccounts Payable17,751
- Purchase item was cancelled or deletedBuyer8,417
- A receipt or invoice was cancelledFinance reviewer6,659
- Recorded values differBuyer1,556
- Warehouse receipt is missingWarehouse receiving296
How the decision is made
The purchase, receipt and invoice meet before payment.
The BPI item record states whether a goods receipt is required. Two-way items compare the purchase item and supplier invoice. Three-way items also require a warehouse receipt. Ordered rules then deal with timing, repeated events, reversals and missing records.
- 01The buyer raises a purchase item
The purchase document, supplier and required receipt rule remain attached.
- 02The warehouse records what arrived
Receipt, cancellation and repeated receipt events stay in recorded order.
- 03Accounts Payable records the invoice
The build checks whether the required records and translated values agree.
- 04The item is cleared or held
A held item keeps its reason, responsible team, age and complete event history.
Four routes that must not be conflated
Quantity, value, timing and missing records lead to different decisions.
A credible three-way control has to say which comparison failed, whether the retained source can test it and who owns the next action. These routes use only fields and counts present in the retained event-log build.
- 01
Quantity
Not recordedThe event log does not expose ordered, received or invoiced physical quantities. A production quantity rule would need those fields before payment release.- Decision
- Cannot test from this source
- Next team
- Additional purchasing extract required
- 02
Price / recorded value
1,556 itemsUnit prices are not exposed. The retained rule compares the translated purchase, receipt and invoice values and sends differences to the buyer.- Decision
- Payment hold
- Next team
- Buyer
- 03
Timing
109,197 itemsAn invoice recorded before its required receipt waits for the warehouse event; once values agree, the block can be removed with the event order retained.- Decision
- Cleared with timing note
- Next team
- Accounts Payable
- 04
Missing record
18,047 itemsA missing invoice stays with Accounts Payable. A three-way item without a receipt stays with the warehouse until the missing record is supplied or explained.- Decision
- Payment hold
- Next team
- Accounts Payable or warehouse
What the comparison found
Exact, grouped, timing and exception paths are all retained.
The full event log naturally exercises twelve of the fourteen configured classes. Small-value tolerance and partial-open behaviour are tested with explicit SQL fixtures because those two edge cases are not present in the public event log.
- 01Warehouse receipt is missing
A three-way item stays on hold until warehouse receiving records what arrived or corrects the purchase record.
- Purchase items
- 296
- Payment state
- payment hold
- Next team
- Warehouse receiving
- 02A receipt or invoice was cancelled
A finance reviewer checks the reversal history before the item can be cleared.
- Purchase items
- 6,659
- Payment state
- payment hold
- Next team
- Finance reviewer
- 03Invoice arrived before receipt
The invoice waited until the warehouse receipt was recorded, then Accounts Payable cleared it with the timing history retained.
- Purchase items
- 109,197
- Payment state
- cleared with timing note
- Next team
- Accounts Payable
- 04Several receipt events
More than one goods receipt belongs to the purchase item. The events are reviewed together before the invoice is cleared.
- Purchase items
- 1,076
- Payment state
- cleared after grouped review
- Next team
- Warehouse receiving
- 05Purchase, receipt and invoice agree
The required documents are present and their recorded translated values agree.
- Purchase items
- 89,301
- Payment state
- cleared by rule
- Next team
- Accounts Payable
- 06Recorded values differ
The buyer reviews the purchase record and supplier bill because the translated values do not agree within the configured rule.
- Purchase items
- 1,556
- Payment state
- payment hold
- Next team
- Buyer
Executable matching artefact
The payment state is assigned by ordered SQL, not by the website.
The retained SQL classifies each purchase item once, joins it to the named rule and assigns the payment state and responsible team. It runs against the retained DuckDB database during the verifier; this excerpt is the executable file.
CREATE TABLE mart.purchase_item_reconciliation AS
WITH classified AS (
SELECT
comparison.*,
CASE
WHEN match_requirement = 'CONSIGNMENT'
THEN 'OUTSIDE_INVOICE_SCOPE'
WHEN deleted_event_count > 0
THEN 'CANCELLED_OR_DELETED'
WHEN invoice_receipt_event_count = 0
THEN 'MISSING_INVOICE'
WHEN match_requirement = 'THREE_WAY'
AND goods_receipt_event_count = 0
THEN 'MISSING_RECEIPT'
WHEN reversal_event_count > 0
THEN 'REVERSAL_REVIEW'
WHEN invoice_arrived_before_receipt
AND cleared_after_receipt
AND purchase_to_invoice_difference <= value_tolerance
AND receipt_to_invoice_difference <= value_tolerance
THEN 'TIMING_ITEM_RESOLVED'
WHEN goods_receipt_event_count > 1
AND invoice_receipt_event_count > 1
AND purchase_to_invoice_difference <= value_tolerance
AND receipt_to_invoice_difference <= value_tolerance
THEN 'MANY_TO_MANY'
WHEN goods_receipt_event_count > 1
AND invoice_receipt_event_count = 1
AND purchase_to_invoice_difference <= value_tolerance
AND receipt_to_invoice_difference <= value_tolerance
THEN 'ONE_TO_MANY_RECEIPTS'
WHEN goods_receipt_event_count <= 1
AND invoice_receipt_event_count > 1
AND purchase_to_invoice_difference <= value_tolerance
AND receipt_to_invoice_difference <= value_tolerance
THEN 'ONE_TO_MANY_INVOICES'
WHEN match_requirement = 'THREE_WAY'
AND purchase_to_invoice_difference <= 0.01
AND receipt_to_invoice_difference <= 0.01
THEN 'EXACT_THREE_WAY'
WHEN match_requirement = 'TWO_WAY'
AND purchase_to_invoice_difference <= 0.01
THEN 'EXACT_TWO_WAY'
WHEN purchase_to_invoice_difference <= value_tolerance
AND receipt_to_invoice_difference <= value_tolerance
THEN 'WITHIN_VALUE_TOLERANCE'
WHEN recorded_invoice_value < purchase_item_value
AND invoice_cleared_at IS NULL
THEN 'PARTIAL_OR_OPEN'
ELSE 'VALUE_DIFFERENCE'
END AS match_class
FROM staging.document_comparison AS comparison
)
SELECT
classified.*,
rule.rule_id,
rule.priority AS rule_priority,
rule.condition_summary,
rule.responsible_team,
rule.decision,
CASE
WHEN match_class IN (
'OUTSIDE_INVOICE_SCOPE',
'TIMING_ITEM_RESOLVED',
'ONE_TO_MANY_RECEIPTS',
'ONE_TO_MANY_INVOICES',
'MANY_TO_MANY',
'EXACT_THREE_WAY',
'EXACT_TWO_WAY',
'WITHIN_VALUE_TOLERANCE'
) THEN 'SUPPORTED'
ELSE 'UNRESOLVED'
END AS final_group,
CASE
WHEN match_class = 'OUTSIDE_INVOICE_SCOPE' THEN 'OUTSIDE_SCOPE'
WHEN match_class = 'TIMING_ITEM_RESOLVED' THEN 'CLEARED_WITH_TIMING_NOTE'
WHEN match_class IN (
'ONE_TO_MANY_RECEIPTS',
'ONE_TO_MANY_INVOICES',
'MANY_TO_MANY'
) THEN 'CLEARED_AFTER_GROUPED_REVIEW'
WHEN match_class IN (
'EXACT_THREE_WAY',
'EXACT_TWO_WAY'
) THEN 'CLEARED_BY_RULE'
WHEN match_class = 'WITHIN_VALUE_TOLERANCE' THEN 'CLEARED_WITH_TOLERANCE'
ELSE 'PAYMENT_HOLD'
END AS payment_state,
CASE
WHEN match_class IN (
'OUTSIDE_INVOICE_SCOPE',
'TIMING_ITEM_RESOLVED',
'ONE_TO_MANY_RECEIPTS',
'ONE_TO_MANY_INVOICES',
'MANY_TO_MANY',
'EXACT_THREE_WAY',
'EXACT_TWO_WAY',
'WITHIN_VALUE_TOLERANCE'
) THEN 0
ELSE GREATEST(
0,
DATE_DIFF(
'day',
CAST(last_event_at AS DATE),
(
SELECT MAX(CAST(event_timestamp AS DATE))
FROM staging.events
WHERE event_timestamp >= TIMESTAMPTZ '2018-01-01 00:00:00+00'
AND event_timestamp < TIMESTAMPTZ '2020-01-01 00:00:00+00'
)
)
)The retained source files are the XES event log, the executable SQL and Python build, the DuckDB reconciliation database and the exported exception, validation and trace files.
One purchase item traced
This invoice waited for the warehouse receipt.
Purchase item 4507000232_00010 carried the same translated value of 564 on the purchase, receipt and invoice events. The invoice was recorded first, so payment stayed blocked until the warehouse event arrived.
- Supplier invoice decision
- Clear with retained timing note
- Responsible team
- Accounts Payable
- Purchase item value
- 564
2 Jan 2018
3 Jan 2018
4 Jan 2018
Event order
The event history explains the payment hold and release.
The item history is generated from the retained event extract. It is not a narrative added after the page was written.
- 01Create Purchase Order Item
Buyer · 2 Jan 2018
- 02Vendor creates invoice
Accounts Payable · 2 Jan 2018
- 03Record Invoice Receipt
Accounts Payable · 3 Jan 2018
- 04Record Goods Receipt
Warehouse receiving · 4 Jan 2018
- 05Remove Payment Block
Accounts Payable · 4 Jan 2018
- 06Clear Invoice
Accounts Payable · 25 Jan 2018
Accounts Payable removed the block after receipt and cleared the invoice on 25 Jan 2018. The retained decision is “clear with timing note”.
Checks before report release
Twenty-one checks passed on the complete build.
SQL checks the publisher counts, unique keys, classification, responsible teams, summary tie-outs, one purchase item and rule edge cases. A second verifier reads the exported files without querying DuckDB.
Complete purchase-item source retained
PASSComplete event source retained
PASSNo purchase item is classified twice
PASSEvery unresolved item has a responsible team
PASSSummary item counts tie to detail
PASSOut-of-window timestamps are retained as exceptions
PASSTechnical disclosure
The detailed dataset limits stay with the rebuild evidence.
The public page uses the verified counts and keeps the licence, checksum, anomaly list, SQL and independent verifier available for inspection.
Technical detailsOpen the DOI, rebuild command, checks and event-log limits
The build processes all 1,595,923 recorded events for 251,734 purchase order items in BPI Challenge 2019. It retains the publisher DOI, licence, checksum and 320 timestamps outside the main 2018–2019 process window as visible data exceptions.
npm run verify:reconciliationThe command verifies the 728 MB XES file, streams every trace, rebuilds the DuckDB model, exports the decisions and checks those files independently.
Published by 4TU.Centre for Research Data under CC BY 4.0. The DOI resolves to the historical, anonymised BPI Challenge 2019 purchase-to-pay event log.
- BPI Challenge 2019 is historical anonymised research data and does not describe current organisational performance.
- The publisher translated monetary values, so displayed values support comparison but are not disclosed currency amounts.
- The BPI event log does not expose physical ordered, received or invoiced quantities. Quantity matching would require an additional documented extract.
- The build is not an Accounts Payable product, an audit opinion or proof that a live financial control is effective.
Next step
Review the controls beneath a supplier payment queue.
Start with the files, joins and checks that make the current report difficult to explain or maintain.
