feat(mcp): books.mark_sent, and the send in the read tools (P4b S5)

`books.mark_sent` records the send the human or a delegate made: the
invoice (`invoice:7` or its id), the day, who sent it (the token's name
unless given), how (email by default, or mail, portal, hand, another
channel), to whom (the bill-to email, else the customer's), whether it
was the original or a reminder (original on the first send, reminder
after), and the channel's own id. It answers with every send on the
invoice, newest last. `books.documents` rows gain `last_sent` and
`books.document` shows the sends, so "did we send it, and when" is one
read away, while a bill (which we receive) has none.

The schema lint refuses a parameter named `to` (a path-name rule), so
the recipients parameter is `recipients`; the journal event and the
result keep the plan's `to`. The tool needs `books:write`: it changes
the record, never a figure.

The handbook gains the invoices-and-bills section (S4.5 had not landed
yet) with the send procedure: the books never send, the PDF goes to the
human, and each send is recorded afterwards.

Over the wire, mark_sent_records_who_and_how: two sends (the second a
reminder with a message id), the list's `last_sent`, the document's
`sends`, and the invoice still owing its total. The P2 cutover refuses
a send before consent and takes it after.
This commit is contained in:
swaits committed 2026-10-27 13:24:10 -06:00
1 parent d1fb512e4b
commit 75d8c23d9b
10 files changed
+416 -14

No files matched your search

+21 -3
View File
@@ -2029,9 +2029,24 @@ S4 changes `books.create_invoice` and `books.create_bill`; see plan 8.2 S4.
number and reads `void`. Refused while a payment stands on it (the
refusal names the payment entry to reverse first), when void already,
before its date or in a closed period (P4b S1, B-38).
- `books.mark_sent { invoice, on?, by?, via?, recipients?: [string],
kind?, reference?, note? }` → `{ invoice, sends: [{ on, by, via, to, kind,
reference?, note? }] }`. Records that an invoice went out (P4b S5,
A-4): the books never send, so the owner or a delegate sends and this
records who (`by`: the token's name unless given), how (`via`: email
the default, or mail, portal, hand, another channel's name), to whom
(`recipients`: the invoice's bill-to email, else the customer's, else
required), when (`on`: today in the company's time zone), original or
reminder (`kind`: original on the first send, reminder after), and the
channel's own id (`reference`: a message id). `invoice` is
`invoice:N` or `N`. Several sends are allowed; none moves a figure or
the due date. Refused on a void invoice, dated before it, with no
recipients, or while the books wait for `documents_v2`
(`format_upgrade_pending`). `sends` is every send on the invoice, in
order, the new one last.
- `books.documents { kind, status?, party?, due_by?, as_of? }` → `{ as_of,
kind, documents: [{ ref, party, date, due_date, total, outstanding,
status, overdue_days }], outstanding_total }`. Invoices (`kind`
status, overdue_days, last_sent? }], outstanding_total }`. Invoices (`kind`
`invoices`) or bills (`bills`), due date first, every figure derived
from the document's lines that stand (B-38). `status` filters: `open`
(default: anything still owed, partly paid and overdue included),
@@ -2039,11 +2054,14 @@ S4 changes `books.create_invoice` and `books.create_bill`; see plan 8.2 S4.
own `status` is one of `open`, `partial`, `paid`, `written_off`,
`void`, `overpaid` or `unattached` (`DocStatus::key()`). `party` (a
customer's or vendor's name, ignoring case) and `due_by` (due on or
before that day) narrow the list (P4b S2).
before that day) narrow the list (P4b S2). `last_sent` is the day the
invoice last went out, when a send is recorded (P4b S5); absent for an
invoice never sent and for every bill.
- `books.document { ref, as_of?, pdf? }` → `{ ref, party, date, due_date,
entry_id, lines: [{ description, amount, project? }], total, paid,
discounted, written_off, outstanding, status, overdue_days, timeline:
[{ date, entry_id, kind, amount, reversal, stands }], pdf? }`. `ref` is
[{ date, entry_id, kind, amount, reversal, stands }], sends: [{ on, by,
via, to, kind, reference?, note? }], pdf? }`. `ref` is
`invoice:N` or `bill:N`; `status` as `books.documents`' rows; the
timeline is every ledger line tagged with the document, dated on or
before the day, in posting order, each saying whether its entry still