P1: the books keep how they are kept
Settings, conventions (posting templates, approval rules, policies, close steps, calendar, mappings, notes), decisions, open items and entry approvals are journal events (journal format 2). Books that keep their settings never read wanderbooks.toml or config.json; the server and the viewer take the vault key from --vault-key-file instead. books.import_settings moves a settings file in once, exactly as the server reads it. Approval rules, when enforced, gate every posting path. Replay refuses what it can't read as newer, never as corrupt. Lean: a record changes no figure. Mutation on the whole diff: every survivor killed by a test but two equivalent (docs/testing.md). The Company page says a late open item is overdue in words, not colour alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
1 parent
e60ecc5317
commit
c587593d78
71 files changed
+12249
-461
No files matched your search
@@ -11,6 +11,7 @@ name = "wanderbooks"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
chrono = "0.4"
|
||||
ledger-config = { path = "../ledger-config" }
|
||||
ledger-core = { path = "../ledger-core" }
|
||||
serde_json = "1"
|
||||
|
||||
@@ -262,6 +262,9 @@ fiscal_year_start = "01-01"
|
||||
# "partnership", "S corporation", "C corporation", or "disregarded entity"
|
||||
# (Treas. Reg. §301.7701-3). tax_form wins when both are set.
|
||||
# tax_classification = "partnership"
|
||||
# The company's time zone (an IANA name): what "today" means for the
|
||||
# books. Unset: the computer's.
|
||||
# timezone = "America/Denver"
|
||||
# Identity facts the CPA needs, each from a primary document (EIN letter,
|
||||
# certificate of organization). Any simple key = value line here shows in
|
||||
# the viewer's profile, labelled from its key.
|
||||
@@ -304,6 +307,10 @@ accounting_basis = "accrual"
|
||||
# means every entry (IRS Pub. 583: keep the documents that support your
|
||||
# books). A receipt_policy in books-data/config.json overrides it.
|
||||
receipt_threshold = "25.00"
|
||||
# Accounts whose entries always need a receipt, whatever the amount; and
|
||||
# accounts exempt from the rule (an entry whose every line is in one).
|
||||
# receipt_required_accounts = ["6200"]
|
||||
# receipt_exempt_accounts = ["6999"]
|
||||
# Account codes that hold cash (bank, savings, cash on hand), for the cash
|
||||
# flow statement and dashboards. Unset: cash accounts are found by name.
|
||||
# cash_accounts = ["1000", "1010"]
|
||||
@@ -332,6 +339,11 @@ receipt_threshold = "25.00"
|
||||
# (a transfer between your own accounts); "off" never mentions it.
|
||||
# parties = "suggest"
|
||||
|
||||
# Approval rules (conventions naming who approves which entries) block a
|
||||
# post until approved only under "enforce"; "off" (the default) records
|
||||
# them and blocks nothing.
|
||||
# approval_rules = "off"
|
||||
|
||||
# Which accounts invoices, bills, payroll, and closes post to (receivables,
|
||||
# payables, revenue, payroll, retained earnings) is not set here: those
|
||||
# roles are part of the books, journaled with books.assign_role and listed
|
||||
|
||||
@@ -418,3 +418,41 @@ fn the_toml_shows_every_setting_the_config_reads() {
|
||||
assert!(has(sect, key), "[{sect}] {key} is in the template");
|
||||
}
|
||||
}
|
||||
|
||||
/// Moving the scaffolded settings file, and every example in it, into
|
||||
/// books: each imports, reads as the file does (the import refuses
|
||||
/// otherwise), and exporting it back and importing the export changes
|
||||
/// nothing (plan/served.md P1: toml_round_trip_zero_events).
|
||||
#[test]
|
||||
fn the_toml_and_its_examples_import_and_round_trip() {
|
||||
use ledger_core::settings::{Settings, Source};
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let (_, text) = scaffolded_toml(tmp.path());
|
||||
let single_owner = |ex: &str| ex.starts_with("close_into ");
|
||||
let source = Source {
|
||||
decided_by: "onboarding".into(),
|
||||
decided_on: chrono_date(),
|
||||
citation: None,
|
||||
note: None,
|
||||
};
|
||||
for file in [
|
||||
text.clone(),
|
||||
uncomment(&text, |ex| !single_owner(ex)),
|
||||
uncomment(&text, single_owner),
|
||||
] {
|
||||
let import = ledger_config::codec::import(&file, None, &Settings::default(), "USD")
|
||||
.unwrap_or_else(|e| panic!("imports: {e}\n{file}"));
|
||||
assert!(import.unknown.is_empty(), "{:?}", import.unknown);
|
||||
let mut books = Settings::default();
|
||||
let changes = books.check_changes(&import.changes, &source).unwrap();
|
||||
books.apply(&changes, &source, 1).unwrap();
|
||||
let export = ledger_config::codec::export(&books, "USD");
|
||||
let again = ledger_config::codec::import(&export, None, &books, "USD")
|
||||
.unwrap_or_else(|e| panic!("the export imports: {e}\n{export}"));
|
||||
assert!(again.changes.is_empty(), "{:?}\n{export}", again.changes);
|
||||
}
|
||||
}
|
||||
|
||||
fn chrono_date() -> chrono::NaiveDate {
|
||||
chrono::NaiveDate::from_ymd_opt(2026, 10, 17).unwrap()
|
||||
}
|
||||
Reference in new issue
Block a user