jaz-cli 2.6.0 → 2.8.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 (62) hide show
  1. package/assets/skills/api/SKILL.md +12 -2
  2. package/assets/skills/api/references/dependencies.md +3 -2
  3. package/assets/skills/api/references/endpoints.md +78 -0
  4. package/assets/skills/api/references/feature-glossary.md +4 -4
  5. package/assets/skills/api/references/field-map.md +17 -0
  6. package/assets/skills/api/references/full-api-surface.md +1 -1
  7. package/assets/skills/conversion/SKILL.md +1 -1
  8. package/assets/skills/jobs/SKILL.md +104 -0
  9. package/assets/skills/jobs/references/audit-prep.md +319 -0
  10. package/assets/skills/jobs/references/bank-recon.md +234 -0
  11. package/assets/skills/jobs/references/building-blocks.md +135 -0
  12. package/assets/skills/jobs/references/credit-control.md +273 -0
  13. package/assets/skills/jobs/references/fa-review.md +267 -0
  14. package/assets/skills/jobs/references/gst-vat-filing.md +250 -0
  15. package/assets/skills/jobs/references/month-end-close.md +308 -0
  16. package/assets/skills/jobs/references/payment-run.md +246 -0
  17. package/assets/skills/jobs/references/quarter-end-close.md +268 -0
  18. package/assets/skills/jobs/references/supplier-recon.md +330 -0
  19. package/assets/skills/jobs/references/year-end-close.md +341 -0
  20. package/assets/skills/transaction-recipes/SKILL.md +1 -1
  21. package/dist/__tests__/amortization.test.js +101 -0
  22. package/dist/__tests__/asset-disposal.test.js +249 -0
  23. package/dist/__tests__/blueprint.test.js +72 -0
  24. package/dist/__tests__/depreciation.test.js +125 -0
  25. package/dist/__tests__/ecl.test.js +134 -0
  26. package/dist/__tests__/fixed-deposit.test.js +214 -0
  27. package/dist/__tests__/fx-reval.test.js +115 -0
  28. package/dist/__tests__/jobs-audit-prep.test.js +125 -0
  29. package/dist/__tests__/jobs-bank-recon.test.js +108 -0
  30. package/dist/__tests__/jobs-credit-control.test.js +98 -0
  31. package/dist/__tests__/jobs-fa-review.test.js +104 -0
  32. package/dist/__tests__/jobs-gst-vat.test.js +113 -0
  33. package/dist/__tests__/jobs-month-end.test.js +162 -0
  34. package/dist/__tests__/jobs-payment-run.test.js +106 -0
  35. package/dist/__tests__/jobs-quarter-end.test.js +155 -0
  36. package/dist/__tests__/jobs-supplier-recon.test.js +115 -0
  37. package/dist/__tests__/jobs-validate.test.js +181 -0
  38. package/dist/__tests__/jobs-year-end.test.js +149 -0
  39. package/dist/__tests__/lease.test.js +96 -0
  40. package/dist/__tests__/loan.test.js +80 -0
  41. package/dist/__tests__/provision.test.js +141 -0
  42. package/dist/__tests__/validate.test.js +81 -0
  43. package/dist/calc/asset-disposal.js +17 -13
  44. package/dist/calc/fixed-deposit.js +26 -17
  45. package/dist/calc/lease.js +7 -3
  46. package/dist/commands/jobs.js +184 -0
  47. package/dist/index.js +2 -0
  48. package/dist/jobs/audit-prep.js +211 -0
  49. package/dist/jobs/bank-recon.js +163 -0
  50. package/dist/jobs/credit-control.js +126 -0
  51. package/dist/jobs/fa-review.js +121 -0
  52. package/dist/jobs/format.js +102 -0
  53. package/dist/jobs/gst-vat.js +187 -0
  54. package/dist/jobs/month-end.js +232 -0
  55. package/dist/jobs/payment-run.js +199 -0
  56. package/dist/jobs/quarter-end.js +135 -0
  57. package/dist/jobs/supplier-recon.js +132 -0
  58. package/dist/jobs/types.js +36 -0
  59. package/dist/jobs/validate.js +115 -0
  60. package/dist/jobs/year-end.js +153 -0
  61. package/dist/types/index.js +2 -1
  62. package/package.json +5 -2
@@ -0,0 +1,341 @@
1
+ # Year-End Close
2
+
3
+ The annual close builds on the quarterly close. It is quarter-end close repeated four times, plus a set of year-end-specific adjustments — true-ups, dividends, retained earnings rollover, final GST reconciliation, and audit preparation. For most SMBs, the annual extras add 2-5 days of work depending on complexity and whether an external audit is required.
4
+
5
+ **CLI:** `jaz jobs year-end --period 2025 [--currency SGD] [--json]`
6
+
7
+ **Standalone vs Incremental:**
8
+ - **Standalone (default):** Generates the full plan — all quarterly close steps for each of the four quarters, followed by the annual extras. Use this when quarters haven't been closed yet.
9
+ - **Incremental (`--incremental`):** Generates only the annual extras below. Use this when all four quarters are already closed and locked.
10
+
11
+ ---
12
+
13
+ ## Phase 1–7: Quarterly Close (x4)
14
+
15
+ Complete the full quarter-end close for each quarter in the year. Each quarter follows the monthly close (x3) plus quarterly extras pattern.
16
+
17
+ **Reference:** `references/quarter-end-close.md`
18
+
19
+ | FY | Q1 | Q2 | Q3 | Q4 |
20
+ |----|----|----|----|----|
21
+ | Calendar year | Jan–Mar | Apr–Jun | Jul–Sep | Oct–Dec |
22
+
23
+ **Important:** Close quarters in order. Q1 must be locked before starting Q2, and so on. The annual extras run after Q4 is closed. By the time you reach this phase, all 12 months are individually closed, all four quarterly GST returns are filed, and all quarterly provisions are up to date.
24
+
25
+ ---
26
+
27
+ ## Phase 8: Annual Extras
28
+
29
+ These steps run once per year, after all four quarters are closed. They address items that are inherently annual — full-year depreciation reconciliation, true-ups against actuals, dividends, retained earnings rollover, and audit preparation.
30
+
31
+ ### Step Y1: Final depreciation run
32
+
33
+ Verify the full year's depreciation is correctly recorded and reconciles to the fixed asset register.
34
+
35
+ **Recipe:** `declining-balance` | **Calculator:** `jaz calc depreciation`
36
+
37
+ **Step-by-step:**
38
+
39
+ 1. Pull the fixed asset register as at year-end:
40
+ ```
41
+ POST /generate-reports/fixed-assets-summary
42
+ {}
43
+ ```
44
+
45
+ 2. For each asset class, verify that 12 months of depreciation have been recorded. If using Jaz native FA depreciation (straight-line), this should be automatic. If using manual journals for non-standard methods (DDB, 150DB), verify all 12 monthly entries exist.
46
+
47
+ 3. Run the calculator for each non-standard asset to confirm the full-year charge:
48
+ ```bash
49
+ jaz calc depreciation --cost 50000 --salvage 5000 --life 5 --method ddb --frequency monthly --json
50
+ ```
51
+
52
+ 4. Reconcile accumulated depreciation per the FA register to accumulated depreciation per the trial balance:
53
+ ```
54
+ POST /generate-reports/trial-balance
55
+ { "startDate": "2025-01-01", "endDate": "2025-12-31" }
56
+ ```
57
+
58
+ **What to check:**
59
+ - Depreciation expense on P&L = sum of all 12 monthly depreciation charges
60
+ - Accumulated depreciation on balance sheet = prior year balance + current year depreciation - accumulated depreciation on disposed assets
61
+ - FA register NBV (net book value) per asset ties to the balance sheet total
62
+ - No asset has been depreciated below its salvage value
63
+
64
+ **Conditional:** Always, if fixed assets exist. Even if depreciation runs automatically, verify it at year-end. Auditors will test this.
65
+
66
+ ### Step Y2: Year-end true-ups
67
+
68
+ Reconcile accruals to actuals and post adjustments. Three components:
69
+
70
+ #### a) Leave balance true-up
71
+
72
+ Monthly closes accrued a fixed amount for leave liability. At year-end, reconcile to actual leave balances.
73
+
74
+ **Recipe:** `employee-accruals`
75
+
76
+ ```
77
+ POST /journals
78
+ {
79
+ "valueDate": "2025-12-31",
80
+ "reference": "YE-LEAVE-TRUEUP-FY25",
81
+ "journalEntries": [
82
+ { "accountResourceId": "<leave-expense>", "amount": 3200.00, "type": "DEBIT", "name": "Leave accrual true-up — FY2025 (actual vs accrued)" },
83
+ { "accountResourceId": "<leave-liability>", "amount": 3200.00, "type": "CREDIT", "name": "Leave accrual true-up — FY2025 (actual vs accrued)" }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ **What to check:** Leave liability on balance sheet should equal (unused leave days x daily rate) for each employee. If accrual > actual, reverse the excess (DR Leave Liability, CR Leave Expense).
89
+
90
+ **Conditional:** Only if you track leave balances in the books. Most SMBs with 5+ employees should.
91
+
92
+ #### b) Bonus true-up
93
+
94
+ Quarterly closes maintained the bonus accrual estimate. At year-end, true up to actual bonus amounts declared.
95
+
96
+ **Recipe:** `employee-accruals` (bonus component)
97
+
98
+ ```
99
+ POST /journals
100
+ {
101
+ "valueDate": "2025-12-31",
102
+ "reference": "YE-BONUS-TRUEUP-FY25",
103
+ "journalEntries": [
104
+ { "accountResourceId": "<bonus-expense>", "amount": 5000.00, "type": "DEBIT", "name": "Bonus true-up — FY2025 (actual declared vs accrued)" },
105
+ { "accountResourceId": "<bonus-liability>", "amount": 5000.00, "type": "CREDIT", "name": "Bonus true-up — FY2025 (actual declared vs accrued)" }
106
+ ]
107
+ }
108
+ ```
109
+
110
+ **What to check:** Bonus liability on balance sheet = actual bonuses declared but not yet paid. If over-accrued, reverse the excess.
111
+
112
+ **Conditional:** Only if the business has bonus schemes.
113
+
114
+ #### c) Provision reassessment (IAS 37)
115
+
116
+ At year-end, reassess all provisions — not just unwind the discount, but review whether the provision amount itself is still the best estimate.
117
+
118
+ **Recipe:** `provisions`
119
+
120
+ **What to check for each provision:**
121
+ - Is the obligating event still valid? (If resolved, release the provision.)
122
+ - Has the best estimate changed? (Adjust up or down.)
123
+ - Has the discount rate changed? (Re-measure at current rate, post the difference.)
124
+ - Has the expected settlement date changed? (Adjust the unwinding schedule.)
125
+
126
+ Post adjustment journals for any changes. Document the reasoning — auditors will review provision assessments.
127
+
128
+ **Conditional:** Only if IAS 37 provisions exist. Most simple SMBs can skip this.
129
+
130
+ ### Step Y3: Dividend declaration and payment
131
+
132
+ If the business is declaring dividends for the fiscal year, post two journals — one for declaration and one for payment. These are distinct accounting events.
133
+
134
+ **Recipe:** `dividend`
135
+
136
+ #### Declaration (board resolution date):
137
+
138
+ ```
139
+ POST /journals
140
+ {
141
+ "valueDate": "2025-12-31",
142
+ "reference": "YE-DIV-DECLARE-FY25",
143
+ "journalEntries": [
144
+ { "accountResourceId": "<retained-earnings>", "amount": 50000.00, "type": "DEBIT", "name": "Dividend declared — FY2025" },
145
+ { "accountResourceId": "<dividends-payable>", "amount": 50000.00, "type": "CREDIT", "name": "Dividend declared — FY2025" }
146
+ ]
147
+ }
148
+ ```
149
+
150
+ #### Payment (actual payment date):
151
+
152
+ ```
153
+ POST /journals
154
+ {
155
+ "valueDate": "2026-01-15",
156
+ "reference": "YE-DIV-PAY-FY25",
157
+ "journalEntries": [
158
+ { "accountResourceId": "<dividends-payable>", "amount": 50000.00, "type": "DEBIT", "name": "Dividend paid — FY2025" },
159
+ { "accountResourceId": "<bank-account>", "amount": 50000.00, "type": "CREDIT", "name": "Dividend paid — FY2025" }
160
+ ]
161
+ }
162
+ ```
163
+
164
+ **Note:** Declaration often falls in the current FY (e.g., Dec 31), but payment may fall in the next FY (e.g., Jan 15). The declaration journal reduces retained earnings in the current year. The payment journal just clears the liability — no P&L impact.
165
+
166
+ **For Singapore:** Dividends are tax-exempt in the hands of the shareholder (one-tier system). No withholding tax. No additional tax journal needed.
167
+
168
+ **For Philippines:** Dividends to resident individuals are subject to 10% final withholding tax. Post the withholding tax separately (DR Dividends Payable, CR Withholding Tax Payable) and remit to BIR.
169
+
170
+ **Conditional:** Only if the business is declaring dividends. Many SMBs retain all earnings. Skip if no dividend resolution.
171
+
172
+ ### Step Y4: Retained earnings rollover (Current Year Earnings)
173
+
174
+ Jaz auto-manages the annual rollover of Current Year Earnings (CYE) into Retained Earnings at the start of each new fiscal year. The net P&L for the year is accumulated in CYE during the year, then rolled forward.
175
+
176
+ **What to verify:**
177
+
178
+ 1. Pull the equity movement report:
179
+ ```
180
+ POST /generate-reports/equity-movement
181
+ { "primarySnapshotStartDate": "2025-01-01", "primarySnapshotEndDate": "2025-12-31" }
182
+ ```
183
+
184
+ 2. Confirm:
185
+ - CYE balance at year-end = Net profit (or loss) for the year per P&L
186
+ - Retained Earnings opening balance = prior year closing Retained Earnings
187
+ - After rollover (in the new year): CYE resets to zero, Retained Earnings increases by last year's net profit (less any dividends declared)
188
+
189
+ **Tip:** If CYE doesn't match the P&L bottom line, there's a posting error somewhere — most likely a journal posted directly to an equity account that shouldn't have been.
190
+
191
+ **No journal needed** — this is a platform-managed rollover. Your job is to verify it happened correctly.
192
+
193
+ ### Step Y5: Final GST/VAT reconciliation
194
+
195
+ Reconcile the full year's GST/VAT against all four quarterly returns (or twelve monthly returns for PH).
196
+
197
+ ```
198
+ POST /generate-reports/vat-ledger
199
+ { "startDate": "2025-01-01", "endDate": "2025-12-31" }
200
+ ```
201
+
202
+ **What to check:**
203
+
204
+ 1. Annual output tax per tax ledger = sum of output tax across all four quarterly returns
205
+ 2. Annual input tax per tax ledger = sum of input tax across all four quarterly returns
206
+ 3. Net GST paid/refunded during the year = sum of all quarterly GST payments/refunds
207
+ 4. GST control account on trial balance = net GST payable/receivable for Q4 (if Q1–Q3 already settled)
208
+ 5. No orphan transactions — every taxable invoice and bill is accounted for in the tax ledger
209
+
210
+ **For Singapore:** IRAS may request the Annual GST Return (GST F8) for certain businesses. The annual reconciliation feeds directly into this.
211
+
212
+ **For Philippines:** Cross-check annual totals against the Summary List of Sales (SLS) and Summary List of Purchases (SLP) required by BIR.
213
+
214
+ **Tip:** If the annual tax ledger total doesn't match the sum of quarterly returns, the most common causes are: (a) transactions entered after the quarterly filing with a backdated date, (b) credit notes or amendments posted in a later quarter, (c) reclassification of tax codes mid-year.
215
+
216
+ ### Step Y6: Audit preparation
217
+
218
+ Compile all reports, reconciliation schedules, and supporting documentation for the external auditor or tax preparer.
219
+
220
+ **Reference:** `references/audit-prep.md` for the full audit preparation job.
221
+
222
+ At minimum, generate and export:
223
+
224
+ | Report | API Call |
225
+ |--------|----------|
226
+ | Trial Balance (full year) | `POST /generate-reports/trial-balance { "startDate": "2025-01-01", "endDate": "2025-12-31" }` |
227
+ | Balance Sheet (year-end) | `POST /generate-reports/balance-sheet { "primarySnapshotDate": "2025-12-31" }` |
228
+ | P&L (full year) | `POST /generate-reports/profit-and-loss { "primarySnapshotDate": "2025-12-31", "secondarySnapshotDate": "2025-01-01" }` |
229
+ | General Ledger (full year) | `POST /generate-reports/general-ledger { "startDate": "2025-01-01", "endDate": "2025-12-31", "groupBy": "ACCOUNT" }` |
230
+ | Cashflow Statement | `POST /generate-reports/cashflow { "primaryStartDate": "2025-01-01", "primaryEndDate": "2025-12-31" }` |
231
+ | AR Aging (year-end) | `POST /generate-reports/ar-report { "endDate": "2025-12-31" }` |
232
+ | AP Aging (year-end) | `POST /generate-reports/ap-report { "endDate": "2025-12-31" }` |
233
+ | FA Register | `POST /generate-reports/fixed-assets-summary {}` |
234
+ | Tax Ledger (full year) | `POST /generate-reports/vat-ledger { "startDate": "2025-01-01", "endDate": "2025-12-31" }` |
235
+ | Equity Movement | `POST /generate-reports/equity-movement { "primarySnapshotStartDate": "2025-01-01", "primarySnapshotEndDate": "2025-12-31" }` |
236
+
237
+ **Supporting schedules to prepare:**
238
+ - Bank reconciliation statements for each bank account at year-end
239
+ - ECL provision calculation workpaper (from Q4 Step Q2)
240
+ - Fixed asset movement schedule (additions, disposals, depreciation, NBV)
241
+ - Intercompany balance confirmation letters (if multi-entity)
242
+ - Loan amortization schedules with principal/interest split
243
+ - Provision schedules with movement analysis
244
+
245
+ **Conditional:** Always for audited entities. For non-audited SMBs, at minimum prepare TB + BS + P&L for tax filing purposes.
246
+
247
+ ### Step Y7: Final lock date
248
+
249
+ Lock the entire fiscal year. This prevents any accidental or unauthorized entries into the closed year.
250
+
251
+ **Best practice:** Set the org lock date to the last day of the fiscal year (e.g., `2025-12-31`).
252
+
253
+ **Important considerations:**
254
+ - Confirm with the auditor that they have no remaining adjustments before locking. Many auditors request a "soft close" first, with the hard lock applied after audit adjustments are posted.
255
+ - If using an audit adjustments period (e.g., January 1–15 of the new year for audit entries), set the lock date after those entries are complete.
256
+ - Once locked, reopening requires admin action and should be documented.
257
+
258
+ ---
259
+
260
+ ## Phase 9: Annual Verification
261
+
262
+ Run the standard period verification (see `references/building-blocks.md` — Period Verification Pattern) for the full year, plus these annual-specific checks.
263
+
264
+ ### Standard verification (full year)
265
+
266
+ ```
267
+ POST /generate-reports/trial-balance
268
+ { "startDate": "2025-01-01", "endDate": "2025-12-31" }
269
+ ```
270
+
271
+ ```
272
+ POST /generate-reports/profit-and-loss
273
+ { "primarySnapshotDate": "2025-12-31", "secondarySnapshotDate": "2025-01-01" }
274
+ ```
275
+
276
+ ```
277
+ POST /generate-reports/balance-sheet
278
+ { "primarySnapshotDate": "2025-12-31" }
279
+ ```
280
+
281
+ ### Annual-specific checks
282
+
283
+ **CYE rollover check:**
284
+ - Current Year Earnings at year-end = Net profit per the annual P&L
285
+ - After the new FY opens: CYE resets to zero, Retained Earnings increases by the prior year's net profit (less dividends)
286
+ - Total equity movement = Net profit - Dividends declared + Other equity movements (capital injection, etc.)
287
+
288
+ **Annual P&L vs monthly P&Ls sum:**
289
+ - Generate P&L for each of the 12 months individually
290
+ - Sum all 12 monthly net profit figures
291
+ - This sum MUST equal the annual P&L net profit
292
+ - If it doesn't, there's a timing/posting error — most commonly a journal posted to a month that was supposed to be locked
293
+
294
+ **FA register reconciliation:**
295
+ - NBV per FA register = Cost less accumulated depreciation per balance sheet
296
+ - Depreciation expense per FA register = Depreciation expense per P&L
297
+ - Asset count per FA register = physical count (if performed)
298
+ - Any fully depreciated assets still in use should be noted but not removed from register
299
+
300
+ **Provision balances:**
301
+ - Each provision on the balance sheet has documented support (IAS 37 criteria met)
302
+ - Provision movement = Opening + New provisions + Unwinding - Utilizations - Releases
303
+ - Unwinding total for the year = sum of four quarterly unwinding entries
304
+
305
+ **Intercompany elimination (if consolidating):**
306
+ - All intercompany balances net to zero across the group
307
+ - Intercompany revenue/expense eliminated on consolidation
308
+ - No intercompany profit in inventory or fixed assets (if applicable)
309
+
310
+ ---
311
+
312
+ ## Year-End Close Checklist (Quick Reference)
313
+
314
+ | # | Step | Phase | Conditional | Recipe/Calc |
315
+ |---|------|-------|-------------|-------------|
316
+ | 1–18 | Q1 Month 1 close | Monthly | Always | See month-end-close.md |
317
+ | 1–18 | Q1 Month 2 close | Monthly | Always | See month-end-close.md |
318
+ | 1–18 | Q1 Month 3 close | Monthly | Always | See month-end-close.md |
319
+ | Q1–Q5 | Q1 quarterly extras | Quarterly | See quarter-end-close.md | See quarter-end-close.md |
320
+ | 1–18 | Q2 Month 1 close | Monthly | Always | See month-end-close.md |
321
+ | 1–18 | Q2 Month 2 close | Monthly | Always | See month-end-close.md |
322
+ | 1–18 | Q2 Month 3 close | Monthly | Always | See month-end-close.md |
323
+ | Q1–Q5 | Q2 quarterly extras | Quarterly | See quarter-end-close.md | See quarter-end-close.md |
324
+ | 1–18 | Q3 Month 1 close | Monthly | Always | See month-end-close.md |
325
+ | 1–18 | Q3 Month 2 close | Monthly | Always | See month-end-close.md |
326
+ | 1–18 | Q3 Month 3 close | Monthly | Always | See month-end-close.md |
327
+ | Q1–Q5 | Q3 quarterly extras | Quarterly | See quarter-end-close.md | See quarter-end-close.md |
328
+ | 1–18 | Q4 Month 1 close | Monthly | Always | See month-end-close.md |
329
+ | 1–18 | Q4 Month 2 close | Monthly | Always | See month-end-close.md |
330
+ | 1–18 | Q4 Month 3 close | Monthly | Always | See month-end-close.md |
331
+ | Q1–Q5 | Q4 quarterly extras | Quarterly | See quarter-end-close.md | See quarter-end-close.md |
332
+ | Y1 | Final depreciation run | Annual | If fixed assets exist | declining-balance / `jaz calc depreciation` |
333
+ | Y2a | Leave balance true-up | Annual | If tracking leave | employee-accruals |
334
+ | Y2b | Bonus true-up | Annual | If bonus schemes exist | employee-accruals |
335
+ | Y2c | Provision reassessment | Annual | If IAS 37 provisions exist | provisions |
336
+ | Y3 | Dividend declaration & payment | Annual | If declaring dividends | dividend |
337
+ | Y4 | Retained earnings rollover (CYE) | Annual | Always (verify only) | — |
338
+ | Y5 | Final GST/VAT reconciliation | Annual | Always (if GST-registered) | gst-vat-filing job |
339
+ | Y6 | Audit preparation | Annual | Always (scope varies) | audit-prep job |
340
+ | Y7 | Final lock date | Annual | Always | — |
341
+ | V | Annual verification | Verification | Always | — |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: transaction-recipes
3
- version: 2.6.0
3
+ version: 2.8.0
4
4
  description: 16 IFRS-compliant recipes for complex multi-step accounting in Jaz — prepaid amortization, deferred revenue, loan schedules, IFRS 16 leases, hire purchase, fixed deposits, asset disposal, FX revaluation, ECL provisioning, IAS 37 provisions, dividends, intercompany, and capital WIP. Each recipe includes journal entries, capsule structure, and verification steps. Paired with 10 financial calculators that produce execution-ready blueprints with workings.
5
5
  license: MIT
6
6
  compatibility: Works with Claude Code, Claude Cowork, Claude.ai, and any agent that reads markdown. For API payloads, load the jaz-api skill alongside this one.
@@ -0,0 +1,101 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { calculatePrepaidExpense, calculateDeferredRevenue } from '../calc/amortization.js';
3
+ import { CalcValidationError } from '../calc/validate.js';
4
+ describe('calculatePrepaidExpense', () => {
5
+ const base = { amount: 12000, periods: 12 };
6
+ it('per period = amount / periods', () => {
7
+ const r = calculatePrepaidExpense(base);
8
+ expect(r.perPeriodAmount).toBe(1000);
9
+ });
10
+ it('schedule has correct length', () => {
11
+ const r = calculatePrepaidExpense(base);
12
+ expect(r.schedule).toHaveLength(12);
13
+ });
14
+ it('total amortized = original amount', () => {
15
+ const r = calculatePrepaidExpense(base);
16
+ const total = r.schedule.reduce((s, row) => s + row.amortized, 0);
17
+ expect(Math.round(total * 100) / 100).toBe(12000);
18
+ });
19
+ it('remaining balance reaches zero at final period', () => {
20
+ const r = calculatePrepaidExpense(base);
21
+ expect(r.schedule[r.schedule.length - 1].remainingBalance).toBe(0);
22
+ });
23
+ it('handles rounding — 10000 / 3 periods', () => {
24
+ const r = calculatePrepaidExpense({ amount: 10000, periods: 3 });
25
+ expect(r.perPeriodAmount).toBe(3333.33);
26
+ const total = r.schedule.reduce((s, row) => s + row.amortized, 0);
27
+ expect(Math.round(total * 100) / 100).toBe(10000);
28
+ // Final period absorbs rounding
29
+ expect(r.schedule[2].amortized).toBe(3333.34);
30
+ });
31
+ it('every journal entry balanced', () => {
32
+ const r = calculatePrepaidExpense(base);
33
+ for (const row of r.schedule) {
34
+ const debits = row.journal.lines.reduce((s, l) => s + l.debit, 0);
35
+ const credits = row.journal.lines.reduce((s, l) => s + l.credit, 0);
36
+ expect(debits).toBe(credits);
37
+ }
38
+ });
39
+ it('journal entries use Expense / Prepaid Asset accounts', () => {
40
+ const r = calculatePrepaidExpense(base);
41
+ expect(r.schedule[0].journal.lines[0].account).toBe('Expense');
42
+ expect(r.schedule[0].journal.lines[1].account).toBe('Prepaid Asset');
43
+ });
44
+ // Blueprint
45
+ it('blueprint step 1 = bill', () => {
46
+ const r = calculatePrepaidExpense({ ...base, startDate: '2025-01-01' });
47
+ expect(r.blueprint.steps[0].action).toBe('bill');
48
+ });
49
+ it('blueprint step count = 1 + periods', () => {
50
+ const r = calculatePrepaidExpense({ ...base, startDate: '2025-01-01' });
51
+ expect(r.blueprint.steps).toHaveLength(13);
52
+ });
53
+ it('blueprint null without startDate', () => {
54
+ const r = calculatePrepaidExpense(base);
55
+ expect(r.blueprint).toBeNull();
56
+ });
57
+ // Quarterly
58
+ it('quarterly frequency works', () => {
59
+ const r = calculatePrepaidExpense({ amount: 12000, periods: 4, frequency: 'quarterly', startDate: '2025-01-01' });
60
+ expect(r.schedule).toHaveLength(4);
61
+ // Date present and properly formatted (exact value depends on TZ)
62
+ expect(r.schedule[0].date).toMatch(/^\d{4}-\d{2}-\d{2}$/);
63
+ });
64
+ // Validation
65
+ it('rejects zero amount', () => {
66
+ expect(() => calculatePrepaidExpense({ amount: 0, periods: 12 })).toThrow(CalcValidationError);
67
+ });
68
+ it('rejects zero periods', () => {
69
+ expect(() => calculatePrepaidExpense({ amount: 12000, periods: 0 })).toThrow(CalcValidationError);
70
+ });
71
+ });
72
+ describe('calculateDeferredRevenue', () => {
73
+ const base = { amount: 36000, periods: 12 };
74
+ it('per period = amount / periods', () => {
75
+ const r = calculateDeferredRevenue(base);
76
+ expect(r.perPeriodAmount).toBe(3000);
77
+ });
78
+ it('total recognized = original amount', () => {
79
+ const r = calculateDeferredRevenue(base);
80
+ const total = r.schedule.reduce((s, row) => s + row.amortized, 0);
81
+ expect(Math.round(total * 100) / 100).toBe(36000);
82
+ });
83
+ it('journal entries use Deferred Revenue / Revenue accounts', () => {
84
+ const r = calculateDeferredRevenue(base);
85
+ expect(r.schedule[0].journal.lines[0].account).toBe('Deferred Revenue');
86
+ expect(r.schedule[0].journal.lines[1].account).toBe('Revenue');
87
+ });
88
+ // Blueprint
89
+ it('blueprint step 1 = invoice', () => {
90
+ const r = calculateDeferredRevenue({ ...base, startDate: '2025-01-01' });
91
+ expect(r.blueprint.steps[0].action).toBe('invoice');
92
+ });
93
+ it('capsuleType is Deferred Revenue', () => {
94
+ const r = calculateDeferredRevenue({ ...base, startDate: '2025-01-01' });
95
+ expect(r.blueprint.capsuleType).toBe('Deferred Revenue');
96
+ });
97
+ it('capsuleDescription present', () => {
98
+ const r = calculateDeferredRevenue({ ...base, startDate: '2025-01-01' });
99
+ expect(r.blueprint.capsuleDescription).toBeTruthy();
100
+ });
101
+ });