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:
1 parent
d1fb512e4b
commit
75d8c23d9b
10 files changed
+416
-14
No files matched your search
@@ -703,6 +703,16 @@ fn books_p2_served_cut_over_whole() {
|
||||
// A project by name alone is a plain class, which P2 reads.
|
||||
a.call("books.add_project", json!({"name": "Lighthouse"}))
|
||||
.unwrap();
|
||||
// An invoice for the send below: a document P2 reads (no new fields),
|
||||
// so it is written before the go-ahead; its send waits for it.
|
||||
let invoice = a
|
||||
.call(
|
||||
"books.create_invoice",
|
||||
json!({"party_id": 1, "date": "2026-01-05", "due_date": "2026-02-04",
|
||||
"lines": [{"description": "Design", "amount": "400.00"}]}),
|
||||
)
|
||||
.unwrap()["id"]
|
||||
.clone();
|
||||
let waiting = journal(&dir);
|
||||
let refused: Vec<(&str, Value)> = vec![
|
||||
// An invoice's or bill's line naming its project
|
||||
@@ -729,6 +739,13 @@ fn books_p2_served_cut_over_whole() {
|
||||
json!({"decided_by": "Avery Quill",
|
||||
"changes": [{"key": "invoicing.default_terms", "value": "Net 30"}]}),
|
||||
),
|
||||
// A recorded send (invoice.sent, documents_v2, P4b S5): an older
|
||||
// version refuses the type.
|
||||
(
|
||||
"books.mark_sent",
|
||||
json!({"invoice": invoice.to_string(), "on": "2026-01-06",
|
||||
"by": "Avery Quill", "recipients": ["billing@fernhill.example"]}),
|
||||
),
|
||||
(
|
||||
"books.add_account",
|
||||
json!({"code": "4900", "name": "Gain on sale", "account_type": "revenue",
|
||||
|
||||
Reference in new issue
Block a user