jaz-clio 5.74.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +8 -8
  2. package/assets/skills/api/SKILL.md +3 -36
  3. package/assets/skills/cli/SKILL.md +18 -28
  4. package/assets/skills/cli/references/agent-gotchas.md +2 -2
  5. package/assets/skills/cli/references/command-catalog.md +15 -31
  6. package/assets/skills/cli/references/common-workflows.md +19 -28
  7. package/assets/skills/conversion/SKILL.md +1 -1
  8. package/assets/skills/jaz-kit/SKILL.md +1 -1
  9. package/assets/skills/jaz-kit/references/flows.md +2 -2
  10. package/assets/skills/jaz-pseudo-sql/SKILL.md +1 -1
  11. package/assets/skills/jobs/SKILL.md +42 -42
  12. package/assets/skills/jobs/references/audit-prep.md +8 -8
  13. package/assets/skills/jobs/references/bank-match.md +1 -1
  14. package/assets/skills/jobs/references/bank-recon.md +6 -6
  15. package/assets/skills/jobs/references/building-blocks.md +49 -30
  16. package/assets/skills/jobs/references/credit-control.md +13 -12
  17. package/assets/skills/jobs/references/document-collection.md +6 -6
  18. package/assets/skills/jobs/references/fa-review.md +28 -29
  19. package/assets/skills/jobs/references/gst-vat-filing.md +3 -3
  20. package/assets/skills/jobs/references/month-end-close.md +37 -33
  21. package/assets/skills/jobs/references/payment-run.md +3 -4
  22. package/assets/skills/jobs/references/quarter-end-close.md +20 -18
  23. package/assets/skills/jobs/references/supplier-recon.md +5 -5
  24. package/assets/skills/jobs/references/year-end-close.md +31 -21
  25. package/assets/skills/transaction-recipes/SKILL.md +54 -117
  26. package/assets/skills/transaction-recipes/references/accrued-expenses.md +86 -61
  27. package/assets/skills/transaction-recipes/references/asset-disposal.md +135 -93
  28. package/assets/skills/transaction-recipes/references/bad-debt-provision.md +87 -65
  29. package/assets/skills/transaction-recipes/references/bank-loan.md +104 -70
  30. package/assets/skills/transaction-recipes/references/building-blocks.md +168 -72
  31. package/assets/skills/transaction-recipes/references/capital-wip.md +9 -8
  32. package/assets/skills/transaction-recipes/references/declining-balance.md +91 -64
  33. package/assets/skills/transaction-recipes/references/deferred-revenue.md +105 -68
  34. package/assets/skills/transaction-recipes/references/dividend.md +100 -64
  35. package/assets/skills/transaction-recipes/references/employee-accruals.md +111 -73
  36. package/assets/skills/transaction-recipes/references/fixed-deposit.md +116 -63
  37. package/assets/skills/transaction-recipes/references/fx-revaluation.md +28 -19
  38. package/assets/skills/transaction-recipes/references/hire-purchase.md +45 -44
  39. package/assets/skills/transaction-recipes/references/ifrs16-lease.md +108 -73
  40. package/assets/skills/transaction-recipes/references/intercompany.md +8 -6
  41. package/assets/skills/transaction-recipes/references/prepaid-amortization.md +104 -65
  42. package/assets/skills/transaction-recipes/references/provisions.md +116 -64
  43. package/assets/templates/platform-rules/jaz-agent-rules.md +10 -9
  44. package/cli.mjs +529 -607
  45. package/package.json +1 -1
@@ -1,25 +1,27 @@
1
- # Recipe: Declining Balance Depreciation (engine name: `depreciation`)
1
+ # Recipe: Declining Balance Depreciation (calculator type: `depreciation`)
2
2
 
3
- > Canonical recipe for non-straight-line depreciation methods (double declining balance / DDB, 150% declining balance / 150DB) where Jaz native FA can't auto-handle. Engine emits N future-dated DRAFT depreciation journals upfront. **Do NOT register the asset in Jaz native FA register** (would trigger duplicate SL depreciation).
3
+ > Canonical recipe for non-straight-line depreciation methods (double declining balance / DDB, 150% declining balance / 150DB), which the Jaz native FA register does not post. One depreciation journal per period, in one capsule. **Do NOT register the asset in the Jaz FA register with straight-line depreciation** (it would post duplicate SL depreciation). You calculate, create the capsule and post each journal yourself (see `building-blocks.md` § The three-step flow).
4
4
 
5
- ## Tools, recipes, calculators this recipe uses
5
+ ## Tools and calculator this recipe uses
6
6
 
7
- ### Recipe engine entry point
8
- - **`plan_recipe(recipe: 'depreciation', method: 'ddb' | '150db', ...)`** (used in step 2): returns RecipePlan with N future-dated depreciation journals (one per period in the schedule), capsule shape, required accounts.
9
- - **`execute_recipe(recipe: 'depreciation', ...)`** (used in step 4): posts N future-dated DRAFT journals all attached to the same capsule. NO fixed-asset step (recipe assumes asset is tracked in CoA only, not in FA register).
7
+ ### Calculator (offline, posts nothing)
8
+ - **MCP: `calculate(type: 'depreciation', cost, salvageValue, usefulLifeYears, method, frequency, currency)`** (step 1). `method` is `ddb` (the default), `150db` or `sl`; `frequency` is `annual` (the default) or `monthly`.
9
+ - **CLI: `clio calc depreciation --cost <c> --salvage <s> --life <years> --method <ddb|150db|sl> --frequency <annual|monthly> --currency <code> --json`** (step 1): the same result.
10
10
 
11
- ### Calculator (cross-check, no API key needed)
12
- - **`clio calc depreciation --cost <c> --salvage <s> --life <years> --method <ddb|150db|sl> --frequency <annual|monthly> --json`** (used in step 1): full depreciation schedule. Returns `{ totalDepreciation, schedule[n] }` where each row carries `period`, `openingBookValue`, `depreciationAmount`, `accumulatedDepreciation`, `closingBookValue`. Final period absorbs rounding to land at salvage value exactly.
11
+ The depreciation calculator takes no start date: its steps are never dated. You date each journal yourself (step 4).
13
12
 
14
- ### Tools (jaz-api / direct)
15
- - **`search_capsules(filter: {title: {eq: <capsule.name>}})`**: step 0 idempotency check. One depreciation capsule per asset; duplicate setup is almost always an error.
16
- - **`search_accounts(filter: {name: {in: ['Vehicles', 'Accumulated Depreciation (Vehicles)', 'Depreciation Expense']}})`** (step 3): confirm the asset, contra-asset, and expense GL accounts exist.
17
- - **`generate_trial_balance(endDate: <date>)`** (step 5): verify NBV matches schedule.
18
- - **`bulk_update_journals(items: [{resourceId: <id>, saveAsDraft: false}, ...])`** (step 5 monthly): finalize this period's pre-emitted DRAFT depreciation journal.
13
+ ### Posting tools
14
+ - **`list_capsule_types` / `create_capsule_type(displayName: 'Depreciation')` / `create_capsule(...)`** (step 3).
15
+ - **`create_journal(...)`** (step 4): one per period. The amount changes as book value falls, so a fixed-amount schedule does not fit.
16
+
17
+ ### Lookup and verification tools
18
+ - **`search_capsules(filter: {title: {eq: <capsule title>}})`**: step 0 idempotency check. One depreciation capsule per asset; duplicate setup is almost always an error.
19
+ - **`search_accounts(filter: {name: {in: ['Vehicles', 'Accumulated Depreciation (Vehicles)', 'Depreciation Expense']}})`** (step 2): confirm the asset, contra-asset, and expense GL accounts exist.
20
+ - **`generate_trial_balance(endDate: <date>)`** (step 5): verify NBV matches the schedule.
19
21
 
20
22
  ### Cross-references
21
- - Operational context: invoked during month-end close (only when an asset uses a non-SL method; Jaz native FA handles SL automatically). For SL: `create_fixed_asset` directly via `fixed-assets` tool family; do NOT use this recipe.
22
- - Sibling: `asset-disposal.md` for end-of-life de-recognition; `ifrs16-lease.md` (lease engine) which uses SL depreciation via the FA register because ROU is always SL under IFRS 16.
23
+ - Operational context: invoked during month-end close (only when an asset uses a non-SL method; Jaz native FA handles SL automatically). For SL: `create_fixed_asset` directly; do NOT use this recipe.
24
+ - Sibling: `asset-disposal.md` for end-of-life de-recognition; `ifrs16-lease.md`, which uses SL depreciation via the FA register because the ROU asset is depreciated straight-line there.
23
25
  - IFRS / accounting context: IAS 16.62, depreciation method should reflect the pattern of consumption of the asset's economic benefits. DDB / 150DB are valid alternatives to SL when usage is front-loaded (vehicles, technology). NOT for buildings, land improvements (always SL).
24
26
 
25
27
  ---
@@ -29,104 +31,129 @@
29
31
  ### Step 0: Idempotency check
30
32
 
31
33
  ```
32
- search_capsules(filter: {title: {eq: 'DDB Depreciation — 5 years (Delivery Vehicle FY2025)'}})
34
+ search_capsules(filter: {title: {eq: 'DDB Depreciation, 5 years, Delivery Vehicle TRK-001'}})
33
35
  ```
34
36
 
35
- If a result returns: halt and surface "Depreciation capsule for asset `<name>` already exists. Re-running would create duplicate depreciation journals. Confirm: if revising the depreciation schedule (changed useful life or salvage), close the existing capsule, reverse remaining DRAFT journals via `delete_journal`, then re-execute."
37
+ If a result returns: halt and surface "Depreciation capsule for asset `<name>` already exists. Re-posting would create duplicate depreciation journals. Confirm: if revising the depreciation schedule (changed useful life or salvage), delete the remaining DRAFT journals via `delete_journal`, then post the revised periods into the same capsule."
38
+
39
+ ### Step 1: Calculate
36
40
 
37
- ### Step 1: Independent cross-check (calculator)
41
+ ```
42
+ calculate(
43
+ type: 'depreciation',
44
+ cost: 50000,
45
+ salvageValue: 5000,
46
+ usefulLifeYears: 5,
47
+ method: 'ddb',
48
+ frequency: 'monthly',
49
+ currency: 'SGD'
50
+ )
51
+ ```
38
52
 
39
53
  ```
40
54
  clio calc depreciation --cost 50000 --salvage 5000 --life 5 --method ddb --frequency monthly --currency SGD --json
41
55
  ```
42
56
 
43
- Returns: `{ totalDepreciation: 45000, schedule: [{period: 1, openingBookValue: 50000, depreciationAmount: 833.33, accumulatedDepreciation: 833.33, closingBookValue: 49166.67}, ...60] }`. For DDB: amounts decline each period (40% annual rate × declining book value × 1/12 monthly). Final period absorbs rounding to land at exactly salvage value ($5,000).
57
+ Returns `{ totalDepreciation: 45000, schedule: [{ period, date, openingBookValue, ddbAmount, slAmount, methodUsed, depreciation, closingBookValue, journal }, ...60], blueprint }`. `date` is `null` on every row.
44
58
 
45
59
  DDB rate formula: `2 / useful-life-years = 40% annual` for 5-year life.
46
60
  150DB rate: `1.5 / useful-life-years = 30% annual` for 5-year life.
47
61
 
48
- Save schedule to `workpapers/<period>/depreciation-<asset-id>.json` for the workpaper record (audit will sample-test).
62
+ For this asset the annual charges are 20,000 / 12,000 / 7,200 / 4,320 / 1,480. Each year the calculator computes the declining-balance charge (`ddbAmount`) and straight-line over the remaining life (`slAmount`), and switches to SL once SL is at least the declining-balance charge or the declining-balance charge would take book value below salvage; `methodUsed` shows which applied. Here it switches in year 5 (the DDB charge would breach the $5,000 floor), so the book value lands on the salvage value exactly and never below it.
63
+
64
+ With `frequency: 'monthly'` each year's charge is spread evenly over its 12 months (1,666.67 a month in year 1, 1,000.00 in year 2, ...), and the last month of each year absorbs the rounding.
65
+
66
+ **Monthly posting reads `schedule[]`, not `blueprint.steps`.** For DDB and 150DB the blueprint always carries the ANNUAL steps (5 here), whatever the frequency. The 60 monthly entries are the `journal` on each `schedule` row.
67
+
68
+ Save the schedule to `workpapers/<period>/depreciation-<asset-id>.json` for the workpaper record (audit will sample-test).
49
69
 
50
- ### Step 2: Plan the recipe
70
+ ### Step 2: Resolve accounts
71
+
72
+ The calculator's `Depreciation Expense` and `Accumulated Depreciation` are labels. Map each to the real account:
73
+ - `search_accounts(filter: {name: {in: ['Vehicles', 'Accumulated Depreciation (Vehicles)', 'Depreciation Expense']}})`. If one is missing: halt. Suggested classifications: asset GL → `Non-current Asset`; accumulated depreciation → `Non-current Asset` (contra); expense → `Operating Expense`.
74
+
75
+ NO contact (depreciation has no counterparty). NO bank account.
76
+
77
+ ### Step 3: Create the capsule
51
78
 
52
79
  ```
53
- plan_recipe(
54
- // Accounts, capsule and counterparty are not plan_recipe params: execute_recipe resolves accounts from the CoA and takes bankAccountName / contactName.
55
- recipe: 'depreciation',
56
- cost: 50000,
57
- salvageValue: 5000,
58
- usefulLifeYears: 5,
59
- method: 'ddb',
60
- frequency: 'monthly',
61
- startDate: '2025-01-01',
62
- currency: 'SGD'
80
+ list_capsule_types()
81
+ create_capsule(
82
+ capsuleTypeResourceId: <id of 'Depreciation'>,
83
+ title: 'DDB Depreciation, 5 years, Delivery Vehicle TRK-001',
84
+ description: <blueprint.capsuleDescription>
63
85
  )
64
86
  ```
65
87
 
66
- Returns `RecipePlan` with `requiredAccounts: ['Vehicles', 'Accumulated Depreciation (Vehicles)', 'Depreciation Expense']`, `needsContact: false`, `needsBankAccount: false`, `steps[1..60]`: 60 future-dated DRAFT depreciation journals (one per month). Each is Dr Depreciation Expense / Cr Accumulated Depreciation, varying amount per the DDB schedule.
67
-
68
- ### Step 3: Resolve dependencies
88
+ If `Depreciation` is not in the list: `create_capsule_type(displayName: 'Depreciation')` first.
69
89
 
70
- For each account in `requiredAccounts`:
71
- - `search_accounts(filter: {name: {eq: <accountName>}})`. If empty: halt. Suggested classifications: asset GL → `Non-current Asset`; accumulated depreciation → `Non-current Asset` (contra); expense → `Operating Expense`.
90
+ ### Step 4: Post the journals
72
91
 
73
- NO contact resolution (depreciation has no counterparty). NO bank account resolution.
74
-
75
- ### Step 4: Execute
92
+ One `create_journal` per period, dated by you (month-end for monthly, year-end for annual), with that period's amount:
76
93
 
77
94
  ```
78
- execute_recipe(recipe: 'depreciation', ...same args...) // accounts auto-resolved from CoA; pass `bankAccountName` / `contactName` for fuzzy resolve
95
+ create_journal(
96
+ valueDate: '2025-01-31',
97
+ reference: 'DEP-TRK-001-01',
98
+ journalEntries: [
99
+ { accountResourceId: <Depreciation Expense>, type: 'DEBIT', amount: 1666.67, description: 'DDB depreciation, month 1 of 60' },
100
+ { accountResourceId: <Accumulated Depreciation (Vehicles)>, type: 'CREDIT', amount: 1666.67, description: 'DDB depreciation, month 1 of 60' }
101
+ ],
102
+ saveAsDraft: false,
103
+ capsuleResourceId: <capsule id>
104
+ )
79
105
  ```
80
106
 
81
- Returns: `{ capsule: {resourceId, type, title}, steps: [{step, action, status, resourceId}, ...60], summary: {total: 60, created: 60} }`. The recipe creates **60 future-dated DRAFT depreciation journals** (one per month for 5 years), all attached to the same capsule. Each journal is dated end-of-month for its period.
107
+ Post each period's journal at that period's close, or post them all now as drafts and finalize one per period. Agree the cadence with the practitioner; the reference pattern (`DEP-TRK-001-01` to `-60`) is what finds a period's journal later.
82
108
 
83
- **Critical:** Do NOT also call `create_fixed_asset` for this asset. The asset is tracked via the CoA only (its cost sits in `Vehicles` account; its NBV is `cost − accumulated depreciation` per TB). Registering in Jaz FA would trigger duplicate SL depreciation, double-counting.
109
+ **Critical:** Do NOT also register this asset in the FA register with `depreciationMethod: 'STRAIGHT_LINE'`. The asset is tracked via the CoA (its cost sits in the `Vehicles` account; its NBV is `cost − accumulated depreciation` per TB). A straight-line registration would post duplicate depreciation, double-counting.
84
110
 
85
- If the asset MUST be in the FA register for reporting reasons (e.g. fixed-assets-summary report grouping): use Jaz FA with `depreciationMethod: 'sl'` BUT mark the asset as "manually depreciated" via custom field, and immediately archive the auto-emitted SL depreciation journals (risky pattern, prefer recipe-only).
111
+ If the asset MUST be in the FA register for reporting reasons (e.g. the fixed-assets summary report): register it with `depreciationMethod: 'NO_DEPRECIATION'`, so the register lists the asset and posts nothing. The register's book value then stays at cost; the journals in this capsule are the depreciation record.
86
112
 
87
113
  ### Step 5: Monthly action (during monthly-close)
88
114
 
89
- For each month after recipe execution, this period's DRAFT depreciation journal already exists. Monthly close action:
115
+ Post this period's journal, or find it by its reference and finalize it:
90
116
 
91
117
  ```
92
- **STOP: not selectable by filter.** Journals carry no capsule or fixed-asset link in either direction (`JournalFilter` declares neither; a journal row has no such field even at `view: 'full'`; `GET /capsules/{id}` returns only a `totalTransactions` count, measured 2026-09-07). A date+status search returns every matching DRAFT in the org, so it must never feed `bulk_update_journals` or `delete_journal`. Surface the capsule and its expected count to the practitioner and let them identify the journals.
118
+ search_journals(filter: {reference: {eq: 'DEP-TRK-001-03'}})
93
119
  update_journal(resourceId: <journal id>, saveAsDraft: false)
94
120
  ```
95
121
 
96
- Verify after finalize:
122
+ Journals cannot be filtered by capsule, and a date-plus-status search returns every matching DRAFT in the org: never feed one into `bulk_update_journals` or `delete_journal`.
123
+
124
+ Verify after each period:
97
125
  - `generate_trial_balance(endDate: <month-end>)`.
98
- - Assert: `balance['Accumulated Depreciation (Vehicles)'] == -schedule[periodIndex].accumulatedDepreciation` (within 1 cent).
99
- - Assert: `balance['Depreciation Expense'] (period MTD) == schedule[periodIndex].depreciationAmount` (within 1 cent).
126
+ - Assert: `balance['Accumulated Depreciation (Vehicles)'] == -(cost - schedule[periodIndex].closingBookValue)` (within 1 cent).
127
+ - Assert: `balance['Depreciation Expense'] (period MTD) == schedule[periodIndex].depreciation` (within 1 cent).
100
128
  - Assert: `balance['Vehicles'] - |balance['Accumulated Depreciation (Vehicles)']| == schedule[periodIndex].closingBookValue`.
101
129
 
102
130
  After the FINAL period (month 60):
103
- - Assert: `closingBookValue == salvage` exactly ($5,000; engine forces final-period adjustment).
104
- - Asset is now fully depreciated. If sold/disposed at salvage value: invoke `asset-disposal` recipe. If retained at salvage value: capsule closes; no further depreciation.
131
+ - Assert: `closingBookValue == salvage` exactly ($5,000; the final period carries the rounding).
132
+ - Asset is now fully depreciated. If sold/disposed: follow `asset-disposal.md`. If retained at salvage value: close the capsule; no further depreciation.
105
133
 
106
134
  ---
107
135
 
108
- ## Common error classes and recovery
136
+ ## Common problems and recovery
109
137
 
110
- | Source | Error | Recovery |
138
+ | Where | Problem | Recovery |
111
139
  |--------|-------|----------|
112
- | `plan_recipe` | 422 `unsupported_recipe` | File-name alias `declining-balance` was used. Use canonical engine name `depreciation` with explicit `method: 'ddb'` or `'150db'`. |
113
- | `plan_recipe` | 422 `useful_life_invalid` | Useful life must be ≥ 2 years. Asset with useful life < 2 years: expense as period cost via `create_bill` to `Operating Expense`. |
114
- | `plan_recipe` | 422 `salvage_exceeds_cost` | Salvage ≥ cost is non-sensical. Verify inputs. |
115
- | `execute_recipe` | 422 `account_not_found` | Step 3 incomplete. `search_accounts`; create via `create_account`. |
116
- | Verification | NBV stuck above schedule | Either a DRAFT wasn't finalized (re-run step 5), OR Jaz FA also auto-posted SL depreciation (asset got duplicate-registered). Audit `search_fixed_assets(filter: {name: {contains: <asset>}})`. If duplicated: archive the auto-FA depreciation journals + remove the duplicate, **not** with `discard_fixed_asset`, whose own description is "records the disposal and final depreciation", i.e. it POSTS more entries in a case that wanted fewer. If the duplicate is still a draft, `delete_fixed_asset` (draft-only by design). If it is already active, stop: deleting is impossible and reversing its posted depreciation is a judgment call; surface both registrations to the practitioner. |
117
- | Asset reaches salvage early (impairment) | (process) | Per IAS 36, if recoverable amount drops below NBV, write down. Manual journal: Dr Impairment Loss / Cr Accumulated Depreciation for the impairment amount. Reverse remaining DRAFT depreciation journals (`delete_journal`); recipe assumed normal life. |
118
- | Disposal mid-life | (process) | Invoke `asset-disposal.md` recipe. Then close depreciation capsule, delete remaining DRAFT depreciation journals. |
140
+ | Calculator | "Useful life (years) must be a positive integer" | Whole years only, 1 or more. An item with no multi-period life is a period cost: expense it via `create_bill` to `Operating Expense`. |
141
+ | Calculator | "Salvage value (S) must be less than cost (C)" | Salvage ≥ cost is non-sensical. Verify inputs. |
142
+ | Step 2 | An account is missing | `search_accounts`; create via `create_account`. |
143
+ | Verification | NBV stuck above schedule | Either a period's journal is missing or still a DRAFT (re-run step 5), OR Jaz FA also auto-posted SL depreciation (asset got duplicate-registered). Audit `search_fixed_assets(filter: {name: {contains: <asset>}})`. If duplicated: remove the duplicate registration and its auto-posted depreciation, **not** with `discard_fixed_asset`, whose own description is "records the disposal and final depreciation", i.e. it POSTS more entries in a case that wanted fewer. If the duplicate is still a draft, `delete_fixed_asset` (draft-only by design). If it is already active, stop: deleting is impossible and reversing its posted depreciation is a judgment call; surface both registrations to the practitioner. |
144
+ | Asset reaches salvage early (impairment) | (process) | Per IAS 36, if recoverable amount drops below NBV, write down. Manual journal: Dr Impairment Loss / Cr Accumulated Depreciation for the impairment amount. Delete the remaining DRAFT depreciation journals (`delete_journal`) and recalculate from the written-down value; the schedule assumed a normal life. |
145
+ | Disposal mid-life | (process) | Follow `asset-disposal.md`. Then close the depreciation capsule and delete any remaining DRAFT depreciation journals. |
119
146
 
120
147
  ---
121
148
 
122
149
  ## Variations
123
150
 
124
151
  - **150DB**: `method: '150db'`. Rate is 1.5x SL instead of 2x. Less aggressive front-loading.
125
- - **Annual frequency**: `frequency: 'annual'`. 5 annual journals instead of 60 monthly. Used when reporting cadence is annual or asset is small.
126
- - **Sum-of-years' digits (SYD)**: NOT supported by the engine. Use `clio calc depreciation` with `--method sl` for the calculation, then post manual journals for each period (rare in modern practice).
152
+ - **Annual frequency**: `frequency: 'annual'` (the default). 5 annual journals instead of 60 monthly. Used when reporting cadence is annual or asset is small. Here `blueprint.steps` and `schedule[]` agree.
153
+ - **Sum-of-years' digits (SYD)**: NOT supported by the calculator. Work the SYD amounts by hand and post manual journals for each period (rare in modern practice).
127
154
  - **Units-of-production**: NOT supported (depreciation per unit produced, not per period). Manual journal pattern: at each period-end, compute `units × per-unit-rate`, post Dr Depreciation Expense / Cr Accumulated Depreciation.
128
- - **Component depreciation** (IFRS 16.43): different parts of an asset depreciated separately. Each component gets its own recipe invocation + capsule.
129
- - **Mid-period acquisition:** The calculator does NOT prorate the first period. Schedule entries are full-period (e.g. monthly) starting from `startDate`. For partial-period accuracy, either (a) set `startDate` to the first of the period after acquisition (lose the partial-period depreciation), or (b) post a manual partial-period journal via `create_journal` for the days-in-acquisition-period, then run `plan_recipe` from the next full period.
155
+ - **Component depreciation** (IAS 16.43): different parts of an asset depreciated separately. Each component gets its own calculation + capsule.
156
+ - **Mid-period acquisition:** The calculator does NOT prorate the first period. Schedule entries are full periods. For partial-period accuracy, either (a) start the journals in the first full period after acquisition (lose the partial-period depreciation), or (b) post a manual partial-period journal via `create_journal` for the days in the acquisition period, then post the schedule from the next full period.
130
157
 
131
158
  ---
132
159
 
@@ -136,4 +163,4 @@ After the FINAL period (month 60):
136
163
  - Year-end close (full FY-end depreciation reconciliation): sum 12 monthly journals against `clio calc depreciation --frequency annual` cross-check; auditor will sample-test.
137
164
  - Data migration: opening accumulated depreciation loaded via conversion (Conversion Clearing > Accumulated Depreciation account); recipe runs forward from the migration date with `cost: <NBV at migration>` instead of original cost. Useful-life-years should be `remaining life`, not original.
138
165
  - Sibling recipe `asset-disposal.md`: end-of-life de-recognition.
139
- - `audit-prep.md` step 8: supporting schedule via `search_capsules(filter: {status: {eq: 'ACTIVE'}}) (capsule type is not filterable; see `jobs/references/building-blocks.md` § Filter limits)` + per-capsule `clio calc depreciation` recompute.
166
+ - `audit-prep.md` step 8: supporting schedule via `search_capsules(filter: {status: {eq: 'ACTIVE'}})` (capsule type is not filterable; see `jobs/references/building-blocks.md` § Filter limits) + per-capsule `clio calc depreciation` recompute.
@@ -1,28 +1,28 @@
1
- # Recipe: Deferred Revenue (engine name: `deferred-revenue`)
1
+ # Recipe: Deferred Revenue (calculator type: `deferred-revenue`)
2
2
 
3
- > Mirror of prepaid-amortization for the income side: customer pays upfront for a service delivered over time. The engine creates 1 invoice + N future-dated DRAFT recognition journals, all attached to one capsule. Same operational pattern; opposite accounting direction.
3
+ > Mirror of prepaid-amortization for the income side: customer pays upfront for a service delivered over time. One upfront invoice coded to the Deferred Revenue liability, then N recognition entries that roll the liability into revenue, all in one capsule. Same operational pattern; opposite accounting direction. You calculate, create the capsule and post each entry yourself (see `building-blocks.md` § The three-step flow).
4
4
 
5
- ## Tools, recipes, calculators this recipe uses
5
+ ## Tools and calculator this recipe uses
6
6
 
7
- ### Recipe engine entry point
8
- - **`plan_recipe(recipe: 'deferred-revenue', ...)`**, used in step 2: returns RecipePlan with the upfront customer invoice + N period-end recognition journals, capsule shape, required accounts, customer requirements.
9
- - **`execute_recipe(recipe: 'deferred-revenue', ...)`**, used in step 4: posts 1 invoice + N future-dated DRAFT journals (one per recognition period). Customer invoice creates the AR + Deferred Revenue liability; recognition journals roll the liability into Revenue over `periods`.
7
+ ### Calculator (offline, posts nothing)
8
+ - **MCP: `calculate(type: 'deferred-revenue', amount, periods, frequency, startDate, currency)`** (step 1).
9
+ - **CLI: `clio calc deferred-revenue --amount <total> --periods <n> --start-date <YYYY-MM-DD> --currency <code> --json`** (step 1): the same result.
10
10
 
11
- ### Calculator (cross-check, no API key needed)
12
- - **`clio calc deferred-revenue --amount <total> --periods <n> --start-date <YYYY-MM-DD> --currency <code> --json`**, used in step 1: independently produce the recognition schedule. Returns `{ perPeriodAmount, recognitionStartDate, recognitionEndDate, schedule[n] }`. Final period absorbs rounding remainder.
11
+ ### Posting tools
12
+ - **`list_capsule_types` / `create_capsule_type(displayName: 'Deferred Revenue')` / `create_capsule(...)`** (step 3).
13
+ - **`create_invoice(...)`** (step 4): the upfront customer invoice, line coded to Deferred Revenue (NOT to Revenue).
14
+ - **`create_scheduled_journal(...)`** (step 4, equal monthly amounts) or **`create_journal(...)`** per period.
13
15
 
14
- ### Tools (jaz-api / direct)
15
- - **`search_contacts(filter: {customer: true, name: {eq: <customer>}})`**, step 3: resolve the paying customer.
16
- - **`create_contact(...)` with `customer: true`**, step 3 fallback: create the customer if `search_contacts` returns empty.
17
- - **`search_accounts(filter: {name: {in: ['<deferred liability GL>', '<revenue GL>']}})`**, step 3: confirm both GL accounts exist.
18
- - **`generate_trial_balance(endDate: <date>)`**, step 5: verify Deferred Revenue balance unwinds correctly.
19
- - **`search_capsules(filter: {title: {eq: <capsule.name>}})`**: step 0 idempotency check.
20
- - **`finalize_invoice(resourceId: <id>)`**, step 4 fallback: lift the upfront invoice from DRAFT to ACTIVE once practitioner confirms the engagement is genuinely starting.
21
- - **`bulk_update_journals(items: [{resourceId: <id>, saveAsDraft: false}, ...])`**, step 5 monthly: finalize this period's pre-emitted DRAFT recognition journal.
16
+ ### Lookup and verification tools
17
+ - **`search_capsules(filter: {title: {eq: <capsule title>}})`** (step 0): idempotency check.
18
+ - **`search_contacts(filter: {customer: true, name: {eq: <customer>}})`** (step 2): resolve the paying customer; `create_contact(...)` with `customer: true` if it does not exist.
19
+ - **`search_accounts(filter: {name: {in: ['<deferred liability GL>', '<revenue GL>']}})`** (step 2): confirm both GL accounts exist.
20
+ - **`finalize_invoice(resourceId: <id>)`** (step 4): lift the upfront invoice from DRAFT to ACTIVE once the practitioner confirms the engagement is genuinely starting.
21
+ - **`generate_trial_balance(endDate: <date>)`** (step 5): verify the Deferred Revenue balance unwinds correctly.
22
22
 
23
23
  ### Cross-references
24
- - Operational context: invoked during month-end close (finalize this period's pre-emitted journal for existing capsules; create a new capsule for any new deferred arrangement starting this period).
25
- - Sibling recipes: `prepaid-amortization.md` (mirror: same engine pattern, opposite direction).
24
+ - Operational context: invoked during month-end close (confirm this period's recognition for existing capsules; set up a new capsule for any new deferred arrangement starting this period).
25
+ - Sibling recipes: `prepaid-amortization.md` (mirror: same pattern, opposite direction).
26
26
  - IFRS / accounting context: IFRS 15, revenue recognition over time when control transfers gradually (subscriptions, retainers, multi-period service contracts). The recipe assumes ratable straight-line recognition; for stage-based / milestone billing, use a different pattern (see Variations).
27
27
 
28
28
  ---
@@ -35,22 +35,13 @@
35
35
  search_capsules(filter: {title: {eq: 'FY2025 Acme Annual License'}})
36
36
  ```
37
37
 
38
- If a result returns: halt and surface "Deferred revenue capsule `<name>` already exists. Re-running would create a duplicate upfront invoice. Confirm intent; if extending an existing arrangement, use `update_capsule` not `execute_recipe`."
38
+ If a result returns: halt and surface "Deferred revenue capsule `<name>` already exists. Re-posting would create a duplicate upfront invoice. Confirm intent; if extending an existing arrangement, post the extra entries into the existing capsule."
39
39
 
40
- ### Step 1: Independent cross-check (calculator)
40
+ ### Step 1: Calculate
41
41
 
42
42
  ```
43
- clio calc deferred-revenue --amount 24000 --periods 12 --start-date 2025-01-01 --currency SGD --json
44
- ```
45
-
46
- Returns: `{ perPeriodAmount: 2000, recognitionStartDate: '2025-01-31', recognitionEndDate: '2025-12-31', schedule: [{period, recognitionDate, amount}, ...12] }`. Verify total recognition matches the invoice amount within 1 cent (engine carries rounding into final period).
47
-
48
- ### Step 2: Plan the recipe
49
-
50
- ```
51
- plan_recipe(
52
- // Accounts, capsule and counterparty are not plan_recipe params: execute_recipe resolves accounts from the CoA and takes bankAccountName / contactName.
53
- recipe: 'deferred-revenue',
43
+ calculate(
44
+ type: 'deferred-revenue',
54
45
  amount: 24000,
55
46
  periods: 12,
56
47
  startDate: '2025-01-01',
@@ -58,75 +49,121 @@ plan_recipe(
58
49
  )
59
50
  ```
60
51
 
61
- Returns `RecipePlan` with:
62
- - `requiredAccounts`: `['Accounts Receivable', '<deferred liability GL>', '<revenue GL>']`
63
- - `needsContact`: `true` (customer)
64
- - `steps[0]`: invoice (upfront $24,000 to customer; line item codes to Deferred Revenue, NOT to Revenue)
65
- - `steps[1..12]`: 12 future-dated DRAFT journals (Dr Deferred Revenue / Cr Subscription Revenue, $2,000 each, dated end-of-month)
52
+ ```
53
+ clio calc deferred-revenue --amount 24000 --periods 12 --start-date 2025-01-01 --currency SGD --json
54
+ ```
55
+
56
+ Returns `{ perPeriodAmount: 2000, schedule: [{ period, date, amortized, remainingBalance, journal }, ...12], blueprint }`. Total recognition equals the invoice amount: the final period absorbs any rounding remainder.
57
+
58
+ `blueprint.steps`:
59
+ - Step 1, `invoice`, dated `2025-01-01`: Dr Cash / Bank Account 24,000 / Cr Deferred Revenue 24,000.
60
+ - Steps 2 to 13, `journal`, dated `2025-02-01` through `2026-01-01`: Dr Deferred Revenue 2,000 / Cr Revenue 2,000.
66
61
 
67
- ### Step 3: Resolve dependencies
62
+ For month-end recognition dates, pass a month-end start date (`2024-12-31` gives `2025-01-31`, `2025-02-28`, ...) and date the invoice separately.
68
63
 
69
- For each account in `requiredAccounts`:
70
- - `search_accounts(filter: {name: {eq: <accountName>}})`. If empty: halt. Suggested classifications: `Deferred Revenue` → `Current Liability`; `Subscription Revenue` (or whatever revenue line) → `Operating Revenue`.
64
+ ### Step 2: Resolve accounts and the customer
65
+
66
+ The blueprint's `Deferred Revenue`, `Revenue` and `Cash / Bank Account` are labels. Map each to the real account:
67
+ - `search_accounts(filter: {name: {in: ['Deferred Revenue', 'Subscription Revenue']}})`. If one is missing: halt. Suggested classifications: `Deferred Revenue` → `Current Liability`; `Subscription Revenue` (or whatever revenue line) → `Operating Revenue`.
71
68
 
72
69
  Customer:
73
- - `search_contacts(filter: {customer: true, name: {eq: 'Acme Pte Ltd'}})`. If empty: halt and surface "Customer `Acme Pte Ltd` not in Jaz contacts (or not flagged customer: true). Create via `create_contact(customer: true, ...)` or remap the customer before retry."
70
+ - `search_contacts(filter: {customer: true, name: {eq: 'Acme Pte Ltd'}})`. If empty: halt and surface "Customer `Acme Pte Ltd` not in Jaz contacts (or not flagged customer: true). Create via `create_contact(customer: true, ...)` or remap the customer."
74
71
 
75
- ### Step 4: Execute
72
+ ### Step 3: Create the capsule
76
73
 
77
74
  ```
78
- execute_recipe(recipe: 'deferred-revenue', ...same args...) // accounts auto-resolved from CoA; pass `bankAccountName` / `contactName` for fuzzy resolve
75
+ list_capsule_types()
76
+ create_capsule(
77
+ capsuleTypeResourceId: <id of 'Deferred Revenue'>,
78
+ title: 'FY2025 Acme Annual License',
79
+ description: <blueprint.capsuleDescription>
80
+ )
79
81
  ```
80
82
 
81
- Returns: `{ capsule: {resourceId, type, title}, steps: [{step, action, status, resourceId}, ...], summary: {total: 13, created: 13, ...} }`. The recipe creates **N+1 entries upfront**:
82
- - Step 1: 1 invoice (upfront $24,000 to customer; line coded to Deferred Revenue). DRAFT; finalize via `finalize_invoice(resourceId: <invoiceResourceId>)` once the engagement starts.
83
- - Steps 2..N+1: **N future-dated DRAFT recognition journals** (each Dr Deferred Revenue $2,000 / Cr Subscription Revenue $2,000), dated end-of-month for periods 1 through 12.
83
+ If `Deferred Revenue` is not in the list: `create_capsule_type(displayName: 'Deferred Revenue')` first.
84
+
85
+ ### Step 4: Post the entries
84
86
 
85
- All N journals attach to the same capsule. Customer payment: handled separately via the standard payment flow on the invoice (NOT part of this recipe; recipe assumes upfront cash arrives via `pay_invoice` separately and rolls into AR settlement).
87
+ **4a. The upfront invoice** (blueprint step 1). The credit line becomes the invoice's line item; the bank line is the receipt, recorded when the customer pays:
88
+
89
+ ```
90
+ create_invoice(
91
+ contactResourceId: <customer>,
92
+ autoReference: true,
93
+ valueDate: '2025-01-01',
94
+ dueDate: '2025-01-31',
95
+ lineItems: [{ name: 'Annual licence FY2025', quantity: 1, unitPrice: 24000, accountResourceId: <Deferred Revenue> }],
96
+ saveAsDraft: true,
97
+ capsuleResourceId: <capsule id>
98
+ )
99
+ ```
100
+
101
+ The invoice is a DRAFT; finalize via `finalize_invoice(resourceId: <invoiceResourceId>)` once the engagement starts. Customer payment is recorded separately with `pay_invoice` when the cash arrives; it settles AR and is not part of the recognition schedule.
102
+
103
+ **4b. The recognition entries** (blueprint steps 2 to 13). **Create these only after the invoice is finalized.** A schedule posts on its own dates: one created while the invoice is still a draft starts recognising revenue out of a deferred balance the books do not hold yet. If the invoice cannot be finalized now, stop here and come back.
104
+
105
+ Equal monthly amounts, so one schedule replaces the twelve journals:
106
+
107
+ ```
108
+ create_scheduled_journal(
109
+ startDate: '2025-02-01',
110
+ endDate: '2026-01-01',
111
+ repeat: 'MONTHLY',
112
+ valueDate: '2025-02-01',
113
+ reference: 'DEFREV-ACME-2025',
114
+ schedulerEntries: [
115
+ { accountResourceId: <Deferred Revenue>, type: 'DEBIT', amount: 2000, description: 'Licence revenue: {{MONTH_NAME}} {{YEAR}}' },
116
+ { accountResourceId: <Subscription Revenue>, type: 'CREDIT', amount: 2000, description: 'Licence revenue: {{MONTH_NAME}} {{YEAR}}' }
117
+ ],
118
+ capsuleResourceId: <capsule id>
119
+ )
120
+ ```
121
+
122
+ Read `building-blocks.md` § "Same amount every period" for the schedule's limits (no quarterly repeat, fixed amount). With `capsuleResourceId` on the schedule, every journal it generates lands in the capsule. The alternative is one `create_journal` per blueprint step, each with `valueDate` = the step date, a findable `reference` (`DEFREV-ACME-2025-01` ...) and `capsuleResourceId`.
86
123
 
87
124
  ### Step 5: Monthly action (during monthly-close)
88
125
 
89
- For each month after recipe execution, the corresponding DRAFT recognition journal already exists in the capsule. Monthly close action:
126
+ With a schedule, confirm the period's journal posted. With dated draft journals, find this period's journal by its reference and finalize it:
90
127
 
91
128
  ```
92
- **STOP: not selectable by filter.** Journals carry no capsule or fixed-asset link in either direction (`JournalFilter` declares neither; a journal row has no such field even at `view: 'full'`; `GET /capsules/{id}` returns only a `totalTransactions` count, measured 2026-09-07). A date+status search returns every matching DRAFT in the org, so it must never feed `bulk_update_journals` or `delete_journal`. Surface the capsule and its expected count to the practitioner and let them identify the journals.
129
+ search_journals(filter: {reference: {eq: 'DEFREV-ACME-2025-03'}})
93
130
  update_journal(resourceId: <journal id>, saveAsDraft: false)
94
131
  ```
95
132
 
96
- Verify after finalize:
133
+ Journals cannot be filtered by capsule, and a date-plus-status search returns every matching DRAFT in the org: never feed one into `bulk_update_journals` or `delete_journal`.
134
+
135
+ Verify after each period:
97
136
  - `generate_trial_balance(endDate: <period-end>)`.
98
- - Assert: `balance['Deferred Revenue'] == amount - (perPeriodAmount × periodsFinalizedSoFar)` (within 1 cent).
137
+ - Assert: `balance['Deferred Revenue'] == amount - (perPeriodAmount × periodsRecognizedSoFar)` (within 1 cent).
99
138
  - Assert: `balance['Subscription Revenue'] (period MTD) == perPeriodAmount` (within 1 cent).
100
139
 
101
- After the FINAL period is finalized:
140
+ After the FINAL period:
102
141
  - Assert: `balance['Deferred Revenue'] == 0` exactly (final-period rounding absorbed).
103
- - Close the capsule via a manual `update_capsule(title: '<original> [CLOSED]')` (the API has no `status` field for capsules; closure is informational only) if the org tracks capsule status.
142
+ - Close the capsule via a manual `update_capsule(resourceId: <id>, title: '<original> [CLOSED]')` (the API has no `status` field for capsules; closure is informational only) if the org tracks capsule status.
104
143
 
105
- If customer cancels mid-term: invoke ad-hoc adjustment; delete remaining DRAFT recognition journals via `delete_journal(resourceId: <id>)` per period, and post a customer credit note via `create_customer_credit_note(...)` for the unrecognized portion.
144
+ If the customer cancels mid-term: stop the remaining recognition (end the schedule with `update_scheduled_journal(resourceId: <scheduler id>, status: 'INACTIVE')`, or `delete_journal(resourceId: <id>)` for each remaining DRAFT), and post a customer credit note via `create_customer_credit_note(...)` for the unrecognized portion.
106
145
 
107
146
  ---
108
147
 
109
- ## Common error classes and recovery
148
+ ## Common problems and recovery
110
149
 
111
- | Source | Error | Recovery |
150
+ | Where | Problem | Recovery |
112
151
  |--------|-------|----------|
113
- | `plan_recipe` | 422 `unsupported_recipe` | Use canonical engine name `deferred-revenue` (already canonical, no alias confusion here). |
114
- | `plan_recipe` | 422 `invalid_period` | `periods <= 0` or non-integer. For lump-sum recognition (no deferral), use `create_invoice` directly with line coded to Revenue. |
115
- | `execute_recipe` | 422 `account_not_found` | Step 3 incomplete. `search_accounts`; create via `create_account` if practitioner confirms. |
116
- | `execute_recipe` | 422 `contact_not_customer` | Customer contact exists but `customer: false`. `update_contact(resourceId: <id>, customer: true)` first. |
117
- | `execute_recipe` | 422 `currency_not_enabled` | The recipe currency isn't enabled for the org. `add_currency` first. |
118
- | `execute_recipe` | 409 `capsule_already_exists` | Step 0 should have caught this. Re-run step 0. |
119
- | `finalize_invoice` | 422 `invoice_unbalanced` | Engine output is always balanced. Escalate. |
120
- | `bulk_update_journals` | 422 `journal_in_locked_period` | Period was locked before this monthly-close. Lift lock, finalize, re-lock. |
121
- | Customer cancels mid-term | (process) | Delete remaining DRAFT journals via `delete_journal(resourceId: <id>)` per period; issue customer credit note for the unrecognized portion via `create_customer_credit_note`. |
152
+ | Calculator | "must be a positive number" / "must be a positive integer" | `amount` must be positive and `periods` a positive integer. For lump-sum recognition (no deferral), use `create_invoice` directly with the line coded to Revenue. |
153
+ | Step 2 | An account is missing | `search_accounts`; create via `create_account` if the practitioner confirms. |
154
+ | Step 2 | Contact exists but is not a customer | `update_contact(resourceId: <id>, customer: true)` first. |
155
+ | `create_invoice` | Currency not enabled for the org | `add_currency` first. |
156
+ | `create_capsule` | Returns the existing capsule instead of a new one | Step 0 should have caught this. Re-run step 0 and confirm intent. |
157
+ | Monthly finalize | Journal falls in a locked period | The period was locked before this monthly-close. Lift the lock, finalize, re-lock. |
158
+ | Customer cancels mid-term | (process) | Stop the remaining recognition (see step 5); issue a customer credit note for the unrecognized portion via `create_customer_credit_note`. |
122
159
 
123
160
  ---
124
161
 
125
162
  ## Variations
126
163
 
127
- - **Quarterly recognition:** `periods: 4, frequency: 'quarterly'`. 4 quarter-end recognition journals at $6,000 each.
164
+ - **Quarterly recognition:** `periods: 4, frequency: 'quarterly'`. 4 quarterly recognition journals at $6,000 each. Post them as dated journals (schedules have no quarterly repeat).
128
165
  - **Partial first period:** The calculator does NOT prorate. Schedule entries are equal full-period amounts (`amount / periods`). For partial-period revenue (e.g. annual subscription starting mid-month), use `create_subscription` (handles proration natively) rather than this recipe.
129
- - **Multi-currency:** Pass `currency: 'USD'` if invoice in USD; per `jaz-api/SKILL.md` rule 25, invoice records via `currency: { sourceCurrency: 'USD' }`. Recognition journals also in USD; Jaz auto-handles period-end FX revaluation of the Deferred Revenue liability balance per IAS 21.23 (do NOT invoke `fx-reval` recipe; see `fx-revaluation.md`).
166
+ - **Multi-currency:** Pass `currency: 'USD'` if invoice in USD; per `jaz-api/SKILL.md` rule 25, invoice records via `currency: { sourceCurrency: 'USD' }`. Recognition journals also in USD; Jaz auto-handles period-end FX revaluation of the Deferred Revenue liability balance per IAS 21.23 (do NOT post an `fx-reval` result; see `fx-revaluation.md`).
130
167
  - **Renewal:** New capsule per term (`'FY2026 Acme Annual License'`). Capsule lifecycle is per recognition cycle.
131
168
  - **Stage-based / milestone billing** (NOT ratable): NOT supported by this recipe. Use `create_invoice` per milestone with line coded directly to Revenue; no deferral capsule needed.
132
169
  - **Subscription contracts with proration / mid-cycle upgrades:** prefer Jaz native subscription tools (`create_subscription`) over this recipe; subscriptions handle proration natively. Recipe is for non-subscription deferred revenue (one-time annual licences, retainers, prepaid services).
@@ -135,7 +172,7 @@ If customer cancels mid-term: invoke ad-hoc adjustment; delete remaining DRAFT r
135
172
 
136
173
  ## Cross-references
137
174
 
138
- - Month-end close: invoked monthly to finalize this period's pre-emitted recognition journal per existing Deferred Revenue capsule, AND to plan/execute new capsules when a fresh deferred arrangement starts in the period.
175
+ - Month-end close: invoked monthly to confirm this period's recognition per existing Deferred Revenue capsule, AND to set up new capsules when a fresh deferred arrangement starts in the period.
139
176
  - Data migration: opening trial balance may include opening Deferred Revenue (subscriptions in flight at conversion date). Conversion (`jaz-conversion/SKILL.md § Option 2`) loads the opening balance via clearing account; this recipe then sets up forward recognition only (do NOT model historical periods retroactively).
140
177
  - Year-end close: the final monthly close before year-end handles the December recognition journal; year-end close confirms Deferred Revenue is correctly classified as current vs non-current liability for BS presentation.
141
- - Sibling recipe `prepaid-amortization.md`: same engine pattern from the buyer's perspective.
178
+ - Sibling recipe `prepaid-amortization.md`: the same pattern from the buyer's perspective.