# Willow Event Kitchen: procure-to-pay document chain

One month of ordering, receiving, invoicing, crediting, reconciling and paying between Willow Event Kitchen and its two suppliers. Every document references the supplier ids, ingredient ids, dates, quantities and unit prices that already exist in the `restaurant-catering` business kit's `purchases` table, so this chain and that table agree.

Fixed snapshot 2026-09-08. All amounts in CAD. GST at 5 percent applies to every line.

## The chain

| Step | Document | Number | Date | Value |
| --- | --- | --- | --- | ---: |
| 1 | Price list | 48 rows | 2026-08-05, 2026-08-12, 2026-08-19, 2026-08-26 | list, case and contract tier prices |
| 2 | Purchase order | PO-CAT-1001 | 2026-08-05 | 310.63 |
| 2 | Purchase order | PO-CAT-1002 | 2026-08-12 | 208.13 |
| 2 | Purchase order | PO-CAT-1003 | 2026-08-19 | 211.02 |
| 2 | Purchase order | PO-CAT-1004 | 2026-08-26 | 270.22 |
| 3 | Goods received | GRN-CAT-2001 | 2026-08-07 | 60 units delivered |
| 3 | Goods received | GRN-CAT-2002 | 2026-08-14 | 71 units delivered |
| 3 | Goods received | GRN-CAT-2003 | 2026-08-21 | 99 units delivered |
| 3 | Goods received | GRN-CAT-2004 | 2026-08-28 | 78 units delivered |
| 4 | Supplier invoice | INV-CAT-5001 | 2026-08-07 | 310.63 |
| 4 | Supplier invoice | INV-CAT-5002 | 2026-08-14 | 208.13 |
| 4 | Supplier invoice | INV-CAT-5003 | 2026-08-21 | 180.19 |
| 4 | Supplier invoice | INV-CAT-5004 | 2026-08-28 | 270.22 |
| 4 | Supplier invoice | INV-CAT-5002 | 2026-08-17 | 144.60 |
| 5 | Credit note | CN-CAT-7001 | 2026-08-21 | -10.16 |
| 5 | Credit note | CN-CAT-7002 | 2026-08-29 | -5.99 |
| 6 | Statement | STM-CAT-6001 | 2026-08-31 | 1003.36 |
| 6 | Statement | STM-CAT-6002 | 2026-08-31 | 981.44 |
| 7 | Remittance advice | REM-CAT-8001 | 2026-09-05 | 1088.50 |

## What deliberately disagrees

These six disagreements are on purpose. Each one is a failure a real accounts-payable system has to detect, and each is stated here with the exact numbers so a test can assert the detection rather than merely the parse.

### 1. Stated tax total is one cent above the sum of the line tax amounts

Affects: INV-CAT-5003 dated 2026-08-21.

Invoice INV-CAT-5003 states a tax total of 8.59 CAD and a total due of 180.19 CAD. Its three line tax amounts sum to 8.58 CAD, one cent less. The document-level calculation round(net x 0.05, 2) on a net of 171.60 gives 8.58 CAD.

Assertion: `statedTaxTotal - lineTaxSum == 0.01`.

### 2. Invoice bills the ordered quantity after a recorded short delivery

Affects: INV-CAT-5002 line 2, GRN-CAT-2002 line 2.

Purchase order PO-CAT-1002 line 2 ordered 25 kg of ING-03. Goods-received note GRN-CAT-2002 records 21 kg delivered. Invoice INV-CAT-5002 line 2 bills 25 kg, a variance of 4 kg and 9.68 CAD net.

Assertion: `invoicedQuantity - deliveredQuantity == 4`.

### 3. Two different invoices carry the same invoice number

Affects: INV-CAT-5002 dated 2026-08-17, INV-CAT-5002 dated 2026-08-14.

Invoice number INV-CAT-5002 is used twice: once dated 2026-08-14 with 3 lines totalling 208.13 CAD, and again dated 2026-08-17 with 2 lines totalling 144.60 CAD. Both are listed on statement STM-CAT-6002 and both are included in its closing balance.

Assertion: `count(invoices where invoiceNumber == that number) == 2`.

### 4. Credit note credits more units than the goods-received note records returned

Affects: CN-CAT-7002, GRN-CAT-2004.

Goods-received note GRN-CAT-2004 line 3 records 3 each returned. Credit note CN-CAT-7002 credits 5 each at 1.14 CAD. The 2 each difference overstates the credit by 2.28 CAD net and 2.39 CAD including GST.

Assertion: `creditedQuantity - returnedQuantity == 2`.

### 5. Statement ageing buckets do not sum to the closing balance

Affects: STM-CAT-6002.

Statement STM-CAT-6002 for supplier SUP-02 declares ageing buckets of 616.96 current, 156.10 at 31-60 days, 124.88 at 61-90 days and 93.66 over 90 days, summing to 991.60 CAD. The closing balance on the same document is 981.44 CAD. The difference of 10.16 CAD is exactly credit note CN-CAT-7001, which appears in the transaction lines but was never applied to the ageing analysis.

Assertion: `sum(declaredAgeing) - closingBalance == creditNote.grossTotal`.

### 6. EDI 810 SE segment count disagrees with the actual segment count

Affects: edi-810-invalid-segment-count.edi.

The SE segment of the invalid interchange declares a segment count two lower than the number of segments actually present between ST and SE inclusive. Every other segment, the ISA/GS/GE/IEA control numbers and the TDS total are unchanged from edi-810-invoice.edi, so a parser that checks only syntax accepts the file.

Assertion: `SE01 != count(segments from ST to SE inclusive)`.

## What agrees, and is worth checking

- Every purchase order line price equals the price-list row for that supplier, item and effective date. The four effective dates are the four order dates.
- `three-way-match.csv` joins all 14 invoice lines to their purchase order and goods-received lines. 9 rows match; 5 do not, and the `match_result` and `note` columns say where.
- Statement STM-CAT-6001 is arithmetically clean: its running balance closes at 1003.36 and its four ageing buckets sum to exactly that.
- The remittance is deliberately not equal to the statements: 1088.50 paid against 1984.80 owed across both statements. The identity that does hold is remittance total plus withheld total (896.30) equals the statements total. Every document on both statements appears exactly once on the remittance, paid or withheld with a reason.
- The UBL 2.1 invoice, the Factur-X CII invoice and the embedded copy inside `invoice-facturx.pdf` all describe invoice INV-CAT-5001 and carry the same line values, tax total and payable amount.

## Traps that are modelling, not defects

- **Case pack versus each.** `price-list.csv` carries both `list_unit_price` and `price_per_case`. Every quantity in every document in this set is in the stock unit (`uom`), never in cases. Reading `price_per_case` as the unit price turns the correct purchase order net of 952.38 into 10639.32.
- **Contract tier.** `contract_tier_unit_price` applies at 250 units and no line in this set reaches that break, so the tier never applies. Applying it unconditionally gives 914.27 instead of 952.38.
- **Two legitimate tax conventions.** Every invoice here states its tax total as the sum of its line tax amounts. `expected-results.json` also publishes the document-level calculation `round(net x 0.05, 2)` for each invoice, so you can see where the two conventions diverge on their own, separately from the deliberate one-cent defect.
- **Prior-period opening items.** The `OPN-` documents on both statements predate the eight-week purchase window in the business kit, so they appear on the statements and the remittance but not in `purchase-orders.csv`. They are itemised rather than summarised as a brought-forward figure so the ageing arithmetic stays checkable.

## Formats and byte-level details

| File | Format | Notes |
| --- | --- | --- |
| `price-list.csv` | CSV | Comma delimited, UTF-8 with no byte-order mark, LF line endings, decimal point. 48 data rows and 15 columns. |
| `price-list-semicolon-decimal-comma.csv` | CSV | Semicolon delimited, UTF-8 with a byte-order mark, CRLF line endings, decimal comma on the three price columns. Same rows, columns and values as price-list.csv. |
| `purchase-orders.csv` | CSV | Comma delimited, UTF-8, LF. One row per order line; the three order-total columns repeat on every line of their order. |
| `goods-received.csv` | CSV | Comma delimited, UTF-8, LF. Separate po_ingredient_id and delivered_ingredient_id columns, and a free-text note column at the end of each row. |
| `invoices.csv` | CSV | Comma delimited, UTF-8, LF. Five invoice documents but four distinct invoice numbers, so invoice_number alone is not a key. |
| `credit-notes.csv` | CSV | Comma delimited, UTF-8, LF. The reason column is free text and names the goods-received note each credit answers. |
| `statements.csv` | CSV | Comma delimited, UTF-8, LF. The four declared ageing figures and the declared closing balance repeat on every row of their statement. |
| `remittance-advice.csv` | CSV | Comma delimited, UTF-8, LF. settled_amount is 0.00 on every withheld line, and the remittance total repeats on every row. |
| `three-way-match.csv` | CSV | Comma delimited, UTF-8, LF. quantity_variance is a signed integer; price_variance and value_variance are signed two-place decimals. |
| `procure-to-pay.json` | JSON | UTF-8, LF, two-space indent, trailing newline. Money is quoted, never a JSON number, so a parser cannot silently turn it into a binary float. |
| `expected-results.json` | JSON | UTF-8, LF, two-space indent. Every monetary value is a two-place decimal string. |
| `procure-to-pay.xlsx` | XLSX | Office Open XML with 9 sheets and frozen header rows. Container entry timestamps are fixed at 2026-09-08 so repeat builds are byte-identical. |
| `purchase-order.pdf` | PDF | Letter 612x792 points. Text is selectable Helvetica rather than an image, so an extractor reads the line table directly. |
| `goods-received-note.pdf` | PDF | Letter 612x792 points, selectable text. Ordered, delivered, returned and accepted quantities are separate columns. |
| `invoice.pdf` | PDF | Letter 612x792 points, selectable text. The stated tax total equals the sum of the line tax amounts on this document. |
| `invoice-duplicate-number.pdf` | PDF | Letter 612x792 points, selectable text. Internally consistent on its own; only the invoice number collides with another file in this folder. |
| `credit-note.pdf` | PDF | Letter 612x792 points, selectable text. Amounts are positive on the document; the credit is signed negative in statements.csv and remittance-advice.csv. |
| `statement-of-account.pdf` | PDF | Letter 612x792 points, selectable text. The running balance column recomputes from the debit and credit columns on every row. |
| `remittance-advice.pdf` | PDF | Letter 612x792 points, selectable text. The paid column is 0.00 on withheld lines, so the column total is the payment and not the exposure. |
| `invoice-ubl.xml` | XML | UTF-8, LF, no byte-order mark, two-space indent. The default namespace is the UBL Invoice-2 schema, with cac and cbc prefixes bound on the root element. |
| `credit-note-ubl.xml` | XML | UTF-8, LF, no byte-order mark. The default namespace is the UBL CreditNote-2 schema; the credited quantity is positive and the document type code carries the sign. |
| `invoice-facturx-cii.xml` | XML | UTF-8, LF, no byte-order mark. rsm, ram and udt namespace prefixes are bound on the root element. Byte-identical to the attachment inside invoice-facturx.pdf. |
| `invoice-facturx.pdf` | PDF | Letter 612x792 points. XMP metadata declares pdfaid:part 3 and the Factur-X BASIC conformance level; the file is not otherwise validated as PDF/A-3. |
| `edi-850-purchase-order.edi` | EDI | ASCII. Element separator '*', sub-element separator '>', segment terminator '~' followed by CRLF. The CRLF after every terminator is deliberate and is part of the fixture. |
| `edi-856-despatch-advice.edi` | EDI | ASCII. Segment terminator '~' followed by CRLF. HL segments are numbered from 1 and each item HL names its parent order HL. |
| `edi-810-invoice.edi` | EDI | ASCII. Segment terminator '~' followed by CRLF. TDS carries an implied decimal: divide by 100 to get the amount. |
| `edi-810-invalid-segment-count.edi` | EDI | ASCII. Segment terminator '~' followed by CRLF. The two 810 files in this folder differ in exactly one element, SE01. |
| `README.md` | MD | UTF-8, LF, trailing newline. Names every file above and states each deliberate disagreement with its exact numbers. |

## Scope

- Fictional suppliers, buyer, addresses, tax registration numbers and bank references. The GLN and GTIN values are 13 digits of the right shape and are not registered numbers; their check digits are not computed.
- A flat 5 percent GST is applied to every line so the arithmetic is checkable end to end. Real Canadian basic-grocery zero-rating and province-specific rates are out of scope for this fixture and must not be inferred from it.
- This is document-processing test data. It is not accounting, tax or purchasing advice, and the suppliers, prices and terms are not real offers.
