merge: P4b S7, bad debts (branch S7, 4b4fecb9)

The fourth of the paused branches: Ledger::write_off and the bad_debts
control role, invoice.written_off with its evidence filed in the same
store call, books.write_off_invoice, the write-off on the timeline and
in Needs attention, Lean writeOff_within_bounds, rules.md B-41 and the
bookspec examples. Twenty-eight files conflicted with the S4.4, S5 and
S6 merges; resolved as plan 8.0.2 says, keeping both sides' appends with
their seams restored, and: the scope table at 125; S7's law numbered 37;
B-41 placed before B-42; the handbook's step 4 and recovery procedures
folded into the one invoices-and-bills section; S7's wire tests in their
own module with the confirm S4.4's contract needs; S7's copy of the
payment steps dropped (S6 replaced them with Ledger::settle) and its
write-off journaled through S6's journal_auto_lines; in the old-books
fixture S7's hand-written write-off moved to a second invoice, since
S6's hand-written payment settles invoice 1 in full.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-28 21:47:33 -06:00
commit 335a3eaa7e
47 files changed
+2883 -74

No files matched your search

+48
View File
@@ -418,6 +418,54 @@ pub fn invoice_sent_of(line: &JournalLine) -> Result<(u64, Sent), StoreError> {
))
}
/// A bad-debt write-off as journaled (plan P4b S7; rules.md B-41):
/// `invoice.written_off {invoice_id, entry_id, date, amount, reason}`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WrittenOff {
/// The invoice written off.
pub invoice_id: u64,
/// The entry that posted it (already rebuilt from its own events).
pub entry_id: u64,
/// The write-off's day.
pub date: NaiveDate,
/// What was written off.
pub amount: rust_decimal::Decimal,
/// Why (it has no resolution: the evidence is the receipt).
pub reason: String,
}
/// `invoice.written_off {invoice_id, entry_id, date, amount, reason}`.
pub fn invoice_written_off(
invoice_id: u64,
entry_id: u64,
date: NaiveDate,
amount: rust_decimal::Decimal,
reason: &str,
) -> Value {
json!({
"invoice_id": invoice_id,
"entry_id": entry_id,
"date": date.to_string(),
"amount": amount.to_string(),
"reason": reason,
})
}
/// Read an `invoice.written_off` payload.
pub fn invoice_written_off_of(p: &Value) -> Result<WrittenOff, StoreError> {
const E: &str = "invoice.written_off";
let amount = str_of(p, E, "amount")?
.parse()
.map_err(|_| corrupt(E, "amount"))?;
Ok(WrittenOff {
invoice_id: u64_of(p, E, "invoice_id")?,
entry_id: u64_of(p, E, "entry_id")?,
date: date_of(&str_of(p, E, "date")?, E, "date")?,
amount,
reason: str_of(p, E, "reason")?,
})
}
#[cfg(test)]
mod tests {
use super::*;