feat(store): journal a write-off with its evidence (P4b S7)

A bad debt is deducted on evidence it is worthless (Treas. Reg.
§1.166-2), so the store will not journal one without it:
Store::write_off_invoice takes the evidence as bytes to file or as an
attachment already filed, and files it in the same call — a
receipt.attached on the write-off's entry and an attachment.linked to
the invoice (rules.md B-41). With no evidence it refuses, journaling
nothing.

The entry (Dr 6990 Bad Debt Expense / Cr 1200) and its evidence are
journaled after the engine's whole check and after the feature gate: the
new event type invoice.written_off needs documents_v2, so an older
version stops at the declaration rather than reading a written-off
invoice as still owed (B-27, D-52). Its payload is the invoice, the
entry, the day, the amount and the reason; replay tags the receivable
line WriteOff, and the projection keeps no written-off figure — it is
the tagged lines' sum (B-38).

`meta::invoice_written_off_of` reads and writes the payload; the replay
fuzzer draws the type.

8.0.6: (1) `books_written_under_older_rules_still_open` hand-writes the
new event and its entry through JournalWriter and still opens, with the
figures tailing; (2) a new fixture old-books/p4_s7_before was written by
main 4cad2cfc (the version before this unit) through the old-books-writer
harness — a numbered invoice with terms and snapshots, a partial payment,
a voided invoice and a paid bill — with its expected.json; this version
opens it and reports the same figures, the viewer included; (3) the
cut-over entry is in format_upgrade.rs's books_p2_served_cut_over_whole;
(4) documents_v2_is_its_own_feature is green and the event gate's arms
pass; (5) no figure moves: no golden changed (the previous version's
expected.json is the comparison); (6) merge does not need the owner's
go-ahead; the live books refuse the new write until he gives it.

Tests: write_off_waits_for_documents_v2, write_off_replays_and_projects,
write_off_without_evidence_is_refused; arap_props.rs gains WriteOff.
This commit is contained in:
swaits committed 2026-10-27 12:44:45 -06:00
1 parent 62f2fb058e
commit 4e71cf6320
13 files changed
+1005 -7

No files matched your search

@@ -0,0 +1,431 @@
{
"assets": [],
"bills": [
{
"amount_paid": "80.00",
"date": "2026-02-07",
"due_date": "2026-03-09",
"entry_id": 6,
"id": 1,
"lines": [
{
"amount": "80.00",
"description": "Paper"
}
],
"memo": "A4 paper",
"received_on": "2026-02-08",
"status": "Paid",
"terms": {
"kind": {
"net": 30
},
"text": "Net 30"
},
"vendor_id": 1,
"vendor_ref": "TOBIN-77"
}
],
"entries": [
{
"date": "2026-01-01",
"description": "Opening capital",
"id": 1,
"lines": [
{
"account": 1,
"amount": {
"amount": "1000.00",
"currency": [
85,
83,
68
]
},
"side": "Debit"
},
{
"account": 2,
"amount": {
"amount": "1000.00",
"currency": [
85,
83,
68
]
},
"side": "Credit"
}
],
"reversal_of": null,
"status": "Posted"
},
{
"date": "2026-02-01",
"description": "Invoice 1",
"id": 2,
"lines": [
{
"account": 3,
"amount": {
"amount": "500.00",
"currency": [
85,
83,
68
]
},
"side": "Debit"
},
{
"account": 4,
"amount": {
"amount": "500.00",
"currency": [
85,
83,
68
]
},
"side": "Credit"
}
],
"reversal_of": null,
"status": "Posted"
},
{
"date": "2026-02-10",
"description": "Payment on invoice 1",
"id": 3,
"lines": [
{
"account": 1,
"amount": {
"amount": "200.00",
"currency": [
85,
83,
68
]
},
"side": "Debit"
},
{
"account": 3,
"amount": {
"amount": "200.00",
"currency": [
85,
83,
68
]
},
"side": "Credit"
}
],
"reversal_of": null,
"status": "Posted"
},
{
"date": "2026-02-05",
"description": "Invoice 2",
"id": 4,
"lines": [
{
"account": 3,
"amount": {
"amount": "40.00",
"currency": [
85,
83,
68
]
},
"side": "Debit"
},
{
"account": 4,
"amount": {
"amount": "40.00",
"currency": [
85,
83,
68
]
},
"side": "Credit"
}
],
"reversal_of": null,
"status": "Posted"
},
{
"date": "2026-02-06",
"description": "Void invoice 2: wrong customer",
"id": 5,
"lines": [
{
"account": 3,
"amount": {
"amount": "40.00",
"currency": [
85,
83,
68
]
},
"side": "Credit"
},
{
"account": 4,
"amount": {
"amount": "40.00",
"currency": [
85,
83,
68
]
},
"side": "Debit"
}
],
"reversal_of": 4,
"status": "Posted"
},
{
"date": "2026-02-07",
"description": "Bill 1",
"id": 6,
"lines": [
{
"account": 5,
"amount": {
"amount": "80.00",
"currency": [
85,
83,
68
]
},
"side": "Debit"
},
{
"account": 6,
"amount": {
"amount": "80.00",
"currency": [
85,
83,
68
]
},
"side": "Credit"
}
],
"reversal_of": null,
"status": "Posted"
},
{
"date": "2026-03-01",
"description": "Payment on bill 1",
"id": 7,
"lines": [
{
"account": 6,
"amount": {
"amount": "80.00",
"currency": [
85,
83,
68
]
},
"side": "Debit"
},
{
"account": 1,
"amount": {
"amount": "80.00",
"currency": [
85,
83,
68
]
},
"side": "Credit"
}
],
"reversal_of": null,
"status": "Posted"
}
],
"entry_parties": [
[
2,
[
{
"party_id": 1,
"share": "derived"
}
]
],
[
3,
[
{
"party_id": 1,
"share": "derived"
}
]
],
[
4,
[
{
"party_id": 1,
"share": "derived"
}
]
],
[
5,
[
{
"party_id": 1,
"share": "derived"
}
]
],
[
6,
[
{
"party_id": 2,
"share": "derived"
}
]
],
[
7,
[
{
"party_id": 2,
"share": "derived"
}
]
]
],
"invoices": [
{
"amount_paid": "200.00",
"bill_to": {
"address": "9 Ridgeway Rd",
"email": "ap@fernhill.example",
"name": "Fernhill Labs"
},
"customer_id": 1,
"customer_ref": "PO-7",
"date": "2026-02-01",
"due_date": "2026-03-03",
"entry_id": 2,
"id": 1,
"lines": [
{
"amount": "500.00",
"description": "Design"
}
],
"memo": "Thanks",
"number": "INV-0001",
"seller": {
"address": "1 Main St",
"name": "Wanderbooks Test Co"
},
"status": "Partial",
"terms": {
"discount": {
"days": 10,
"percent_bp": 200
},
"kind": {
"net": 30
},
"text": "2/10 net 30"
}
},
{
"amount_paid": "0",
"customer_id": 1,
"date": "2026-02-05",
"due_date": "2026-03-07",
"entry_id": 4,
"id": 2,
"lines": [
{
"amount": "40.00",
"description": "Setup"
}
],
"status": "Open"
}
],
"payroll_runs": [],
"periods": [],
"recurring": [],
"schedules": [],
"snapshot": {
"Ok": {
"balance_sheet": {
"assets": "1420",
"balanced": true,
"current_earnings": "420",
"equity": "1420",
"liabilities": "0"
},
"profit_and_loss": {
"expenses": "80",
"net_income": "420",
"revenue": "500"
},
"trial_balance": {
"lines": [
{
"code": "1000",
"credit": "0",
"debit": "1120",
"name": "Checking"
},
{
"code": "1200",
"credit": "0",
"debit": "300",
"name": "Accounts Receivable"
},
{
"code": "3000",
"credit": "1000",
"debit": "0",
"name": "Capital"
},
{
"code": "4000",
"credit": "500",
"debit": "0",
"name": "Revenue"
},
{
"code": "6999",
"credit": "0",
"debit": "80",
"name": "General Expenses"
}
],
"total_credits": "1500",
"total_debits": "1500"
}
}
},
"verify": {
"Ok": null
}
}
@@ -0,0 +1,40 @@
{"payload":{"base_currency":"USD"},"prev":"0000000000000000000000000000000000000000000000000000000000000000","seq":1,"ts":"2026-10-27T17:42:15Z","type":"store.created"}
{"payload":{"features":["account_renamed","asset_basis","depreciation_catch_up","disposal_before_service","document_line_projects","document_standing","documents_v2","members_cumulative","month_reposting","party_creditors","payment_instruments","projects","require_tax_category","schedule_convention","tax_categories_gain_exempt","vendor_1099_decisions"],"format":2},"prev":"853fa1a23a8e98ed4afffa7f01cb1c0bb458fd7c2218706cdb53a579308a6003","seq":2,"ts":"2026-10-27T17:42:15Z","type":"journal.format_set"}
{"payload":{"account_type":"Asset","code":"1000","currency":"USD","id":1,"name":"Checking","parent":null,"placeholder":false},"prev":"3dea41067629e227334a7e6b050ac103ca121872f2d912fb1d472eb903ae7f59","seq":3,"ts":"2026-10-27T17:42:15Z","type":"account.added"}
{"payload":{"account_type":"Equity","code":"3000","currency":"USD","id":2,"name":"Capital","parent":null,"placeholder":false},"prev":"ee5de65058a81e155528a5258852611d86c0e8ef41e2e97b40644685eb3250df","seq":4,"ts":"2026-10-27T17:42:15Z","type":"account.added"}
{"payload":{"date":"2026-01-01","description":"Opening capital","id":1},"prev":"26cb19b6f713e2269e2e16d040128daa6b69b354c70d51f0202185e84c8f5450","seq":5,"ts":"2026-10-27T17:42:15Z","type":"entry.created"}
{"payload":{"account":1,"amount":"1000.00","currency":"USD","id":1,"side":"debit"},"prev":"597afb2305dfcd7a9c9ba11b22996a53a29d71f11895405c29f36d9d8eba26aa","seq":6,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"account":2,"amount":"1000.00","currency":"USD","id":1,"side":"credit"},"prev":"7ebce83af99c2a0be9b4626d5d4546c3392d11b96bebb9276597c0b1944cada2","seq":7,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"date":"2026-01-01","description":"Opening capital","id":1,"lines":[{"account":1,"amount":"1000.00","currency":"USD","side":"debit"},{"account":2,"amount":"1000.00","currency":"USD","side":"credit"}],"reversal_of":null},"prev":"1b4733efe3583fe881e92aabc2b1a8f319911a779e8d986edd427d6fc392b619","seq":8,"ts":"2026-10-27T17:42:15Z","type":"entry.posted"}
{"payload":{"id":1,"name":"Fernhill Labs"},"prev":"17ef917991a9c0f46e6a28b108fc3cc181a61a7c0bd5827f123cceea993c19ad","seq":9,"ts":"2026-10-27T17:42:15Z","type":"customer.added"}
{"payload":{"id":1,"name":"Tobin Harlow Supply"},"prev":"6f1d020a91130ceedf8830af4f048b42e1c243636be9a70a7f1abf0568147836","seq":10,"ts":"2026-10-27T17:42:15Z","type":"vendor.added"}
{"payload":{"account_type":"Asset","code":"1200","currency":"USD","id":3,"name":"Accounts Receivable","parent":null,"placeholder":false},"prev":"fb7aeacef5825917e93fe9dea60e638c60207656b3e6bcfa79ea269eedd67eb3","seq":11,"ts":"2026-10-27T17:42:15Z","type":"account.added"}
{"payload":{"account_type":"Revenue","code":"4000","currency":"USD","id":4,"name":"Revenue","parent":null,"placeholder":false},"prev":"a6c35a0e3fe245e8bf455163a9599eca70df8295daa9f899ca5b1168815d42dc","seq":12,"ts":"2026-10-27T17:42:15Z","type":"account.added"}
{"payload":{"date":"2026-02-01","description":"Invoice 1","id":2},"prev":"0d4ed5e9db70c993de38467a67ed80dde4185eaec240b390d062bd5102b86a6c","seq":13,"ts":"2026-10-27T17:42:15Z","type":"entry.created"}
{"payload":{"account":3,"amount":"500.00","currency":"USD","id":2,"side":"debit"},"prev":"b4b54695c5cb7b92c423cb0336eef51cd493516ea1c073986d53dea2bf38dedf","seq":14,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"account":4,"amount":"500.00","currency":"USD","id":2,"side":"credit"},"prev":"79a2c8f3a4dce65d6cf6c6c13f0d7df4feeff4fa3543fa4732b64fb96c6c57c9","seq":15,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"date":"2026-02-01","description":"Invoice 1","id":2,"lines":[{"account":3,"amount":"500.00","currency":"USD","side":"debit"},{"account":4,"amount":"500.00","currency":"USD","side":"credit"}],"reversal_of":null},"prev":"a965e96d442a0ee5abb6878ca981583f4fe29da564a71217d331ec5fc642b701","seq":16,"ts":"2026-10-27T17:42:15Z","type":"entry.posted"}
{"payload":{"bill_to":{"address":"9 Ridgeway Rd","email":"ap@fernhill.example","name":"Fernhill Labs"},"customer_id":1,"customer_ref":"PO-7","date":"2026-02-01","due_date":"2026-03-03","entry_id":2,"id":1,"lines":[{"amount":"500.00","description":"Design"}],"memo":"Thanks","number":"INV-0001","seller":{"address":"1 Main St","name":"Wanderbooks Test Co"},"terms":{"discount":{"days":10,"percent_bp":200},"kind":{"net":30},"text":"2/10 net 30"}},"prev":"9d8f3a1a5a0161040cf9caf8b5710c9fd7e61d909cee685d990d9b5a309fa695","seq":17,"ts":"2026-10-27T17:42:15Z","type":"invoice.created"}
{"payload":{"date":"2026-02-10","description":"Payment on invoice 1","id":3},"prev":"5eac387cbd55eafd2637474b8958baf2e7ca7cacec73535b2310ab124e79e6a1","seq":18,"ts":"2026-10-27T17:42:15Z","type":"entry.created"}
{"payload":{"account":1,"amount":"200.00","currency":"USD","id":3,"side":"debit"},"prev":"00b6089df07024fb077049c5206d3dd3812fa662b57af1ae50d42511b34ca0d9","seq":19,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"account":3,"amount":"200.00","currency":"USD","id":3,"side":"credit"},"prev":"26e9ee32e801bedd995f28eee065e4915b654a55c8569fb3ec9327a577c81959","seq":20,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"date":"2026-02-10","description":"Payment on invoice 1","id":3,"lines":[{"account":1,"amount":"200.00","currency":"USD","side":"debit"},{"account":3,"amount":"200.00","currency":"USD","side":"credit"}],"reversal_of":null},"prev":"f9e36c8f805aeea8656182becc1b47e7947791ff6f4fe36fc469fd7aaa4354c8","seq":21,"ts":"2026-10-27T17:42:15Z","type":"entry.posted"}
{"payload":{"amount":"200.00","cash_account":1,"date":"2026-02-10","entry_id":3,"invoice_id":1},"prev":"a95f752a661405b4fe0904623b0a2b908b184af28a371f9833b9aa3232685dbb","seq":22,"ts":"2026-10-27T17:42:15Z","type":"invoice.payment_received"}
{"payload":{"date":"2026-02-05","description":"Invoice 2","id":4},"prev":"4b96e27f6bb06775b7d3f4b183ed16e5d7c07ab8dd5e202fafbb8aaa23915cc6","seq":23,"ts":"2026-10-27T17:42:15Z","type":"entry.created"}
{"payload":{"account":3,"amount":"40.00","currency":"USD","id":4,"side":"debit"},"prev":"95674fd07333f129d8289f1287190f39f6a8235bdcd9bd1ce01fa7f3f81b2068","seq":24,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"account":4,"amount":"40.00","currency":"USD","id":4,"side":"credit"},"prev":"6c7b09c9a08781eb0f424d05252b88194063b7d954d0a3eed24a7d102fc228e0","seq":25,"ts":"2026-10-27T17:42:15Z","type":"entry.line_added"}
{"payload":{"date":"2026-02-05","description":"Invoice 2","id":4,"lines":[{"account":3,"amount":"40.00","currency":"USD","side":"debit"},{"account":4,"amount":"40.00","currency":"USD","side":"credit"}],"reversal_of":null},"prev":"4aef53f197b1ad2b8287d1fbdd2cba6c5ee965421f77a889be5ba30541d4c2d9","seq":26,"ts":"2026-10-27T17:42:15Z","type":"entry.posted"}
{"payload":{"customer_id":1,"date":"2026-02-05","due_date":"2026-03-07","entry_id":4,"id":2,"lines":[{"amount":"40.00","description":"Setup"}]},"prev":"672d61478a6f83d099c7889f75880e5ce0d7a214838720536fcb2eb998ebc2d4","seq":27,"ts":"2026-10-27T17:42:15Z","type":"invoice.created"}
{"payload":{"date":"2026-02-06","description":"Void invoice 2: wrong customer","id":5,"lines":[{"account":3,"amount":"40.00","currency":"USD","side":"credit"},{"account":4,"amount":"40.00","currency":"USD","side":"debit"}],"reverses":4},"prev":"3f8b16f5949e56504750ee6ebfe1edf08a1a8afe74fc75aafe6b2bee883056be","seq":28,"ts":"2026-10-27T17:42:15Z","type":"entry.reversed"}
{"payload":{"account_type":"Expense","code":"6999","currency":"USD","id":5,"name":"General Expenses","parent":null,"placeholder":false},"prev":"57481256716e8ec09f325832cf98df9020505c58b8b2d7136f14d2f8cb4b57ea","seq":29,"ts":"2026-10-27T17:42:16Z","type":"account.added"}
{"payload":{"account_type":"Liability","code":"2000","currency":"USD","id":6,"name":"Accounts Payable","parent":null,"placeholder":false},"prev":"7e7dd770042b61627f9f6a528f38fcae1447eb6b5ad8f1ed378f851667ae51de","seq":30,"ts":"2026-10-27T17:42:16Z","type":"account.added"}
{"payload":{"date":"2026-02-07","description":"Bill 1","id":6},"prev":"f97abbb46d74293510e8b37f54a8c863287df3cb9775e8e159550508a6313160","seq":31,"ts":"2026-10-27T17:42:16Z","type":"entry.created"}
{"payload":{"account":5,"amount":"80.00","currency":"USD","id":6,"side":"debit"},"prev":"d7e394993994fd4e5a1d4017cd30b4e72e3b2ddac8d91ff86a97cbc4a517a667","seq":32,"ts":"2026-10-27T17:42:16Z","type":"entry.line_added"}
{"payload":{"account":6,"amount":"80.00","currency":"USD","id":6,"side":"credit"},"prev":"048f99094bf692587a1f3463d663d42efd5681935b9cdb8b6ad187408b23c51d","seq":33,"ts":"2026-10-27T17:42:16Z","type":"entry.line_added"}
{"payload":{"date":"2026-02-07","description":"Bill 1","id":6,"lines":[{"account":5,"amount":"80.00","currency":"USD","side":"debit"},{"account":6,"amount":"80.00","currency":"USD","side":"credit"}],"reversal_of":null},"prev":"b38c0078bb78b62f9d565bad95519c243d0374d480b7f17f8f2d0c8a24f7945c","seq":34,"ts":"2026-10-27T17:42:16Z","type":"entry.posted"}
{"payload":{"date":"2026-02-07","due_date":"2026-03-09","entry_id":6,"id":1,"lines":[{"amount":"80.00","description":"Paper"}],"memo":"A4 paper","received_on":"2026-02-08","terms":{"kind":{"net":30},"text":"Net 30"},"vendor_id":1,"vendor_ref":"TOBIN-77"},"prev":"d4aef1e2609466fda52daf6f871bf77c6f3851f58f73e0dc4a59c2b50fb8b472","seq":35,"ts":"2026-10-27T17:42:16Z","type":"bill.created"}
{"payload":{"date":"2026-03-01","description":"Payment on bill 1","id":7},"prev":"cf87cbbda32520851e8a0e341235f24a6c33d095c2b619ff558549c6a200f2ce","seq":36,"ts":"2026-10-27T17:42:16Z","type":"entry.created"}
{"payload":{"account":6,"amount":"80.00","currency":"USD","id":7,"side":"debit"},"prev":"a7df5eacd102668f821c155ebca4caaf3811ed684e89ce76963fad08b02aa60c","seq":37,"ts":"2026-10-27T17:42:16Z","type":"entry.line_added"}
{"payload":{"account":1,"amount":"80.00","currency":"USD","id":7,"side":"credit"},"prev":"2969a6d12f0b3c81edd637d436d97e91ef41197c5d353b0d8a78b9b14396fe50","seq":38,"ts":"2026-10-27T17:42:16Z","type":"entry.line_added"}
{"payload":{"date":"2026-03-01","description":"Payment on bill 1","id":7,"lines":[{"account":6,"amount":"80.00","currency":"USD","side":"debit"},{"account":1,"amount":"80.00","currency":"USD","side":"credit"}],"reversal_of":null},"prev":"fce397fd5ac8118392a5b46682afe929293c91753ac7ac3bffb9bbb67d692ef9","seq":39,"ts":"2026-10-27T17:42:16Z","type":"entry.posted"}
{"payload":{"amount":"80.00","bill_id":1,"cash_account":1,"date":"2026-03-01","entry_id":7},"prev":"a56a40dc3b3d9b1a5ff33ec8814ff8053bdd9c17f2453ece991eb930633d0e4f","seq":40,"ts":"2026-10-27T17:42:16Z","type":"bill.paid"}