merge: P4b S6, paying on terms (branch S6, 6c4480cd)

The third of the paused branches: Ledger::settle with discounts and the
processor's fee (kernel::settle, Kani settle_is_exact, Lean
settle_posts_a_document_step), books.receive_payment and books.pay_bill
with applications, books.bills_due, the agings' detail and tie-out, the
aging exports and the CPA package's aging CSVs, the handlers in
tools/settlements.rs. Twelve files conflicted with the S4.4 and S5
merges, resolved as plan 8.0.2 says: both routers; both test appends
(the shared match arm in arap_standing.rs spliced into both tests; the
shared `.unwrap()` in store.rs); the scope table at 124; one
invoices-and-bills handbook (S5's send step 1 with S6's steps 2, 3, 5
and procedures) registered once; docs/mcp-interface.md with S4.4's
create docs and S6's payment docs; S5's law renumbered 35 and S6's 36
(S1's is 34); the parity rows and sections of both. S6's wire tests
create their bills as S4.4's contract now requires (confirmed, the
vendor's number, a line account, the vendor's invoice uploaded) and live
in their own module beside S4.4's tests.

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:26:40 -06:00
commit be58059830
45 files changed
+3949 -520

No files matched your search

+69 -1
View File
@@ -1875,6 +1875,8 @@ fn books_written_under_older_rules_still_open() {
(4, "4000", "Revenue", "Revenue"),
(5, "2000", "Accounts Payable", "Liability"),
(6, "6500", "Supplies", "Expense"),
(7, "4100", "Refunds and Allowances", "Revenue"),
(8, "1020", "Bank", "Asset"),
] {
w.append(
"account.added",
@@ -1966,13 +1968,55 @@ fn books_written_under_older_rules_still_open() {
"to": ["billing@fernhill.example"], "kind": "original", "reference": "msg-1"}),
)
.unwrap();
// P4b S6's new shape, hand-written as this version writes it: a
// payment event naming its line and the discount line it also tags.
// This reader tags both; the version before it would drop the discount
// and read the invoice paid above its total.
w.append(
"entry.created",
json!({"id": 4, "date": "2026-04-05", "description": "Payment on invoice 1"}),
)
.unwrap();
for (account, side, amount) in [
(3, "credit", "98.99"),
(3, "credit", "1.00"),
(7, "debit", "1.00"),
(8, "debit", "98.99"),
] {
w.append(
"entry.line_added",
json!({"id": 4, "account": account, "side": side, "amount": amount,
"currency": "USD"}),
)
.unwrap();
}
w.append(
"entry.posted",
json!({"id": 4, "date": "2026-04-05", "description": "Payment on invoice 1",
"reversal_of": null,
"lines": [
{"account": 3, "side": "credit", "amount": "98.99", "currency": "USD"},
{"account": 3, "side": "credit", "amount": "1.00", "currency": "USD"},
{"account": 7, "side": "debit", "amount": "1.00", "currency": "USD"},
{"account": 8, "side": "debit", "amount": "98.99", "currency": "USD"}
]}),
)
.unwrap();
w.append(
"invoice.payment_received",
json!({"invoice_id": 1, "date": "2026-04-05", "amount": "98.99",
"cash_account": 8, "entry_id": 4, "line": 0,
"discount": "1.00", "discount_line": 1,
"discount_reason": "the customer paid the rest late"}),
)
.unwrap();
drop(w);
// Opening rebuilds from the journal; it must accept all of it.
let _ = std::fs::remove_file(dir.join(ledger_store::db_file()));
let store = Store::open(&dir).expect("older books still open");
store.verify().unwrap();
assert_eq!(store.journal_format().format, 1);
assert_eq!(store.ledger().chart().len(), 6);
assert_eq!(store.ledger().chart().len(), 8);
assert_eq!(store.ledger().list_periods().len(), 2);
let lines = store.ledger().get_entry(1).unwrap().lines();
assert_eq!(lines.len(), 2);
@@ -2040,6 +2084,30 @@ fn books_written_under_older_rules_still_open() {
assert_eq!(sends[0].to, ["billing@fernhill.example"]);
assert_eq!(sends[0].kind, ledger_core::arap::SendKind::Original);
assert_eq!(sends[0].reference.as_deref(), Some("msg-1"));
// The S6 shape: the invoice reads paid and discounted, and replay
// tagged the lines the event names.
let st = store
.ledger()
.invoice_state(1, NaiveDate::from_ymd_opt(2026, 12, 31).unwrap())
.unwrap()
.unwrap();
assert_eq!(
(st.status, st.paid, st.discounted, st.outstanding),
(
ledger_core::DocStatus::Paid,
"98.99".parse::<Decimal>().unwrap(),
"1.00".parse::<Decimal>().unwrap(),
Decimal::ZERO
)
);
assert_eq!(
store.ledger().line_doc(4, 0).unwrap().movement,
ledger_core::Movement::Payment
);
assert_eq!(
store.ledger().line_doc(4, 1).unwrap().movement,
ledger_core::Movement::Discount
);
}
/// The books an earlier wanderbooks wrote, one directory each under