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

+72 -1
View File
@@ -676,9 +676,80 @@ pub enum LedgerError {
InvalidPaymentAmount(Decimal),
/// Payment exceeds the remaining balance.
#[error("payment of {0} exceeds remaining balance of {1}")]
#[error(
"payment of {0} exceeds remaining balance of {1}; the difference is the excess: record it as a plain entry to a customer-deposits liability (2300) and apply it later"
)]
Overpayment(Decimal, Decimal),
/// A settlement named no documents.
#[error("a settlement needs at least one invoice or bill")]
SettlementEmpty,
/// A settlement's documents aren't all invoices or all bills.
#[error("a settlement cannot mix invoices and bills: {0} is not the same kind as the others")]
SettlementMixedKinds(String),
/// A settlement's documents don't all belong to the one party it
/// names (rules.md B-42 INVARIANT 1).
#[error("{0} is not {1}'s: a settlement covers one party only")]
SettlementPartiesDiffer(String, u64),
/// A discount needs a reason: none is stated in the document's terms,
/// or it is larger than its terms allow, or it is taken after the
/// discount date (rules.md B-42 INVARIANT 2).
#[error(
"{doc}: the discount of {discount} needs a reason ({because}); pass discount_reason with the payment"
)]
DiscountNeedsReason {
/// The document.
doc: String,
/// The discount taken.
discount: Decimal,
/// Why the terms don't allow it as a matter of course.
because: String,
},
/// A discount on a bill whose lines include an asset or prepaid
/// account is refused: apportioning it across those lines is deferred
/// (D-49).
#[error(
"{doc} has a line on {code} \"{name}\" (an asset or prepaid account), so a discount on it cannot be apportioned (D-49): record the bill net of the discount, or reclassify by an entry Dr 6995 Purchase Discounts / Cr {code}"
)]
DiscountOnAssetBill {
/// The bill.
doc: String,
/// The asset line's account code.
code: String,
/// Its name.
name: String,
},
/// A settlement names the same document twice: each document takes one
/// application (its cash and its discount), so its bound is one bound.
#[error("{0} appears twice in one settlement: name it once, with all its cash and discount")]
SettlementDuplicateDocument(String),
/// A settlement's discount reason is not one line of at most
/// [`crate::arap::MAX_REFERENCE_CHARS`] characters.
#[error("a discount's reason is one line of at most {0} characters")]
InvalidDiscountReason(usize),
/// A processor's fee is a customer payment's only: a bill is paid from
/// the company's own money, with no processor taking a cut.
#[error("a processor's fee belongs on a customer payment, not on paying a bill")]
FeeOnBill,
/// A fee's amount or account the books can't take: the message says
/// why.
#[error("{0}")]
InvalidFee(String),
/// A settlement's figures can't be exact: the cash and discounts
/// don't cover the applications, or the fee leaves a negative deposit
/// (rules.md B-42 INVARIANT 1).
#[error("{0}")]
InvalidSettlement(String),
/// A payment's details were refused (its reference): the message says
/// why.
#[error("{0}")]