feat(mcp): payments on terms, bills due, and the agings' tie-out (P4b S6)

books.receive_payment and books.pay_bill take applications, each
document's cash with an optional discount and reason, and a customer
payment may carry the processor's fee; the result names every document it
settled and what reached the account. books.bills_due lists what to pay
and the running total by due date. The agings take detail and return the
control account's tie-out, and books.export writes ar_aging/ap_aging.

The handlers live in tools/settlements.rs (the EM's split, so S4.4's
documents.rs stays free); documents.rs keeps the documents, the void and
the control check. docs/mcp-interface.md documents every changed tool and
the new one; docs/architecture.md names the group; docs/testing.md
records the tests and P12's mutation targets.

Old names (document_id, cash_account) still work; the output keeps
remaining/status for one document. A fee's processor is named in the
description, not tagged as the entry's vendor: ledger tags are entry-wide
and tagging would move the customer's party totals and 1099-NEC.

The handbook's invoices-and-bills section gains the payment, discount,
fee and bounce-replacement procedures (S4.5 can extend it). Over the wire:
one_payment_settles_two_invoices, late_discount_needs_a_reason,
a_fee_posts_gross_to_receivables_and_net_to_the_bank,
card_paid_bill_is_not_1099_nec,
multi_document_payment_counts_once_for_1099_and_party_totals,
overpayment_refused_with_the_excess,
bills_due_orders_by_due_and_discount and the_aging_export_ties_out.

Terms cannot yet be journaled on a document (S4.3) and a bill line's
account is not an MCP field (S4.4), so bill_on_terms_with_discount,
equipment_bill_payment_is_investing_cash and the discount half of
bills_due are core tests until those land; the unit's As-built block
records this.
This commit is contained in:
swaits committed 2026-10-27 12:59:40 -06:00
1 parent 78a2385440
commit 57f04dbb0c
17 files changed
+1467 -231

No files matched your search

+6 -1
View File
@@ -878,6 +878,7 @@ fn happy_path(name: &str) -> Mcp {
);
m.ok("books.ar_aging", json!({"as_of": "2026-06-30"}));
m.ok("books.ap_aging", json!({"as_of": "2026-06-30"}));
m.ok("books.bills_due", json!({"through": "2026-06-30"}));
// P4b S1: a document voided, and the control accounts' tie-out.
let dup = m.ok(
"books.create_invoice",
@@ -1176,10 +1177,14 @@ fn happy_path(name: &str) -> Mcp {
("csv", "general_ledger"),
("json", "members_equity"),
("csv", "tax_summary"),
("csv", "ar_aging"),
("xlsx", "ar_aging"),
("pdf", "ap_aging"),
] {
m.ok(
"books.export",
json!({"format": format, "report": report, "start": "2026-01-01", "end": "2026-12-31"}),
json!({"format": format, "report": report, "start": "2026-01-01", "end": "2026-12-31",
"as_of": "2026-12-31"}),
);
}
// The IIF takes as_of (every posted entry from the first through it),