P3: party totals by what moved the money, once (defects 14, 11, 24)
Each party's part of an entry is signed as its own account would show it, with a direction and its cash and in-kind amounts; a reversal is its original negated. Totals on a cash or all basis: for a member, all equals the members' equity statement and cash the K-1's cash. Money is cash, cards and the financing accounts, and a liability credit never pays its own creditor (creditor_of), so accruals and declared distributions count once. tin_last4 from the TIN or W-9 facts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
52 files changed
+7535
-560
No files matched your search
@@ -43,6 +43,7 @@ pub const KNOWN_FEATURES: &[&str] = &[
|
||||
MEMBERS_CUMULATIVE,
|
||||
FEATURE_ACCOUNT_RENAMED,
|
||||
FEATURE_REQUIRE_TAX_CATEGORY,
|
||||
PARTY_CREDITORS,
|
||||
];
|
||||
|
||||
/// Feature: `period.closed` closes to members with `"rounding":
|
||||
@@ -70,6 +71,10 @@ pub(crate) fn setting_feature(key: &str) -> Option<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The feature `party.creditor_set` lines need: a party named the
|
||||
/// creditor of a liability (rules.md B-35, P3 defect 24).
|
||||
pub const PARTY_CREDITORS: &str = "party_creditors";
|
||||
|
||||
/// The line type that declares the journal format.
|
||||
pub const FORMAT_SET: &str = "journal.format_set";
|
||||
|
||||
@@ -119,6 +124,7 @@ pub const EVENT_TYPES: &[&str] = &[
|
||||
"open_item.raised",
|
||||
"open_item.resolved",
|
||||
"party.added",
|
||||
"party.creditor_set",
|
||||
"party.role_set",
|
||||
"party.tin_set",
|
||||
"party.updated",
|
||||
|
||||
@@ -55,16 +55,11 @@ pub fn cash_accounts(ledger: &Ledger, configured: &[String]) -> Vec<AccountId> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The card accounts: liabilities whose names contain "card".
|
||||
/// The card accounts: liabilities whose names contain "card"
|
||||
/// (`ledger_core::parties::card_accounts`, the same rule party money
|
||||
/// reads, so the two never disagree on what a card is).
|
||||
pub fn card_accounts(ledger: &Ledger) -> Vec<AccountId> {
|
||||
ledger
|
||||
.chart()
|
||||
.all()
|
||||
.filter(|a| {
|
||||
a.account_type == AccountType::Liability && a.name.to_lowercase().contains("card")
|
||||
})
|
||||
.map(|a| a.id)
|
||||
.collect()
|
||||
ledger_core::parties::card_accounts(ledger)
|
||||
}
|
||||
|
||||
/// The accounts the books reconcile monthly (INVARIANT 1).
|
||||
|
||||
@@ -1238,6 +1238,30 @@ impl Store {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Name a party the creditor of a liability account the company owes
|
||||
/// it itself on, or stop (`on` false): ledger_core's
|
||||
/// `Ledger::set_party_creditor`. Journaled as `party.creditor_set
|
||||
/// {id, account_id, on}`, after the journal declares the feature it
|
||||
/// needs ([`crate::format::PARTY_CREDITORS`]) the first time, so an
|
||||
/// older wanderbooks stops there and says it is too old instead of
|
||||
/// misreading the books.
|
||||
pub fn set_party_creditor(
|
||||
&mut self,
|
||||
id: ledger_core::PartyId,
|
||||
account: AccountId,
|
||||
on: bool,
|
||||
) -> Result<(), StoreError> {
|
||||
self.ledger.check_set_party_creditor(id, account, on)?;
|
||||
self.ensure_feature(crate::format::PARTY_CREDITORS)?;
|
||||
let line = self.append_journal(
|
||||
"party.creditor_set",
|
||||
json!({"id": id, "account_id": account, "on": on}),
|
||||
)?;
|
||||
self.ledger.set_party_creditor(id, account, on)?;
|
||||
apply_line_to_sqlite(&self.conn, &line)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Record or clear a party's W-9 facts. Journaled as `party.w9_set`.
|
||||
pub fn set_party_w9(
|
||||
&mut self,
|
||||
@@ -4844,6 +4868,13 @@ pub(crate) fn replay_line_into_ledger(
|
||||
.ok_or_else(|| StoreError::JournalCorrupt("party.role_set on".into()))?;
|
||||
ledger.set_party_role(get_u64(p, "id")?, role, on)?;
|
||||
}
|
||||
"party.creditor_set" => {
|
||||
let on = p
|
||||
.get("on")
|
||||
.and_then(|v| v.as_bool())
|
||||
.ok_or_else(|| StoreError::JournalCorrupt("party.creditor_set on".into()))?;
|
||||
ledger.set_party_creditor(get_u64(p, "id")?, get_u64(p, "account_id")?, on)?;
|
||||
}
|
||||
"party.w9_set" => {
|
||||
let facts: Option<ledger_core::W9Facts> = serde_json::from_value(p["facts"].clone())
|
||||
.map_err(|_| StoreError::JournalCorrupt("party.w9_set facts".into()))?;
|
||||
@@ -5569,8 +5600,8 @@ fn apply_line_rows(conn: &Connection, line: &JournalLine) -> Result<(), StoreErr
|
||||
)?;
|
||||
}
|
||||
"role.assigned" | "party.added" | "party.updated" | "party.role_set" | "party.w9_set"
|
||||
| "party.w4_set" | "party.tin_set" | "entry.party_set" | "entry.parties_set"
|
||||
| "entry.no_party" => {
|
||||
| "party.w4_set" | "party.tin_set" | "party.creditor_set" | "entry.party_set"
|
||||
| "entry.parties_set" | "entry.no_party" => {
|
||||
// Control roles and the party directory live in the ledger
|
||||
// (rebuilt by replay); the events mirror below keeps the
|
||||
// history.
|
||||
|
||||
Reference in new issue
Block a user