From d50b8444765f76935fa1d9724bf88965e5d1a8e5 Mon Sep 17 00:00:00 2001 From: Stephen Waits <1+swaits@noreply.git.swaits.com> Date: Sun, 11 Oct 2026 15:31:56 +0000 Subject: [PATCH] Payroll accounting, class tracking, CPA handoff package (48 MCP tools) --- crates/ledger-core/src/journal.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/ledger-core/src/journal.rs b/crates/ledger-core/src/journal.rs index f885ed4..19ec9e0 100644 --- a/crates/ledger-core/src/journal.rs +++ b/crates/ledger-core/src/journal.rs @@ -4,7 +4,7 @@ //! total credits. Entries are born as drafts, validated, then posted. Posting //! is one-way; a posted entry can only be corrected by a reversing entry. -use crate::{AccountId, ChartOfAccounts, Currency, LedgerError, Money, Side}; +use crate::{AccountId, ChartOfAccounts, ClassId, Currency, LedgerError, Money, Side}; use chrono::NaiveDate; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; @@ -41,6 +41,12 @@ pub struct JournalLine { /// base-currency lines. #[serde(default, skip_serializing_if = "Option::is_none")] pub foreign_amount: Option, + /// Divisional tracking: which class (department, location, job) this + /// line belongs to. `None` means unclassified. Set on drafts only via + /// [`crate::Ledger::set_line_class`]; posted lines are immutable, so + /// their classes are frozen. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub class_id: Option, } /// A balanced set of journal lines with a date and a description. @@ -103,6 +109,12 @@ impl JournalEntry { self.lines.push(line); } + /// Tag a line with a class (or clear it). Only while drafted; only via + /// [`crate::Ledger::set_line_class`]. + pub(crate) fn set_line_class(&mut self, index: usize, class_id: Option) { + self.lines[index].class_id = class_id; + } + /// Mark posted. Only via [`crate::Ledger`], after [`Self::validate`]. pub(crate) fn mark_posted(&mut self) { self.status = EntryStatus::Posted;