Parties, attachment links, statements, reconciliations (store), TIN vault crate

Parties: one directory for vendors, contractors, customers, employees,
members, platforms, agencies, banks, landlords (ledger-core parties.rs).
W-9/W-4 facts without the number (last four only; any nine-digit run in a
party field is refused, Treas. Reg. 301.6109-4). Vendor and customer
records link to parties on replay; core tags every bill, invoice, payment,
and reversal with its counterparty. Journal events party.*,
entry.party_set; payroll lines name the employee party.

MCP: books.add_party, update_party, set_party_roles, set_w9, set_w4,
parties, party, set_entry_party; draft_entry party; run_payroll employee;
show party. Viewer: Parties directory and party pages, search, journal
text, W-9 attention, demo data.

One definition of a party's money (ledger_core::parties::activity) for
MCP and viewer. 1099 amounts count payments from cash and financing
accounts (a member paying for the company counts; a card doesn't; a
prepayment counts when made): vendor_1099_report_with, B-19.

Store: attachment links to any subject, statements, reconciliations
(schema v11), with replay and projection. ledger-vault: encrypted TIN and
W-9 records (ChaCha20-Poly1305, key file outside the books).

Spec-blind tests for parties (core, store, MCP, viewer) and attachments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-15 14:43:40 -06:00
1 parent 2a188bd039
commit a299077991
54 files changed
+10961 -178

No files matched your search

+49
View File
@@ -213,3 +213,52 @@ fn forms_have_their_irs_names() {
assert_eq!(Form1099::parse(f.label()), Some(f));
}
}
/// vendor_1099_report_with SCENARIOS 1–2, worked by hand. Payment sources:
/// cash 1000 and the members' financing liability 2200. Kim (1099-MISC,
/// landlord) in 2026:
/// - 1,000 rent from the bank (Dr rent, Cr 1000): +1,000
/// - 2,400 prepaid rent from the bank (Dr 1300, Cr 1000): +2,400 (paid
/// when made; the prepaid asset is not a source, so it doesn't offset)
/// - 800 rent paid by a member (Dr rent, Cr 2200): +800
/// - 500 rent on the card (Dr rent, Cr 2100): 0 (1099-K)
/// - 300 refunded to the bank (Dr 1000, Cr rent): −300
///
/// Total 3,900. The plain report (every asset a source, no financing)
/// sees 1,000 + 0 + 0 + 0 − 300 = 700: the understatement this fixes.
#[test]
fn payments_come_from_cash_and_financing_only() {
use ledger_core::{PaymentAccounts, vendor_1099_report_with};
let mut l = Ledger::new(Currency::USD);
let acct = |l: &mut Ledger, code: &str, t| l.add_account(code, code, t, None, false).unwrap();
let bank = acct(&mut l, "1000", AccountType::Asset);
let prepaid = acct(&mut l, "1300", AccountType::Asset);
let card = acct(&mut l, "2100", AccountType::Liability);
let members = acct(&mut l, "2200", AccountType::Liability);
let rent = acct(&mut l, "6600", AccountType::Expense);
let kim = l.add_vendor("Kim").unwrap();
l.set_vendor_1099(kim, Some(Form1099::Misc), true).unwrap();
for (dr, cr, amt) in [
(rent, bank, "1000"),
(prepaid, bank, "2400"),
(rent, members, "800"),
(rent, card, "500"),
(bank, rent, "300"),
] {
let e = l.create_entry(d(2026, 5, 1), "rent");
let m = Money::new(dec(amt), Currency::USD);
l.add_line(e, dr, Side::Debit, m).unwrap();
l.add_line(e, cr, Side::Credit, m).unwrap();
l.post_entry(e).unwrap();
l.set_entry_vendor(e, Some(kim)).unwrap();
}
let sources = PaymentAccounts {
cash: vec![bank],
financing: vec![members],
};
let r = vendor_1099_report_with(&l, 2026, None, &sources).unwrap();
assert_eq!(r.vendors[0].paid, dec("3900"));
assert!(r.vendors[0].reportable, "3,900 is over 2,000");
let plain = vendor_1099_report(&l, 2026, None).unwrap();
assert_eq!(plain.vendors[0].paid, dec("700"));
}
@@ -196,6 +196,7 @@ fn pline(
) -> PayrollLine {
PayrollLine {
employee_name: name.to_string(),
employee: None,
gross_pay: d(gross),
federal_withholding: d(fed),
state_withholding: d(state),
@@ -1208,6 +1209,7 @@ fn arb_line() -> impl Strategy<Value = PayrollLine> {
let z = |(v, keep): (i64, bool)| Decimal::new(if keep { v } else { 0 }, 2);
PayrollLine {
employee_name: "E".to_string(),
employee: None,
gross_pay: Decimal::new(gross, 2),
federal_withholding: z(w[0]),
state_withholding: z(w[1]),
File diff suppressed because it is too large. Load diff
+1
View File
@@ -224,6 +224,7 @@ fn refuses(l: &mut Ledger, key: &str, account: Option<AccountId>) -> LedgerError
fn alice() -> PayrollLine {
PayrollLine {
employee_name: "Alice".to_string(),
employee: None,
gross_pay: dec("5000.00"),
federal_withholding: dec("600.00"),
state_withholding: dec("250.00"),