feat(arap): record an invoice's sends as invoice.sent (P4b S5)

The books never send invoices (A-4), but they must remember that one
went out: who sent it, how, to whom, when, whether it was the original
or a reminder, and the channel's own reference. That record is a fact
of the document's life, not a figure: a send moves no money, so it is
one journal line (`invoice.sent`) and no entry, and every derived
figure (outstanding, aging, DocState) is exactly as it was.

`Ledger::mark_sent` refuses a send on a void invoice (it should never
have reached the customer), a send dated before the invoice, no
recipients, and any recipient, reference or note that would put a full
TIN or control characters into the journal. Open, partial, paid and
written-off invoices can be sent (a copy for the customer's records, a
reminder).

The event is `documents_v2`'s (D-52): an older version refuses the
type rather than dropping the record. Replay restores it and the
projection keeps it in schema 16's `invoice_sends` table, so live,
replay and the viewer agree. The new old-books fixture `p4_s5_before`
was written by eeee9658 and opens to the same figures; a hand-written
send in `books_written_under_older_rules_still_open` proves an older
journal with the new line still opens.

Tests: mark_sent_waits_for_documents_v2, sends_replay_and_project,
a_send_changes_no_figure, mark_sent_on_void_refused, Op::MarkSent in
status_is_a_function_of_journal_and_date,
an_older_version_stops_at_invoice_sent.
This commit is contained in:
swaits committed 2026-10-27 13:24:10 -06:00
1 parent ed637f0e27
commit d1fb512e4b
16 files changed
+979 -5

No files matched your search

+3 -1
View File
@@ -27,7 +27,8 @@ const TYPES: &[&str] = &[
"asset.registered", "attachment.withdrawn", "bill.created", "bill.paid", "class.added",
"customer.added", "document.filed", "entry.created", "entry.line_added",
"entry.line_class_set", "entry.posted", "entry.reversed", "entry.vendor_set",
"fx.rate_set", "invoice.created", "invoice.payment_received", "journal.tail_repaired",
"fx.rate_set", "invoice.created", "invoice.payment_received", "invoice.sent",
"journal.tail_repaired",
"line.cleared", "payroll.run", "period.closed", "period.created", "receipt.attached",
"receipt.linked", "receipt.unlinked", "recurring.generated", "recurring.registered",
"schedule.posted", "schedule.registered", "vendor.1099_set", "vendor.added",
@@ -61,6 +62,7 @@ const FIELDS: &[&str] = &[
"rule", "contributor_adjusted_basis", "fmv_at_contribution", "sec704c_method",
"tax_basis_723", "depreciable_basis", "built_in_gain_704c", "on", "convention",
"status", "party_id", "started_on", "ended_on", "links", "notes", "projects",
"via", "by", "to", "reference", "note",
];
/// Journal features a `journal.format_set` may declare.