diff --git a/README.md b/README.md index 70b2ea6..c36cca5 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ code = "1000" name = "Cash" type = "asset" # asset | liability | equity | revenue | expense -[[events]] # in order; event N is entry #N +[[events]] # in order, counting from 1 date = "2026-01-10" description = "Property contributed" lines = [ @@ -50,8 +50,9 @@ lines = [ [[events]] date = "2026-01-10" description = "Reverse entry 1" -reverses = 1 # the event (1-based) this one reverses: - # its lines, sides flipped; no `lines` here +reverses = 1 # the event (1-based) whose entry this + # one reverses: its lines, sides flipped; + # no `lines` here [[expect]] query = "cash_flow" # what is asked of the books (see below) @@ -62,14 +63,29 @@ to = "2026-12-31" # inclusive (a query as of a day: as_of) operating = "0.00" ``` -Events count from 1, as entries do: the first `[[events]]` is event 1 -(entry #1), and `reverses = N` reverses the Nth event, one before it; -`reverses = 0` names no event and is refused. An event is an entry -(`lines`), a reversal (`reverses`), a close (`close`, below) or a bill -(`bill`, below), exactly one of them, and posts entry #N. A change that -posts no entry (a rename, a policy) is a `[[changes]]` entry instead -(below), so the numbering holds. A key the shape doesn't have is refused -too, never ignored. +Events count from 1: the first `[[events]]` is event 1. An event is an +entry (`lines`), a reversal (`reverses`), a close (`close`, below) or a +bill (`bill`, below), each posting one entry; or one of the books' +commands that post what is due (`run_depreciation`, `dispose_asset`, +`run_schedules`, below), which post as many entries as are due, none +included; or a schedule's registration (`register_schedule`), which +posts none: exactly one of them. A key that names an event (`reverses = +N`, `entry = N`, a cash flow's ``) names the entry event N posted, one +before it: an event that posted none, or several, can't be named that +way, and `reverses = 0` names no event and is refused. While every event +posts one entry, event N's is entry #N. A change that posts no entry (a +rename, a policy) is a `[[changes]]` entry instead (below). A key the +shape doesn't have is refused too, never ignored. + +An entry, a reversal, a close, a bill and a disposal are dated (`date`); +the other commands date their own entries (a run its month's end) and +take no date. An entry, a reversal, a close and a bill are described +(`description`); a command describes its own entries and takes none. + +The books are the engine's own (`ledger_store::Store`, in a directory of +their own): each event is the command the books take it by, journaled as +they journal it, and after every expectation is checked the books must +replay to what they kept (`Store::verify`). `rounding` names the rule the example's figures are rounded by where one rounds: `half-even` or `half-away-from-zero` to the currency's minor unit, @@ -113,6 +129,34 @@ the side gives the direction. expense_account }`: accounts that hold property and equipment (their cost and accumulated depreciation are investing; ASC 230-10-45-12(c), 45-13(c)). +- `[[setup.assets]]`: fixed assets registered before the first event, + each named by its `id` in the events and queries that ask about it + (rules.md B-15): + + ```toml + [[setup.assets]] + id = "X" # its name in this example + description = "Asset X" + cost = "12000.00" + salvage = "0.00" + life_months = 12 + in_service = "2026-01-01" # depreciation starts here + method = "straight_line" # the one method the books have + cost_account = "1500" + accumulated_account = "1510" + expense_account = "6200" + ``` + + Registering an asset posts nothing: the entry that buys it is an event + of its own. +- `[[setup.schedules]]`, and `register_schedule = { ... }` as an event: a + prepaid expense or deferred revenue recognized over its months (rules.md + B-16), named by its `id`: + `{ id, kind, description, total, start, months, convention, + balance_account, pl_account }`, `kind` `"prepaid_expense"` or + `"deferred_revenue"`, `convention` `"daily"` (by the day, the last month + taking what the others leave) or `"monthly_even"` (equal months from + the 1st). Registering one posts nothing. - `[[setup.parties]]` `{ name, roles, creditor_of?, tin_last4?, w9_tin_last4?, tin_purged? }`: the company's directory (B-26). `roles` are role keys (`member`, `vendor`, `customer`, `contractor`, @@ -153,8 +197,8 @@ the side gives the direction. liability is a card account (B-35 item 5): while none is recorded, a liability whose name says "card", not "gift card", is. - `payee` on an event: the payee (by name, one of `setup.payees`) the - entry paid. Not on a reversal, which takes its original's, nor on a - close. + entry paid. Only on an entry (`lines`): a reversal takes its + original's, a bill names its vendor, a close and a command have none. - `parties = [{ party, amount? }, { party, percent? }, ...]` on an event: the parties (by name, in `setup.parties`) the entry is with, each with its share: an `amount` (an exact decimal string), a `percent`, or @@ -164,7 +208,7 @@ the side gives the direction. `payee` as well, the payee's party is one of the parties. - `instrument` on an event: the payment method (a `setup.instruments` label) the entry was paid with. Neither `parties` nor `instrument` on a - reversal, which takes its original's. + reversal, which takes its original's, nor on a command. - `close = { start, to, rounding? }` as an event: closes the period from `start` through the event's `date` (rules.md B-11, B-13), `to` `"members"` (split by share, in the order listed) or @@ -173,13 +217,28 @@ the side gives the direction. have closed less what earlier closes gave them; `rounding = "per-close"` splits that close's net on its own instead (books from an engine that rounded each close by itself). A close must have something to close: it - posts entry #N like any other event. + posts one entry like any other event. - `bill = { vendor, amount }` as an event: a bill from `vendor` (a name; a payee's record if one has it) for `amount`, dated and due on the - event's date. It posts entry #N: General Expenses (6999) debited and + event's date. It posts one entry: General Expenses (6999) debited and Accounts Payable (2000) credited, each account created with that fixed name when first needed, as the books create a control role's account (B-32). +- `run_depreciation = { asset, period }` as an event: the asset's run for + `period` (`"YYYY-MM"`), which posts what the schedule says is due + through the month's end and the books don't carry, dated then: a + skipped month caught up, nothing when nothing is due (rules.md B-15). +- `dispose_asset = { asset, proceeds, proceeds_account, gain_loss_account + }` as an event, dated: the asset's disposal for `proceeds` (an exact + decimal, zero allowed), received into `proceeds_account`, its gain or + loss to `gain_loss_account` (B-15). It posts the depreciation still due + through its day in an entry of its own when the month has no run, then + the disposal entry. +- `run_schedules = { through }` as an event: every schedule's months due + through `through` posted (B-16). +- `refused = ""` on a reversal or a command: the books refuse it, + for that reason (below); it changes nothing, posts nothing, and the + example goes on without it. - `[[changes]]` `{ after, rename?, require_tax_category? }`: a change that posts no entry, exactly one of `rename = { code, name }` (rename the account `code`, B-31) and `require_tax_category = "warn"` or @@ -213,7 +272,7 @@ answers is refused, never skipped. | `cash_flow` | `from`, `to` | figures (below) | `ledger-core` `tests/bookspec.rs` | | `trial_balance` | `as_of` | each account's balance by code, debits less credits (a credit balance negative), every account listed | `ledger-core` `tests/bookspec.rs` | | `profit_and_loss` | `from`, `to` | each revenue and expense account's activity by code, on its normal side, closing entries left out, every one listed; `net_income`, revenue less expenses | `ledger-core` `tests/bookspec.rs` | -| `entry` | `entry = N`, no dates | entry N's lines, `".debit"` and `".credit"` each the sum of that account's lines on that side, every line of the entry named | `ledger-core` `tests/bookspec.rs` | +| `entry` | `entry = N`, no dates | the lines of event N's entry, `".debit"` and `".credit"` each the sum of that account's lines on that side, every line of the entry named | `ledger-core` `tests/bookspec.rs` | | `balances` | `as_of` | each account named by code, its balance on its normal side (a member's capital after a loss is negative); the example names the accounts it shows | `ledger-core` `tests/bookspec.rs` | | `members_equity` | `from`, `to` | the statement of changes in members' equity (rules.md B-13): `".beginning"`, `.contributions`, `.net_income`, `.distributions`, `.other`, `.ending`; the example names the figures it shows | `ledger-core` `tests/bookspec.rs` | | `k1` | `from`, `to` | the K-1 worksheet (B-13; Schedule K-1 Item L): `".beginning"`, `.contributions_cash`, `.contributions_property`, `.net_income`, `.distributions`, `.other`, `.ending`; the example names the figures it shows (`cash_accounts` required) | `ledger-core` `tests/bookspec.rs` | @@ -227,6 +286,10 @@ answers is refused, never skipped. | `calendar_year` | none | the year a calendar-year count (1099s, party totals) asked without one counts: `year` | `ledger-core` `tests/bookspec.rs` | | `account_names` | none | each account's name by code, as the chart holds it and every report prints it, past periods included (B-31); every account listed, those the books created included | `ledger-core` `tests/bookspec.rs` | | `uncategorized_accounts` | none | a list: every account that needs a tax category (revenue or expense, not a header) and has none, with activity or not, `" "`, by code (B-32) | `ledger-core` `tests/bookspec.rs` | +| `run` | `event = N`, no dates | what the `run_depreciation` event N posted: `amount` (zero when nothing was due, or the month had its run already), and `caught_up`, what of it is beyond the month's own depreciation (B-15); both listed | `ledger-core` `tests/bookspec.rs` | +| `disposal` | `asset`, no dates | what the asset's disposal took (B-15), every figure listed: `final_depreciation` (posted in its own entry), `catch_up_depreciation` and `excess_depreciation` (charged, and credited back, in the disposal entry), `accumulated` (cleared), `book_value`, `gain_loss` (a gain positive) | `ledger-core` `tests/bookspec.rs` | +| `depreciation_schedule` | `asset`, `as_of` | the asset's schedule through the day (B-15): each month named (`"YYYY-MM"`) its depreciation, `"0.00"` for a month it doesn't reach; `accumulated` and `book_value` through the day; the example names the figures it shows | `ledger-core` `tests/bookspec.rs` | +| `schedule_plan` | `schedule`, no dates | the schedule's plan (B-16): what it recognizes at each month end named (`"YYYY-MM-DD"`), `"0.00"` at one it doesn't post; `total`, all it recognizes; the example names the figures it shows | `ledger-core` `tests/bookspec.rs` | | `iif_lines` | `as_of` | formats appendix: the file's lines in order, each its fields joined by tabs, without the CR LF that ends each | `ledger-package` `the_bookspec_iif_example_matches_the_engine` | | `iif_changes` | `as_of` | formats appendix: each text the file writes differently from the books, in file order, as `[what, books' text, file's text]` (the example's header gives `what`) | `ledger-package` `the_bookspec_iif_example_matches_the_engine` | @@ -255,7 +318,15 @@ name, ignoring capitals and surrounding space), `holds_a_tin` (a run of nine digits), `not_one_line` (a line break, tab or other control character, or past 200 characters), `code_taken`, and `tax_category_required` (an account that needs a category left without -one under `refuse`). +one under `refuse`). A refused event's: `depreciation_past_disposal` (a +disposal dated before depreciation a run carries: a later month's run +stands, or a run's change dated after the disposal's month isn't undone), +`entry_irreversible` (an asset's entry that can't be reversed: its +disposal, a disposed asset's depreciation, a run re-instated twice or past +its schedule, or a reversal dated before its run), `already_disposed`, +`invalid_depreciation_period` (a month that ends before the asset is in +service) and `invalid_schedule` (an equal-months schedule not starting on +the 1st, among others). `cash_flow` (rules.md B-14): the statement of cash flows for `[from, to]`. @@ -265,10 +336,10 @@ one under `refuse`). | `net_change`, `cash_beginning`, `cash_ending` | the cash accounts' change and balances | | `difference` | sections less the change in cash: always `"0.00"` | | `net_income` | revenue less expenses, closing entries excluded | -| `
.` | entry N's cash in that section (`financing.4`) | +| `
.` | event N's entry's cash in that section (`financing.4`) | | `
..on_behalf` | of it, what a member or a lender paid or received for the company, no cash account moving (constructive receipt and disbursement) | | `
.count` | how many entries the section lists | -| `noncash.` | entry N's noncash investing and financing disclosure | +| `noncash.` | event N's entry's noncash investing and financing disclosure | | `noncash.count` | how many entries are disclosed | | `reconciliation.depreciation` | depreciation added back | | `reconciliation.investing_gain_loss` | gains and losses whose cash is investing, taken out | @@ -284,8 +355,9 @@ reconciliation key not given must be zero or absent. Every example, in any appendix, is loaded by `crates/ledger-core/tests/bookspec.rs` (`every_bookspec_example_holds`): its shape (a key the shape doesn't have is refused), its setup, and every -event posted. Each query is then checked by the crate that answers it (the -table above), so the shape never rots either. +event taken or refused as it says, on the books' own store. Each query is +then checked by the crate that answers it (the table above), so the shape +never rots either. ## Licence and sources diff --git a/examples/depreciation-catch-up-month.toml b/examples/depreciation-catch-up-month.toml new file mode 100644 index 0000000..e61ffc8 --- /dev/null +++ b/examples/depreciation-catch-up-month.toml @@ -0,0 +1,112 @@ +id = "depreciation-catch-up-month" +title = "A month of depreciation skipped is caught up in the month the run is made" +rule = "A depreciation run for a month posts everything the schedule says is due through that month's end that the books don't carry, so a skipped month is caught up in it, and a later run for the skipped month posts nothing." +status = "CHOICE" +sources = ["ASC 360-10-35-4", "DECISION: plan/served.md P3, Settled in design (2026-10-20)"] +tags = ["depreciation", "catch-up", "skipped-month", "p3"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +[[setup.accounts]] +code = "7100" +name = "Gain or Loss on Disposal" +type = "revenue" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +# February is skipped; March is run. +[[events]] +run_depreciation = { asset = "X", period = "2026-03" } + +# February run afterwards: nothing is due (March caught it up). +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +# Hand check (worked without the code): the schedule through Mar 31 is +# 3 x 1,000.00 = 3,000.00; January's run carried 1,000.00, so March's run +# posts 2,000.00 (its own 1,000.00 and February's, caught up). February's +# run then posts 0.00: the books already carry the schedule through +# February's end and beyond. +[[expect]] +query = "run" +event = 4 + +[expect.values] +amount = "2000.00" +caught_up = "1000.00" + +[[expect]] +query = "run" +event = 5 + +[expect.values] +amount = "0.00" +caught_up = "0.00" + +[[expect]] +query = "balances" +as_of = "2026-03-31" + +[expect.values] +"1510" = "-3000.00" +"6200" = "3000.00" diff --git a/examples/depreciation-disposal-after-a-late-change-undone.toml b/examples/depreciation-disposal-after-a-late-change-undone.toml new file mode 100644 index 0000000..2931fa5 --- /dev/null +++ b/examples/depreciation-disposal-after-a-late-change-undone.toml @@ -0,0 +1,158 @@ +id = "depreciation-disposal-after-a-late-change-undone" +title = "A disposal is taken once a late change to an earlier run is undone, on any later day" +rule = "A disposal clears what the asset's runs carry now, so it waits until each run carries at the end of the disposal's month what it carries now. A reversal of an earlier run dated after that month is undone by reversing it, on any open day: the disposal month and the end are then right, and a later month end keeps the change and its correction as they were made." +status = "CONVENTION" +sources = ["ASC 360-10-35-4", "Pacioli 1494, ch. 31"] +tags = ["depreciation", "disposal", "refusal", "reversal", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +[[setup.accounts]] +code = "7100" +name = "Gain or Loss on Disposal" +type = "revenue" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-03" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-04" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-05" } + +# May's run reversed on Aug 1 (doubted). Its month, and July's, still +# carried it at their ends. +[[events]] +date = "2026-08-01" +description = "Reverse event 7" +reverses = 7 + +# Disposed of 2026-06-15 for 7,000.00: refused. The disposal clears what +# the runs carry now (January to April, 4,000.00) and would charge May +# again at Jun 15, so June would end carrying 6,500.00 against a +# schedule of 5,500.00. The refusal names May's run and the reversal. +[[events]] +date = "2026-06-15" +dispose_asset = { asset = "X", proceeds = "7000.00", proceeds_account = "1000", gain_loss_account = "7100" } +refused = "depreciation_past_disposal" + +# May was right: its reversal reversed on Sep 2 (August may be closed by +# then; any open day undoes it). +[[events]] +date = "2026-09-02" +description = "Reverse event 8" +reverses = 8 + +[[events]] +date = "2026-06-15" +dispose_asset = { asset = "X", proceeds = "7000.00", proceeds_account = "1000", gain_loss_account = "7100" } + +# Hand check (worked without the code): the runs carry 5,000.00 at Jun 30 +# and now. Held through Jun 15: 5,000.00 + 1,000.00 x 15/30 = 5,500.00, so +# the final depreciation is 500.00 and the disposal clears 5,500.00. Book +# value 6,500.00; gain 7,000.00 - 6,500.00 = 500.00. August's end carries +# May's reversal (accumulated depreciation a 1,000.00 debit, the expense +# 4,500.00) and September's its correction, as they would without the +# disposal. +[[expect]] +query = "disposal" +asset = "X" + +[expect.values] +final_depreciation = "500.00" +catch_up_depreciation = "0.00" +excess_depreciation = "0.00" +accumulated = "5500.00" +book_value = "6500.00" +gain_loss = "500.00" + +[[expect]] +query = "balances" +as_of = "2026-06-30" + +[expect.values] +"1510" = "0.00" +"6200" = "5500.00" +"7100" = "500.00" + +[[expect]] +query = "balances" +as_of = "2026-08-31" + +[expect.values] +"1510" = "1000.00" +"6200" = "4500.00" + +[[expect]] +query = "balances" +as_of = "2026-09-30" + +[expect.values] +"1510" = "0.00" +"6200" = "5500.00" diff --git a/examples/depreciation-disposal-before-a-later-run.toml b/examples/depreciation-disposal-before-a-later-run.toml new file mode 100644 index 0000000..f309041 --- /dev/null +++ b/examples/depreciation-disposal-before-a-later-run.toml @@ -0,0 +1,146 @@ +id = "depreciation-disposal-before-a-later-run" +title = "A disposal dated before a later month's run is refused until that run is reversed" +rule = "A disposal is refused while a run of a month after the disposal's stands: crediting it back at the disposal day would leave the disposal month's accumulated depreciation a debit and its expense short at the month's end. Once that run is reversed, the disposal takes the depreciation of the days held." +status = "CONVENTION" +sources = ["ASC 360-10-35-4"] +tags = ["depreciation", "disposal", "refusal", "later-run", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +[[setup.accounts]] +code = "7100" +name = "Gain or Loss on Disposal" +type = "revenue" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-03" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-04" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-05" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-06" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-07" } + +# Disposed of 2026-06-15 for 7,000.00, after July's run: refused, naming +# July's run. +[[events]] +date = "2026-06-15" +dispose_asset = { asset = "X", proceeds = "7000.00", proceeds_account = "1000", gain_loss_account = "7100" } +refused = "depreciation_past_disposal" + +# July's run reversed, dated in July. +[[events]] +date = "2026-07-31" +description = "Reverse event 9" +reverses = 9 + +[[events]] +date = "2026-06-15" +dispose_asset = { asset = "X", proceeds = "7000.00", proceeds_account = "1000", gain_loss_account = "7100" } + +# Hand check (worked without the code): January through June carry +# 6,000.00 once July's run is reversed. Held through Jun 15: 5,000.00 + +# 1,000.00 x 15/30 = 5,500.00, so June's run posted 500.00 past the +# disposal day. Book value 6,500.00; gain 7,000.00 - 6,500.00 = 500.00. +# Had the disposal taken July's run back at Jun 15 (1,500.00 credited), +# June would end with accumulated depreciation a 1,000.00 debit and +# 4,500.00 of expense. +[[expect]] +query = "disposal" +asset = "X" + +[expect.values] +final_depreciation = "0.00" +catch_up_depreciation = "0.00" +excess_depreciation = "500.00" +accumulated = "6000.00" +book_value = "6500.00" +gain_loss = "500.00" + +[[expect]] +query = "balances" +as_of = "2026-06-30" + +[expect.values] +"1510" = "0.00" +"6200" = "5500.00" +"7100" = "500.00" + +[[expect]] +query = "balances" +as_of = "2026-07-31" + +[expect.values] +"1510" = "0.00" +"6200" = "5500.00" diff --git a/examples/depreciation-disposal-in-final-month.toml b/examples/depreciation-disposal-in-final-month.toml new file mode 100644 index 0000000..be7d3ee --- /dev/null +++ b/examples/depreciation-disposal-in-final-month.toml @@ -0,0 +1,136 @@ +id = "depreciation-disposal-in-final-month" +title = "A disposal in the life's final month depreciates that month by its days held" +rule = "A disposal dated inside an asset's final month takes depreciation for the days held in that month, not the month's whole charge." +status = "CONVENTION" +sources = ["ASC 360-10-35-4"] +tags = ["depreciation", "disposal", "final-month", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +[[setup.accounts]] +code = "7100" +name = "Gain or Loss on Disposal" +type = "revenue" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-03" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-04" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-05" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-06" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-07" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-08" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-09" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-10" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-11" } + +# Disposed of 2026-12-10 for 1,000.00. +[[events]] +date = "2026-12-10" +dispose_asset = { asset = "X", proceeds = "1000.00", proceeds_account = "1000", gain_loss_account = "7100" } + +# Hand check (worked without the code): January..November run, 11,000.00. +# December held through the 10th: 1,000.00 x 10/31 = 322.580... -> 322.58. +# Accumulated 11,322.58; book value 677.42; gain 1,000.00 - 677.42 = +# 322.58. (Before P3: 1,000.00 of depreciation and a 1,000.00 gain.) +[[expect]] +query = "disposal" +asset = "X" + +[expect.values] +final_depreciation = "322.58" +catch_up_depreciation = "0.00" +excess_depreciation = "0.00" +accumulated = "11322.58" +book_value = "677.42" +gain_loss = "322.58" + +[[expect]] +query = "balances" +as_of = "2026-12-31" + +[expect.values] +"1000" = "1000.00" +"1510" = "0.00" +"6200" = "11322.58" +"7100" = "322.58" diff --git a/examples/depreciation-disposal-inside-a-posted-month.toml b/examples/depreciation-disposal-inside-a-posted-month.toml new file mode 100644 index 0000000..5145d75 --- /dev/null +++ b/examples/depreciation-disposal-inside-a-posted-month.toml @@ -0,0 +1,122 @@ +id = "depreciation-disposal-inside-a-posted-month" +title = "A disposal inside a month already run takes back the days after it" +rule = "When a month's depreciation was posted through its end and the asset is disposed of inside that month, the disposal credits back the depreciation for the days after the disposal day, so book value and gain are of the days held." +status = "CONVENTION" +sources = ["ASC 360-10-35-4"] +tags = ["depreciation", "disposal", "mid-month", "excess-depreciation", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +[[setup.accounts]] +code = "7100" +name = "Gain or Loss on Disposal" +type = "revenue" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-03" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-04" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-05" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-06" } + +# Disposed of 2026-06-15 for 7,000.00, after June's run (dated Jun 30). +[[events]] +date = "2026-06-15" +dispose_asset = { asset = "X", proceeds = "7000.00", proceeds_account = "1000", gain_loss_account = "7100" } + +# Hand check (worked without the code): the runs carried 6,000.00. Held +# through Jun 15: 5,000.00 + 1,000.00 x 15/30 = 5,500.00, so 500.00 was +# posted past the disposal day. The disposal entry: Dr accumulated +# depreciation 6,000.00, Dr bank 7,000.00 / Cr equipment 12,000.00, Cr +# depreciation expense 500.00, Cr gain 500.00 (13,000.00 a side). Book +# value 6,500.00; gain 7,000.00 - 6,500.00 = 500.00. +[[expect]] +query = "disposal" +asset = "X" + +[expect.values] +final_depreciation = "0.00" +catch_up_depreciation = "0.00" +excess_depreciation = "500.00" +accumulated = "6000.00" +book_value = "6500.00" +gain_loss = "500.00" + +[[expect]] +query = "balances" +as_of = "2026-06-30" + +[expect.values] +"1510" = "0.00" +"6200" = "5500.00" +"7100" = "500.00" diff --git a/examples/depreciation-disposal-mid-month.toml b/examples/depreciation-disposal-mid-month.toml new file mode 100644 index 0000000..d581198 --- /dev/null +++ b/examples/depreciation-disposal-mid-month.toml @@ -0,0 +1,134 @@ +id = "depreciation-disposal-mid-month" +title = "A disposed asset depreciates through its disposal day, and its schedule stops there" +rule = "An asset disposed of mid-month is depreciated through the disposal day, held, and its schedule, accumulated depreciation and book value afterwards are what the disposal took." +status = "CONVENTION" +sources = ["ASC 360-10-35-4"] +tags = ["depreciation", "disposal", "mid-month", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +[[setup.accounts]] +code = "7100" +name = "Gain or Loss on Disposal" +type = "revenue" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-03" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-04" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-05" } + +# Disposed of 2026-06-15 for 7,000.00. +[[events]] +date = "2026-06-15" +dispose_asset = { asset = "X", proceeds = "7000.00", proceeds_account = "1000", gain_loss_account = "7100" } + +# Hand check (worked without the code): January..May run, 5 x 1,000.00 = +# 5,000.00. June held through the 15th: 1,000.00 x 15/30 = 500.00, the +# final depreciation. Accumulated 5,500.00; book value 12,000.00 - +# 5,500.00 = 6,500.00; gain 7,000.00 - 6,500.00 = 500.00. +[[expect]] +query = "disposal" +asset = "X" + +[expect.values] +final_depreciation = "500.00" +catch_up_depreciation = "0.00" +excess_depreciation = "0.00" +accumulated = "5500.00" +book_value = "6500.00" +gain_loss = "500.00" + +# The disposed asset's schedule ends on the disposal day, whatever date it +# is asked through. +[[expect]] +query = "depreciation_schedule" +asset = "X" +as_of = "2030-12-31" + +[expect.values] +"2026-06" = "500.00" +accumulated = "5500.00" +book_value = "6500.00" + +# The books: depreciation expense 5,500.00 (debit), the gain 500.00 +# (credit), accumulated depreciation and equipment cleared, the bank +# 7,000.00 (debit). +[[expect]] +query = "balances" +as_of = "2026-06-30" + +[expect.values] +"1000" = "7000.00" +"1500" = "0.00" +"1510" = "0.00" +"6200" = "5500.00" +"7100" = "500.00" diff --git a/examples/depreciation-final-month-by-days.toml b/examples/depreciation-final-month-by-days.toml new file mode 100644 index 0000000..3c3a54f --- /dev/null +++ b/examples/depreciation-final-month-by-days.toml @@ -0,0 +1,93 @@ +id = "depreciation-final-month-by-days" +title = "Straight-line depreciation by the day through the life's final month" +rule = "Depreciation through a day of an asset's final month is the depreciation before that month plus the final month's share for the days held, never the whole remaining cost." +status = "CONVENTION" +sources = ["ASC 360-10-35-4"] +tags = ["depreciation", "straight-line", "final-month", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01. Its life is +# 2026-01-01 through 2026-12-31; December is its final month. +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +# Hand check (worked without the code): 12,000.00 / 12 = 1,000.00 a month. +# Through Dec 15: January..November, 11 x 1,000.00 = 11,000.00, plus +# December's 1,000.00 x 15/31 = 483.870... -> 483.87 (half away from zero). +# Accumulated 11,483.87; book value 12,000.00 - 11,483.87 = 516.13. +[[expect]] +query = "depreciation_schedule" +asset = "X" +as_of = "2026-12-15" + +[expect.values] +"2026-11" = "1000.00" +"2026-12" = "483.87" +accumulated = "11483.87" +book_value = "516.13" + +# Hand check: through the life's last day, the whole of it. +[[expect]] +query = "depreciation_schedule" +asset = "X" +as_of = "2026-12-31" + +[expect.values] +"2026-12" = "1000.00" +accumulated = "12000.00" +book_value = "0.00" diff --git a/examples/depreciation-final-month-drift.toml b/examples/depreciation-final-month-drift.toml new file mode 100644 index 0000000..e871319 --- /dev/null +++ b/examples/depreciation-final-month-drift.toml @@ -0,0 +1,98 @@ +id = "depreciation-final-month-drift" +title = "The final month of a mid-month life takes what the months before it leave, evenly over its days" +rule = "When a life does not start on the 1st, its final month is charged the depreciable amount less what the months before it took, spread evenly over the final month's days." +status = "CONVENTION" +sources = ["ASC 360-10-35-4"] +tags = ["depreciation", "straight-line", "final-month", "month-length-drift", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +# Cost 1,030.00, salvage 100.00 (930.00 to depreciate), 3 months from +# 2026-01-15: the life is 2026-01-15 through 2026-04-14. +[[setup.assets]] +id = "D" +description = "Asset D" +cost = "1030.00" +salvage = "100.00" +life_months = 3 +in_service = "2026-01-15" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-15" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "1030.00" }, + { account = "3000", side = "credit", amount = "1030.00" }, +] + +[[events]] +date = "2026-01-15" +description = "Bought asset D" +lines = [ + { account = "1500", side = "debit", amount = "1030.00" }, + { account = "1000", side = "credit", amount = "1030.00" }, +] + +# Hand check (worked without the code): 930.00 / 3 = 310.00 a month. +# January holds 17 of its 31 days: 310.00 x 17/31 = 170.00. February and +# March are whole: 310.00 each. Before April: 790.00. April holds 14 days +# (1-14) and is charged what is left, 930.00 - 790.00 = 140.00, evenly: +# 10.00 a day (January's own daily rate, 310.00/31). Through Apr 13: +# 130.00; accumulated 920.00; book value 1,030.00 - 920.00 = 110.00. +[[expect]] +query = "depreciation_schedule" +asset = "D" +as_of = "2026-04-13" + +[expect.values] +"2026-01" = "170.00" +"2026-02" = "310.00" +"2026-03" = "310.00" +"2026-04" = "130.00" +accumulated = "920.00" +book_value = "110.00" + +# Through Apr 14, the life's last day: April 140.00, accumulated 930.00, +# book value 100.00 (the salvage, never depreciated). +[[expect]] +query = "depreciation_schedule" +asset = "D" +as_of = "2026-04-14" + +[expect.values] +"2026-04" = "140.00" +accumulated = "930.00" +book_value = "100.00" diff --git a/examples/depreciation-reversal-before-its-run-is-refused.toml b/examples/depreciation-reversal-before-its-run-is-refused.toml new file mode 100644 index 0000000..7097a2d --- /dev/null +++ b/examples/depreciation-reversal-before-its-run-is-refused.toml @@ -0,0 +1,114 @@ +id = "depreciation-reversal-before-its-run-is-refused" +title = "A depreciation run can't be reversed on a day before its own" +rule = "A correction comes after what it corrects: a depreciation run, or a reversal of one, can't be reversed on a day before its own. A run of March reversed on Jan 31 would leave January and February with negative depreciation." +status = "AUTHORITY" +sources = ["Pacioli 1494, ch. 31"] +tags = ["depreciation", "reversal", "refusal", "p3-defect-22"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +[[setup.accounts]] +code = "7100" +name = "Gain or Loss on Disposal" +type = "revenue" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-03" } + +# March's run (dated Mar 31) reversed on Feb 10: refused, naming its date. +[[events]] +date = "2026-02-10" +description = "Reverse event 5" +reverses = 5 +refused = "entry_irreversible" + +# On its own day it is taken. +[[events]] +date = "2026-03-31" +description = "Reverse event 5" +reverses = 5 + +# Hand check (worked without the code): January and February carry +# 2,000.00 at February's end, never 1,000.00 (with March's 1,000.00 taken +# away on Feb 10); March's end carries 2,000.00 (its run and its reversal +# both dated Mar 31). +[[expect]] +query = "balances" +as_of = "2026-02-28" + +[expect.values] +"1510" = "-2000.00" +"6200" = "2000.00" + +[[expect]] +query = "balances" +as_of = "2026-03-31" + +[expect.values] +"1510" = "-2000.00" +"6200" = "2000.00" diff --git a/examples/depreciation-run-reversed-after-its-month.toml b/examples/depreciation-run-reversed-after-its-month.toml new file mode 100644 index 0000000..905404d --- /dev/null +++ b/examples/depreciation-run-reversed-after-its-month.toml @@ -0,0 +1,121 @@ +id = "depreciation-run-reversed-after-its-month" +title = "A run reversed after its month ended is caught up in the reversal's month, never posted twice" +rule = "What the books carry is read entry by entry, each in its own month: a run reversed after its month ended still carried that month at its end, so running the month again posts nothing, and the run of the reversal's month catches the month up." +status = "CHOICE" +sources = ["ASC 360-10-35-4", "DECISION: plan/served.md P3, Settled in design (2026-10-20)", "docs/spec/rules.md B-9"] +tags = ["depreciation", "catch-up", "reversal", "p3"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1500" +name = "Equipment" +type = "asset" + +[[setup.accounts]] +code = "1510" +name = "Accumulated Depreciation" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6200" +name = "Depreciation Expense" +type = "expense" + +# Asset X: 12,000.00, no salvage, 12 months from 2026-01-01 (1,000.00 a +# month; December the final month). +[[setup.assets]] +id = "X" +description = "Asset X" +cost = "12000.00" +salvage = "0.00" +life_months = 12 +in_service = "2026-01-01" +method = "straight_line" +cost_account = "1500" +accumulated_account = "1510" +expense_account = "6200" + +[[events]] +date = "2026-01-01" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "12000.00" }, + { account = "3000", side = "credit", amount = "12000.00" }, +] + +[[events]] +date = "2026-01-01" +description = "Bought asset X" +lines = [ + { account = "1500", side = "debit", amount = "12000.00" }, + { account = "1000", side = "credit", amount = "12000.00" }, +] + +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +# January's run reversed on 2026-02-02, after January ended. +[[events]] +date = "2026-02-02" +description = "Reverse event 3" +reverses = 3 + +# January run again: nothing is due (Jan 31 carried January). +[[events]] +run_depreciation = { asset = "X", period = "2026-01" } + +[[events]] +run_depreciation = { asset = "X", period = "2026-02" } + +# Hand check (worked without the code): January's run (1,000.00, dated Jan +# 31) stood on Jan 31, so the books carried the schedule there, 1,000.00; +# running January again posts 0.00 (were it to post, Jan 31 would carry +# 2,000.00). The reversal took 1,000.00 away on Feb 2, so February's run +# posts the schedule through Feb 28, 2,000.00, less the 0.00 then carried: +# 2,000.00, of which 1,000.00 is January caught up. +[[expect]] +query = "run" +event = 5 + +[expect.values] +amount = "0.00" +caught_up = "0.00" + +[[expect]] +query = "run" +event = 6 + +[expect.values] +amount = "2000.00" +caught_up = "1000.00" + +[[expect]] +query = "balances" +as_of = "2026-01-31" + +[expect.values] +"1510" = "-1000.00" +"6200" = "1000.00" + +[[expect]] +query = "balances" +as_of = "2026-02-28" + +[expect.values] +"1510" = "-2000.00" +"6200" = "2000.00" diff --git a/examples/schedule-daily-deferred-revenue.toml b/examples/schedule-daily-deferred-revenue.toml new file mode 100644 index 0000000..d1cdb0c --- /dev/null +++ b/examples/schedule-daily-deferred-revenue.toml @@ -0,0 +1,75 @@ +id = "schedule-daily-deferred-revenue" +title = "Deferred revenue is earned by the day, to the cent" +rule = "Revenue collected in advance for a service over time is recognized as each day of the service passes, one posting per calendar month, the last month taking what the others leave." +status = "CHOICE" +sources = ["ASC 606-10-25-23", "DECISION: plan/served.md D-11, D-37 (owner, 2026-10-20)"] +tags = ["schedules", "deferred-revenue", "daily", "p3-defect-9"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "2400" +name = "Deferred Revenue" +type = "liability" + +[[setup.accounts]] +code = "4000" +name = "Subscription Revenue" +type = "revenue" + +# 1,000.00 collected for service 2026-01-05 through 2026-04-04. +[[setup.schedules]] +id = "SUB" +kind = "deferred_revenue" +description = "Quarterly plan, a customer" +total = "1000.00" +start = "2026-01-05" +months = 3 +convention = "daily" +balance_account = "2400" +pl_account = "4000" + +[[events]] +date = "2026-01-05" +description = "Collected a quarter up front" +lines = [ + { account = "1000", side = "debit", amount = "1000.00" }, + { account = "2400", side = "credit", amount = "1000.00" }, +] + +[[events]] +run_schedules = { through = "2026-04-30" } + +# Hand check (worked without the code): January holds 27 of its 31 days +# (5-31); February and March are whole; April holds days 1-4 and takes the +# rest. Cumulative at each month end: 1,000 x (27/31)/3 = 290.3226 -> +# 290.32; 1,000 x (27/31 + 1)/3 = 623.6559 -> 623.66; 1,000 x (27/31 + +# 2)/3 = 956.9892 -> 956.99; then 1,000.00. Months: 290.32, 333.34, +# 333.33, 43.01. +[[expect]] +query = "schedule_plan" +schedule = "SUB" + +[expect.values] +"2026-01-31" = "290.32" +"2026-02-28" = "333.34" +"2026-03-31" = "333.33" +"2026-04-30" = "43.01" + +# Earned through March: 956.99; still owed for April 1-4: 43.01. +[[expect]] +query = "balances" +as_of = "2026-03-31" + +[expect.values] +"2400" = "43.01" +"4000" = "956.99" diff --git a/examples/schedule-daily-small-first-and-last-month.toml b/examples/schedule-daily-small-first-and-last-month.toml new file mode 100644 index 0000000..a786a38 --- /dev/null +++ b/examples/schedule-daily-small-first-and-last-month.toml @@ -0,0 +1,107 @@ +id = "schedule-daily-small-first-and-last-month" +title = "A prepaid expense spread by the day: a small first and last month around full ones" +rule = "A prepaid expense covering whole months from a mid-month start is expensed by the day: each full month an equal share, the first month its days' share, and the last month what the others leave, one posting per calendar month touched." +status = "CHOICE" +sources = ["ASC 340-10-05-4", "ASC 210-10-45-1(g)", "DECISION: plan/served.md D-11, D-37 (owner, 2026-10-20)"] +tags = ["schedules", "prepaid-expense", "daily", "p3-defect-9"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +rounding = "half-away-from-zero" + +[[setup.accounts]] +code = "1000" +name = "Bank" +type = "asset" + +[[setup.accounts]] +code = "1300" +name = "Prepaid Expenses" +type = "asset" + +[[setup.accounts]] +code = "3000" +name = "Capital" +type = "equity" + +[[setup.accounts]] +code = "6300" +name = "Insurance" +type = "expense" + +# 1,200.00 of insurance covering 2026-03-15 through 2027-03-14. +[[setup.schedules]] +id = "INS" +kind = "prepaid_expense" +description = "Liability insurance" +total = "1200.00" +start = "2026-03-15" +months = 12 +convention = "daily" +balance_account = "1300" +pl_account = "6300" + +[[events]] +date = "2026-03-15" +description = "Capital" +lines = [ + { account = "1000", side = "debit", amount = "1200.00" }, + { account = "3000", side = "credit", amount = "1200.00" }, +] + +[[events]] +date = "2026-03-15" +description = "Paid a year of insurance" +lines = [ + { account = "1300", side = "debit", amount = "1200.00" }, + { account = "1000", side = "credit", amount = "1200.00" }, +] + +[[events]] +run_schedules = { through = "2027-03-31" } + +# Hand check (worked without the code): 1,200.00 / 12 = 100.00 a month. +# March 2026 holds 17 of its 31 days (15-31): 100.00 x 17/31 = 54.838... +# -> 54.84. April 2026 through February 2027 are whole: 100.00 each, 11 of +# them (cumulatives 154.84 ... 1,154.84). March 2027 holds days 1-14 and +# takes what is left: 1,200.00 - 1,154.84 = 45.16 (= 100.00 x 14/31 to the +# cent). Thirteen month ends for a twelve-month policy. +[[expect]] +query = "schedule_plan" +schedule = "INS" + +[expect.values] +"2026-03-31" = "54.84" +"2026-04-30" = "100.00" +"2026-05-31" = "100.00" +"2026-06-30" = "100.00" +"2026-07-31" = "100.00" +"2026-08-31" = "100.00" +"2026-09-30" = "100.00" +"2026-10-31" = "100.00" +"2026-11-30" = "100.00" +"2026-12-31" = "100.00" +"2027-01-31" = "100.00" +"2027-02-28" = "100.00" +"2027-03-31" = "45.16" + +# Through June 2026 the books have expensed 54.84 + 3 x 100.00 = 354.84, +# and 845.16 is still prepaid. +[[expect]] +query = "balances" +as_of = "2026-06-30" + +[expect.values] +"1300" = "845.16" +"6300" = "354.84" + +# Through March 2027, all of it. +[[expect]] +query = "balances" +as_of = "2027-03-31" + +[expect.values] +"1300" = "0.00" +"6300" = "1200.00" diff --git a/examples/schedule-equal-months-start-on-the-first.toml b/examples/schedule-equal-months-start-on-the-first.toml new file mode 100644 index 0000000..c4a8843 --- /dev/null +++ b/examples/schedule-equal-months-start-on-the-first.toml @@ -0,0 +1,63 @@ +id = "schedule-equal-months-start-on-the-first" +title = "An equal-months schedule starts on the 1st of a month" +rule = "A schedule that spreads its total in equal months must start on the 1st of a month, for prepaid expenses and deferred revenue alike, so it never recognizes days it does not cover." +status = "CHOICE" +sources = ["ASC 606-10-25-23", "DECISION: plan/served.md D-37 (owner, 2026-10-20)"] +tags = ["schedules", "monthly-even", "refusal", "p3-defect-9"] + +[setup] +currency = "USD" +fiscal_year_start = "01-01" +basis = "accrual" +# Equal months keep the rounding schedules had before P3 (each month the +# total over the months to the cent, ties to even; the last month takes +# what the others leave): a posted month's replay re-derives its plan. +rounding = "half-even" + +[[setup.accounts]] +code = "1300" +name = "Prepaid Expenses" +type = "asset" + +[[setup.accounts]] +code = "2400" +name = "Deferred Revenue" +type = "liability" + +[[setup.accounts]] +code = "4000" +name = "Subscription Revenue" +type = "revenue" + +[[setup.accounts]] +code = "6300" +name = "Insurance" +type = "expense" + +# Equal months from March 15: refused (it would recognize all of March's +# 100.00, 45.16 of it, March 1 to 14 at 100.00 × 14/31, before the service +# began). The refusal names the 1st. +[[events]] +register_schedule = { id = "MID", kind = "deferred_revenue", description = "Annual plan", total = "1200.00", start = "2026-03-15", months = 12, convention = "monthly_even", balance_account = "2400", pl_account = "4000" } +refused = "invalid_schedule" + +# The same for a prepaid expense: one rule for both. +[[events]] +register_schedule = { id = "MIDP", kind = "prepaid_expense", description = "Insurance", total = "1200.00", start = "2026-03-15", months = 12, convention = "monthly_even", balance_account = "1300", pl_account = "6300" } +refused = "invalid_schedule" + +# From the 1st it is taken: twelve equal months. +[[events]] +register_schedule = { id = "FIRST", kind = "deferred_revenue", description = "Annual plan", total = "1200.00", start = "2026-03-01", months = 12, convention = "monthly_even", balance_account = "2400", pl_account = "4000" } + +# Hand check (worked without the code): 1,200.00 / 12 = 100.00, every +# month, the last absorbing no rounding. +[[expect]] +query = "schedule_plan" +schedule = "FIRST" + +[expect.values] +"2026-03-31" = "100.00" +"2026-09-30" = "100.00" +"2027-02-28" = "100.00" +total = "1200.00"