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:
commit
335a3eaa7e
47 files changed
+2883
-74
No files matched your search
@@ -336,3 +336,57 @@ fn an_overdue_invoice_with_an_old_send_is_asked_to_remind() {
|
||||
assert!(item.detail.contains("14 days overdue"), "{}", item.detail);
|
||||
assert!(item.prompt.contains("books.mark_sent"), "{}", item.prompt);
|
||||
}
|
||||
|
||||
/// A written-off invoice (plan P4b S7): $500 issued, written off whole for
|
||||
/// a bad debt with its evidence, so it reads Written off owing nothing.
|
||||
fn write_off_books(dir: &TempDir) -> (u64, u64) {
|
||||
let mut s = Store::create(&dir.0.join("books-data"), Currency::USD).unwrap();
|
||||
s.add_account("1000", "Checking", AccountType::Asset, None, false)
|
||||
.unwrap();
|
||||
s.add_account("3000", "Capital", AccountType::Equity, None, false)
|
||||
.unwrap();
|
||||
let client = s.add_customer("Harbor Client").unwrap();
|
||||
let inv = s
|
||||
.create_invoice(
|
||||
client,
|
||||
d(2026, 2, 1),
|
||||
d(2026, 3, 1),
|
||||
vec![line("Consulting", 50_000)],
|
||||
)
|
||||
.unwrap();
|
||||
let (entry, _) = s
|
||||
.write_off_invoice(
|
||||
inv,
|
||||
d(2026, 3, 10),
|
||||
Decimal::new(50_000, 2),
|
||||
"collection exhausted",
|
||||
Some(ledger_store::WriteOffEvidence::File {
|
||||
file_name: "collection-letter.pdf",
|
||||
mime_type: "application/pdf",
|
||||
bytes: b"%PDF-1.4\nFinal demand\n%%EOF\n",
|
||||
description: Some("Collection attempts"),
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
(inv, entry)
|
||||
}
|
||||
|
||||
/// The write-off is on its invoice's timeline, reads "Written off", and
|
||||
/// opens its entry.
|
||||
#[test]
|
||||
fn a_write_off_shows_on_the_timeline() {
|
||||
let dir = TempDir::new("write-off");
|
||||
let (inv, entry) = write_off_books(&dir);
|
||||
let mut h = open(&dir, Route::Invoice(inv));
|
||||
shot(&mut h, "invoice-written-off");
|
||||
assert!(
|
||||
h.query_by_label_contains("Written off, entry #").is_some(),
|
||||
"the timeline shows the write-off"
|
||||
);
|
||||
// The status chip reads its label ("Written off"), not "late".
|
||||
assert!(h.query_by_label("Written off").is_some());
|
||||
h.get_by_label_contains(&format!("Written off, entry #{entry},"))
|
||||
.click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state_mut().route(), &Route::Entry(entry));
|
||||
}
|
||||
Reference in new issue
Block a user