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:
1 parent
62f2fb058e
commit
4e71cf6320
13 files changed
+1005
-7
No files matched your search
@@ -703,6 +703,28 @@ fn books_p2_served_cut_over_whole() {
|
||||
// A project by name alone is a plain class, which P2 reads.
|
||||
a.call("books.add_project", json!({"name": "Lighthouse"}))
|
||||
.unwrap();
|
||||
// A receipt not yet on an entry (P2 reads the unlink; entry 7's bank
|
||||
// fee is under the receipt policy's threshold, so taking its last
|
||||
// receipt is allowed), and a legacy-shape invoice (no new field, so no
|
||||
// feature): the evidence and the document P4b S7's write-off needs
|
||||
// after the go-ahead.
|
||||
a.call(
|
||||
"books.unlink_receipt",
|
||||
json!({"attachment_id": 7, "reason": "filed against the wrong entry"}),
|
||||
)
|
||||
.unwrap();
|
||||
let customer = a
|
||||
.call("books.add_customer", json!({"name": "Ridgeway Co"}))
|
||||
.unwrap()["id"]
|
||||
.clone();
|
||||
let unpaid = a
|
||||
.call(
|
||||
"books.create_invoice",
|
||||
json!({"party_id": customer, "date": "2026-01-10", "due_date": "2026-02-09",
|
||||
"lines": [{"description": "Survey", "amount": "300.00"}]}),
|
||||
)
|
||||
.unwrap()["id"]
|
||||
.clone();
|
||||
let waiting = journal(&dir);
|
||||
let refused: Vec<(&str, Value)> = vec![
|
||||
// An invoice's or bill's line naming its project
|
||||
@@ -743,6 +765,14 @@ fn books_p2_served_cut_over_whole() {
|
||||
"books.close_period",
|
||||
json!({"period_id": q2, "confirmed": true}),
|
||||
),
|
||||
// A bad-debt write-off (documents_v2's new event, P4b S7): its
|
||||
// evidence filed and linked in the same call.
|
||||
(
|
||||
"books.write_off_invoice",
|
||||
json!({"invoice": unpaid, "date": "2026-02-20",
|
||||
"reason": "collection exhausted",
|
||||
"existing_attachment_id": 7, "confirmed": true}),
|
||||
),
|
||||
];
|
||||
for (tool, args) in &refused {
|
||||
let e = a.call(tool, args.clone()).unwrap_err();
|
||||
|
||||
Reference in new issue
Block a user