feat(mcp): books.write_off_invoice (P4b S7)

One tool for the bad debt (rules.md B-41): it writes off an invoice that
will not be collected, Dr 6990 Bad Debt Expense / Cr 1200 Accounts
Receivable, the receivable line tagged with the invoice so the document
reads written_off and its figures stay derived (B-38).

The evidence is the point, and the tool takes exactly one of
upload_id (an upload, filed with the write-off) and
existing_attachment_id (one already filed, linked to the write-off's
entry and the invoice); neither or both is refused. amount defaults to
what the invoice owes and may be partial.

Without confirmed it is a preview: the engine runs on a clone (every
refusal the post would give) and the answer is the figures the write-off
would leave, entry_id 0, nothing journaled.

The tool lives in its own group file, tools/write_offs.rs (plan U0a), so
S4.4's document tools and this one land apart; it is destructive, needs
books:write to preview and books:post to post (scopes.rs), and is
documented under "Tools — A/R and A/P". The destructive-tool count, the
tool tables and the journey test's tool count each move by one.

Tests over the wire: write_off_then_recovery, write_off_needs_evidence.
This commit is contained in:
swaits committed 2026-10-27 12:44:45 -06:00
1 parent 4e71cf6320
commit 0f1c7786ab
8 files changed
+464 -7

No files matched your search

+3 -1
View File
@@ -56,7 +56,7 @@ const C: Needs = PostWhenConfirmed;
const K: Needs = PostForApprovalRules;
/// Every tool and what it needs (INVARIANT 1).
pub const TABLE: [(&str, Needs); 122] = [
pub const TABLE: [(&str, Needs); 123] = [
("books.handbook", R),
("books.start", R),
// Settings are committed at once: the settings commit of the plan.
@@ -186,6 +186,8 @@ pub const TABLE: [(&str, Needs); 122] = [
("books.access_log", A),
("books.backup", R),
("books.back_up_now", A),
// P4b S7: a bad-debt write-off (preview, then post).
("books.write_off_invoice", C),
];
/// What `tool` needs, given its arguments; None for a tool this server