feat(arap): record an invoice's sends as invoice.sent (P4b S5)
The books never send invoices (A-4), but they must remember that one
went out: who sent it, how, to whom, when, whether it was the original
or a reminder, and the channel's own reference. That record is a fact
of the document's life, not a figure: a send moves no money, so it is
one journal line (`invoice.sent`) and no entry, and every derived
figure (outstanding, aging, DocState) is exactly as it was.
`Ledger::mark_sent` refuses a send on a void invoice (it should never
have reached the customer), a send dated before the invoice, no
recipients, and any recipient, reference or note that would put a full
TIN or control characters into the journal. Open, partial, paid and
written-off invoices can be sent (a copy for the customer's records, a
reminder).
The event is `documents_v2`'s (D-52): an older version refuses the
type rather than dropping the record. Replay restores it and the
projection keeps it in schema 16's `invoice_sends` table, so live,
replay and the viewer agree. The new old-books fixture `p4_s5_before`
was written by eeee9658 and opens to the same figures; a hand-written
send in `books_written_under_older_rules_still_open` proves an older
journal with the new line still opens.
Tests: mark_sent_waits_for_documents_v2, sends_replay_and_project,
a_send_changes_no_figure, mark_sent_on_void_refused, Op::MarkSent in
status_is_a_function_of_journal_and_date,
an_older_version_stops_at_invoice_sent.
This commit is contained in:
1 parent
ed637f0e27
commit
d1fb512e4b
16 files changed
+979
-5
No files matched your search
@@ -0,0 +1,65 @@
|
||||
//! Writes `p4_s5_before`: books the version before P4b S5 (main eeee9658)
|
||||
//! wrote through its Store API: a chart, a customer's invoice part paid,
|
||||
//! and a vendor's bill. No send exists in that version; this version opens
|
||||
//! the same journal and must report the same figures.
|
||||
use chrono::NaiveDate;
|
||||
use ledger_core::{AccountType, Currency, DocRef, DocumentLine, Money, Side};
|
||||
use ledger_store::Store;
|
||||
use rust_decimal::Decimal;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn d(y: i32, m: u32, day: u32) -> NaiveDate {
|
||||
NaiveDate::from_ymd_opt(y, m, day).unwrap()
|
||||
}
|
||||
fn cents(c: i64) -> Decimal {
|
||||
Decimal::new(c, 2)
|
||||
}
|
||||
fn line(what: &str, c: i64) -> DocumentLine {
|
||||
DocumentLine {
|
||||
description: what.into(),
|
||||
amount: cents(c),
|
||||
class_id: None,
|
||||
account: None,
|
||||
quantity: None,
|
||||
unit_price: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let root = PathBuf::from(std::env::args().nth(1).unwrap());
|
||||
let dir = root.join("p4_s5_before");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let mut s = Store::create(&dir, Currency::USD).unwrap();
|
||||
let bank = s
|
||||
.add_account("1000", "Bank", AccountType::Asset, None, false)
|
||||
.unwrap();
|
||||
let capital = s
|
||||
.add_account("3000", "Capital", AccountType::Equity, None, false)
|
||||
.unwrap();
|
||||
let seed = s.create_entry(d(2026, 1, 1), "Opening capital").unwrap();
|
||||
let m = Money::new(Decimal::from(1000), Currency::USD);
|
||||
s.add_line(seed, bank, Side::Debit, m).unwrap();
|
||||
s.add_line(seed, capital, Side::Credit, m).unwrap();
|
||||
s.post_entry(seed).unwrap();
|
||||
let fernhill = s.add_customer("Fernhill Labs").unwrap();
|
||||
let vendor = s.add_vendor("Tobin Harlow Supply").unwrap();
|
||||
let inv = s
|
||||
.create_invoice(
|
||||
fernhill,
|
||||
d(2026, 2, 1),
|
||||
d(2026, 3, 3),
|
||||
vec![line("Design", 120_000)],
|
||||
)
|
||||
.unwrap();
|
||||
s.receive_payment(inv, d(2026, 2, 15), cents(40_000), bank)
|
||||
.unwrap();
|
||||
s.create_bill(
|
||||
vendor,
|
||||
d(2026, 2, 10),
|
||||
d(2026, 3, 12),
|
||||
vec![line("Drafting", 30_000)],
|
||||
)
|
||||
.unwrap();
|
||||
println!("WROTE {:?} (invoice {inv}, {:?})", dir, DocRef::Invoice(inv));
|
||||
}
|
||||
Reference in new issue
Block a user