Audit fixes: party money, reconciliations, require policy, K-1 property

An independent recompute of the real books found no numeric
disagreements; review found 15 defects, all fixed with spec-first
regression tests:

- k1: a prepaid credited straight to capital is property; a member
  paying a vendor through 2100 is cash. Docs and B-13 match.
- parties: a reversal of a reversal counts on its root's side;
  liability-to-liability payments (member pays the card) count as paid.
- viewer: party money uses the server's cash accounts and full calendar
  year; per-party "What for" scales multi-party entries.
- reconciliations: refuse a withdrawn statement before journaling; new
  books.void_statement; reconciled lines can't be un-cleared; one shared
  reconcilable-accounts rule for digest and viewer.
- CPA package includes receipts linked to entries; a file can't be both
  filed on and linked to one entry.
- parties = "require" covers payroll, tax remittance (agency) and
  disposal (buyer); no-party marks follow reversal chains and clear when
  a vendor is tagged.
- derived member shares are frozen to exact amounts when posted, so a
  settings change never re-splits history.

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 20:16:03 -06:00
1 parent e1b2509c22
commit 8498e6cfab
22 files changed
+1802 -343

No files matched your search

+16 -8
View File
@@ -81,18 +81,26 @@ impl Default for Company {
impl Company {
/// The members as party shares see them ([`ledger_core::PartyMember`]):
/// each member whose name (ignoring case and surrounding space) is a
/// party with the member role not archived, and whose capital account
/// is in the chart. Members missing either are left out, so their
/// shares can't be derived (and the viewer shows no capital
/// statement for them).
/// party with the member role, and whose capital account is in the
/// chart. The party not archived of that name when there is one, else
/// the most recent archived one: archiving a member ("no longer dealt
/// with; history stays") never takes their history away. Members
/// missing either are left out, so their shares can't be derived (and
/// the viewer shows no capital statement for them).
pub fn party_members(&self, ledger: &ledger_core::Ledger) -> Vec<ledger_core::PartyMember> {
let is_member = |p: &&ledger_core::Party| p.roles.contains(&ledger_core::Role::Member);
self.members
.iter()
.filter_map(|m| {
let party = ledger.party_by_name(&m.name)?;
if !party.roles.contains(&ledger_core::Role::Member) {
return None;
}
let key = ledger_core::parties::name_key(&m.name);
let party = match ledger.party_by_name(&m.name) {
Some(p) => Some(p).filter(is_member)?,
None => ledger
.parties()
.filter(|p| p.archived && ledger_core::parties::name_key(&p.name) == key)
.filter(is_member)
.max_by_key(|p| p.id)?,
};
Some(ledger_core::PartyMember {
party: party.id,
share: m.share,