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.
- package/assets/skills/api/SKILL.md +12 -2
- package/assets/skills/api/references/dependencies.md +3 -2
- package/assets/skills/api/references/endpoints.md +78 -0
- package/assets/skills/api/references/feature-glossary.md +4 -4
- package/assets/skills/api/references/field-map.md +17 -0
- package/assets/skills/api/references/full-api-surface.md +1 -1
- package/assets/skills/conversion/SKILL.md +1 -1
- package/assets/skills/jobs/SKILL.md +104 -0
- package/assets/skills/jobs/references/audit-prep.md +319 -0
- package/assets/skills/jobs/references/bank-recon.md +234 -0
- package/assets/skills/jobs/references/building-blocks.md +135 -0
- package/assets/skills/jobs/references/credit-control.md +273 -0
- package/assets/skills/jobs/references/fa-review.md +267 -0
- package/assets/skills/jobs/references/gst-vat-filing.md +250 -0
- package/assets/skills/jobs/references/month-end-close.md +308 -0
- package/assets/skills/jobs/references/payment-run.md +246 -0
- package/assets/skills/jobs/references/quarter-end-close.md +268 -0
- package/assets/skills/jobs/references/supplier-recon.md +330 -0
- package/assets/skills/jobs/references/year-end-close.md +341 -0
- package/assets/skills/transaction-recipes/SKILL.md +1 -1
- package/dist/__tests__/amortization.test.js +101 -0
- package/dist/__tests__/asset-disposal.test.js +249 -0
- package/dist/__tests__/blueprint.test.js +72 -0
- package/dist/__tests__/depreciation.test.js +125 -0
- package/dist/__tests__/ecl.test.js +134 -0
- package/dist/__tests__/fixed-deposit.test.js +214 -0
- package/dist/__tests__/fx-reval.test.js +115 -0
- package/dist/__tests__/jobs-audit-prep.test.js +125 -0
- package/dist/__tests__/jobs-bank-recon.test.js +108 -0
- package/dist/__tests__/jobs-credit-control.test.js +98 -0
- package/dist/__tests__/jobs-fa-review.test.js +104 -0
- package/dist/__tests__/jobs-gst-vat.test.js +113 -0
- package/dist/__tests__/jobs-month-end.test.js +162 -0
- package/dist/__tests__/jobs-payment-run.test.js +106 -0
- package/dist/__tests__/jobs-quarter-end.test.js +155 -0
- package/dist/__tests__/jobs-supplier-recon.test.js +115 -0
- package/dist/__tests__/jobs-validate.test.js +181 -0
- package/dist/__tests__/jobs-year-end.test.js +149 -0
- package/dist/__tests__/lease.test.js +96 -0
- package/dist/__tests__/loan.test.js +80 -0
- package/dist/__tests__/provision.test.js +141 -0
- package/dist/__tests__/validate.test.js +81 -0
- package/dist/calc/asset-disposal.js +17 -13
- package/dist/calc/fixed-deposit.js +26 -17
- package/dist/calc/lease.js +7 -3
- package/dist/commands/jobs.js +184 -0
- package/dist/index.js +2 -0
- package/dist/jobs/audit-prep.js +211 -0
- package/dist/jobs/bank-recon.js +163 -0
- package/dist/jobs/credit-control.js +126 -0
- package/dist/jobs/fa-review.js +121 -0
- package/dist/jobs/format.js +102 -0
- package/dist/jobs/gst-vat.js +187 -0
- package/dist/jobs/month-end.js +232 -0
- package/dist/jobs/payment-run.js +199 -0
- package/dist/jobs/quarter-end.js +135 -0
- package/dist/jobs/supplier-recon.js +132 -0
- package/dist/jobs/types.js +36 -0
- package/dist/jobs/validate.js +115 -0
- package/dist/jobs/year-end.js +153 -0
- package/dist/types/index.js +2 -1
- package/package.json +5 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatters for job blueprints.
|
|
3
|
+
* Checklist table for human reading, JSON for programmatic use.
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
const line = (w) => chalk.dim('─'.repeat(w));
|
|
7
|
+
const CATEGORY_LABELS = {
|
|
8
|
+
verify: 'Verify',
|
|
9
|
+
accrue: 'Accrue',
|
|
10
|
+
adjust: 'Adjust',
|
|
11
|
+
value: 'Value',
|
|
12
|
+
report: 'Report',
|
|
13
|
+
lock: 'Lock',
|
|
14
|
+
resolve: 'Resolve',
|
|
15
|
+
export: 'Export',
|
|
16
|
+
review: 'Review',
|
|
17
|
+
};
|
|
18
|
+
const CATEGORY_COLORS = {
|
|
19
|
+
verify: chalk.cyan,
|
|
20
|
+
accrue: chalk.yellow,
|
|
21
|
+
adjust: chalk.yellow,
|
|
22
|
+
value: chalk.magenta,
|
|
23
|
+
report: chalk.blue,
|
|
24
|
+
lock: chalk.red,
|
|
25
|
+
resolve: chalk.green,
|
|
26
|
+
export: chalk.blue,
|
|
27
|
+
review: chalk.cyan,
|
|
28
|
+
};
|
|
29
|
+
function categoryBadge(cat) {
|
|
30
|
+
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
31
|
+
const color = CATEGORY_COLORS[cat] ?? chalk.white;
|
|
32
|
+
return color(`[${label}]`);
|
|
33
|
+
}
|
|
34
|
+
function printStep(step) {
|
|
35
|
+
const badge = categoryBadge(step.category);
|
|
36
|
+
const cond = step.conditional ? chalk.dim(` (${step.conditional})`) : '';
|
|
37
|
+
console.log(` ${String(step.order).padStart(2)}. ${badge} ${step.description}${cond}`);
|
|
38
|
+
if (step.apiCall) {
|
|
39
|
+
console.log(chalk.dim(` API: ${step.apiCall}`));
|
|
40
|
+
}
|
|
41
|
+
if (step.recipeRef) {
|
|
42
|
+
console.log(chalk.dim(` Recipe: ${step.recipeRef}`));
|
|
43
|
+
}
|
|
44
|
+
if (step.calcCommand) {
|
|
45
|
+
console.log(chalk.dim(` Calc: ${step.calcCommand}`));
|
|
46
|
+
}
|
|
47
|
+
if (step.verification) {
|
|
48
|
+
console.log(chalk.dim(` Check: ${step.verification}`));
|
|
49
|
+
}
|
|
50
|
+
if (step.notes) {
|
|
51
|
+
console.log(chalk.dim(` Note: ${step.notes}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function printPhase(phase) {
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(chalk.bold(` ${phase.name}`));
|
|
57
|
+
console.log(chalk.dim(` ${phase.description}`));
|
|
58
|
+
console.log();
|
|
59
|
+
for (const step of phase.steps) {
|
|
60
|
+
printStep(step);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const JOB_TITLES = {
|
|
64
|
+
'month-end-close': 'Month-End Close',
|
|
65
|
+
'quarter-end-close': 'Quarter-End Close',
|
|
66
|
+
'year-end-close': 'Year-End Close',
|
|
67
|
+
'bank-recon': 'Bank Reconciliation',
|
|
68
|
+
'gst-vat-filing': 'GST/VAT Filing Preparation',
|
|
69
|
+
'payment-run': 'Payment Run',
|
|
70
|
+
'credit-control': 'Credit Control',
|
|
71
|
+
'supplier-recon': 'Supplier Reconciliation',
|
|
72
|
+
'audit-prep': 'Audit Preparation',
|
|
73
|
+
'fa-review': 'Fixed Asset Review',
|
|
74
|
+
};
|
|
75
|
+
export function printBlueprint(bp) {
|
|
76
|
+
const W = 80;
|
|
77
|
+
const title = JOB_TITLES[bp.jobType] ?? bp.jobType;
|
|
78
|
+
const modeLabel = bp.mode === 'incremental' ? ' (Incremental)' : '';
|
|
79
|
+
const currLabel = bp.currency ? ` — ${bp.currency}` : '';
|
|
80
|
+
console.log();
|
|
81
|
+
console.log(chalk.bold(`${title}${modeLabel}${currLabel}`));
|
|
82
|
+
console.log(chalk.bold(`Period: ${bp.period}`));
|
|
83
|
+
console.log(line(W));
|
|
84
|
+
for (const phase of bp.phases) {
|
|
85
|
+
printPhase(phase);
|
|
86
|
+
}
|
|
87
|
+
console.log();
|
|
88
|
+
console.log(line(W));
|
|
89
|
+
console.log(chalk.bold('Summary'));
|
|
90
|
+
console.log(` Total steps: ${bp.summary.totalSteps}`);
|
|
91
|
+
if (bp.summary.recipeReferences.length > 0) {
|
|
92
|
+
console.log(` Recipes: ${bp.summary.recipeReferences.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
if (bp.summary.calcReferences.length > 0) {
|
|
95
|
+
console.log(` Calculators: ${bp.summary.calcReferences.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
console.log(` API calls: ${bp.summary.apiCalls.length} unique endpoints`);
|
|
98
|
+
console.log();
|
|
99
|
+
}
|
|
100
|
+
export function printBlueprintJson(bp) {
|
|
101
|
+
console.log(JSON.stringify(bp, null, 2));
|
|
102
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GST/VAT Filing Preparation blueprint generator.
|
|
3
|
+
* Produces a structured JobBlueprint for quarterly GST/VAT return preparation —
|
|
4
|
+
* no actual API calls, just an actionable checklist for accountants.
|
|
5
|
+
*/
|
|
6
|
+
import { buildSummary } from './types.js';
|
|
7
|
+
import { parseQuarterPeriod } from './validate.js';
|
|
8
|
+
export function generateGstVatBlueprint(opts) {
|
|
9
|
+
const qp = parseQuarterPeriod(opts.period);
|
|
10
|
+
const currency = opts.currency ?? 'SGD';
|
|
11
|
+
// Phase 1: Generate Tax Ledger
|
|
12
|
+
const generateLedger = {
|
|
13
|
+
name: 'Generate Tax Ledger',
|
|
14
|
+
description: `Generate the tax ledger for ${qp.label} to identify all taxable transactions.`,
|
|
15
|
+
steps: [
|
|
16
|
+
{
|
|
17
|
+
order: 1,
|
|
18
|
+
description: `Generate tax ledger for ${qp.label}`,
|
|
19
|
+
category: 'report',
|
|
20
|
+
apiCall: 'POST /generate-reports/vat-ledger',
|
|
21
|
+
apiBody: {
|
|
22
|
+
startDate: qp.startDate,
|
|
23
|
+
endDate: qp.endDate,
|
|
24
|
+
},
|
|
25
|
+
verification: 'Tax ledger generated — review total output tax and input tax',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
order: 2,
|
|
29
|
+
description: 'Export tax ledger for offline review',
|
|
30
|
+
category: 'export',
|
|
31
|
+
apiCall: 'POST /data-exports/vat-ledger',
|
|
32
|
+
apiBody: {
|
|
33
|
+
startDate: qp.startDate,
|
|
34
|
+
endDate: qp.endDate,
|
|
35
|
+
},
|
|
36
|
+
notes: 'Optional — export for working paper or external review',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
// Phase 2: Review Output Tax (Sales)
|
|
41
|
+
const reviewOutputTax = {
|
|
42
|
+
name: 'Review Output Tax',
|
|
43
|
+
description: 'Cross-reference invoices against tax ledger to verify output tax completeness.',
|
|
44
|
+
steps: [
|
|
45
|
+
{
|
|
46
|
+
order: 3,
|
|
47
|
+
description: 'Cross-reference invoices with tax ledger entries',
|
|
48
|
+
category: 'verify',
|
|
49
|
+
apiCall: 'POST /invoices/search',
|
|
50
|
+
apiBody: {
|
|
51
|
+
filters: {
|
|
52
|
+
dateFrom: qp.startDate,
|
|
53
|
+
dateTo: qp.endDate,
|
|
54
|
+
status: ['APPROVED', 'PAID'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
verification: 'Every taxable invoice should appear in the tax ledger with correct tax code',
|
|
58
|
+
notes: 'Check for invoices missing tax profiles or using incorrect rates',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
order: 4,
|
|
62
|
+
description: 'Check for missing or incorrect tax profiles on invoices',
|
|
63
|
+
category: 'verify',
|
|
64
|
+
apiCall: 'POST /invoices/search',
|
|
65
|
+
apiBody: {
|
|
66
|
+
filters: {
|
|
67
|
+
dateFrom: qp.startDate,
|
|
68
|
+
dateTo: qp.endDate,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
notes: 'Look for zero-rated, exempt, or out-of-scope transactions that may be miscategorized',
|
|
72
|
+
verification: 'All invoices have appropriate tax profiles assigned',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
// Phase 3: Review Input Tax (Purchases)
|
|
77
|
+
const reviewInputTax = {
|
|
78
|
+
name: 'Review Input Tax',
|
|
79
|
+
description: 'Cross-reference bills against tax ledger to verify input tax claims.',
|
|
80
|
+
steps: [
|
|
81
|
+
{
|
|
82
|
+
order: 5,
|
|
83
|
+
description: 'Cross-reference bills with tax ledger entries',
|
|
84
|
+
category: 'verify',
|
|
85
|
+
apiCall: 'POST /bills/search',
|
|
86
|
+
apiBody: {
|
|
87
|
+
filters: {
|
|
88
|
+
dateFrom: qp.startDate,
|
|
89
|
+
dateTo: qp.endDate,
|
|
90
|
+
status: ['APPROVED', 'PAID'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
verification: 'Every claimable bill should appear in the tax ledger with correct input tax',
|
|
94
|
+
notes: 'Ensure supplier tax invoice numbers are recorded for audit trail',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
order: 6,
|
|
98
|
+
description: 'Identify blocked input tax claims',
|
|
99
|
+
category: 'review',
|
|
100
|
+
notes: 'Review expenses that are non-claimable under local GST/VAT rules (e.g., entertainment, motor vehicles, medical). These should be coded to a non-claimable tax profile.',
|
|
101
|
+
verification: 'Blocked input tax items correctly excluded from claimable total',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
// Phase 4: Error Checks
|
|
106
|
+
const errorChecks = {
|
|
107
|
+
name: 'Error Checks',
|
|
108
|
+
description: 'Run common GST/VAT error checks before filing.',
|
|
109
|
+
steps: [
|
|
110
|
+
{
|
|
111
|
+
order: 7,
|
|
112
|
+
description: 'Review common GST/VAT errors',
|
|
113
|
+
category: 'review',
|
|
114
|
+
notes: [
|
|
115
|
+
'Check for: (1) transactions with wrong tax period,',
|
|
116
|
+
'(2) reverse charge not applied on imported services,',
|
|
117
|
+
'(3) credit notes not linked to original invoices,',
|
|
118
|
+
'(4) inter-company transactions missing tax,',
|
|
119
|
+
'(5) capital goods above threshold without separate declaration',
|
|
120
|
+
].join(' '),
|
|
121
|
+
verification: 'All common error patterns reviewed and resolved',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
// Phase 5: GST Return Summary
|
|
126
|
+
const returnSummary = {
|
|
127
|
+
name: 'GST Return Summary',
|
|
128
|
+
description: 'Compile the GST F5 return boxes and generate supporting reports.',
|
|
129
|
+
steps: [
|
|
130
|
+
{
|
|
131
|
+
order: 8,
|
|
132
|
+
description: 'Compile F5 box mapping (Boxes 1-16)',
|
|
133
|
+
category: 'report',
|
|
134
|
+
notes: [
|
|
135
|
+
'Box 1: Total value of standard-rated supplies,',
|
|
136
|
+
'Box 2: Total value of zero-rated supplies,',
|
|
137
|
+
'Box 3: Total value of exempt supplies,',
|
|
138
|
+
'Box 5: Total value of taxable purchases,',
|
|
139
|
+
'Box 6: Output tax due,',
|
|
140
|
+
'Box 7: Input tax and refunds claimed,',
|
|
141
|
+
'Box 8: Net GST payable/refundable',
|
|
142
|
+
].join(' '),
|
|
143
|
+
verification: 'F5 boxes balance: Box 6 - Box 7 = Box 8',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
order: 9,
|
|
147
|
+
description: 'Generate supporting reports for filing',
|
|
148
|
+
category: 'report',
|
|
149
|
+
apiCall: 'POST /generate-reports/profit-and-loss',
|
|
150
|
+
apiBody: {
|
|
151
|
+
primarySnapshotDate: qp.endDate,
|
|
152
|
+
secondarySnapshotDate: qp.startDate,
|
|
153
|
+
},
|
|
154
|
+
notes: 'P&L cross-check: revenue should tie to output tax base, expenses to input tax base',
|
|
155
|
+
verification: 'Supporting reports consistent with GST return numbers',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
// Phase 6: Export & File
|
|
160
|
+
const exportAndFile = {
|
|
161
|
+
name: 'Export & File',
|
|
162
|
+
description: 'Export the final tax ledger and prepare for e-filing.',
|
|
163
|
+
steps: [
|
|
164
|
+
{
|
|
165
|
+
order: 10,
|
|
166
|
+
description: 'Export final tax ledger for submission',
|
|
167
|
+
category: 'export',
|
|
168
|
+
apiCall: 'POST /data-exports/vat-ledger',
|
|
169
|
+
apiBody: {
|
|
170
|
+
startDate: qp.startDate,
|
|
171
|
+
endDate: qp.endDate,
|
|
172
|
+
},
|
|
173
|
+
notes: 'Export tax ledger as CSV/PDF for IRAS e-filing or manual submission. Archive working papers.',
|
|
174
|
+
verification: 'Tax ledger exported and filed. Filing deadline noted.',
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
const phases = [generateLedger, reviewOutputTax, reviewInputTax, errorChecks, returnSummary, exportAndFile];
|
|
179
|
+
return {
|
|
180
|
+
jobType: 'gst-vat-filing',
|
|
181
|
+
period: qp.label,
|
|
182
|
+
currency,
|
|
183
|
+
mode: 'standalone',
|
|
184
|
+
phases,
|
|
185
|
+
summary: buildSummary(phases),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Month-end close blueprint generator.
|
|
3
|
+
* Produces a phased checklist with API calls, recipe references, and calc
|
|
4
|
+
* commands populated with the correct dates for the given period.
|
|
5
|
+
*/
|
|
6
|
+
import { parseMonthPeriod } from './validate.js';
|
|
7
|
+
import { buildSummary } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Compute the prior month's start and end dates from a YYYY-MM period.
|
|
10
|
+
* Rolls back to December of the prior year when month is January.
|
|
11
|
+
*/
|
|
12
|
+
function priorMonthDates(year, month) {
|
|
13
|
+
let pYear = year;
|
|
14
|
+
let pMonth = month - 1;
|
|
15
|
+
if (pMonth < 1) {
|
|
16
|
+
pMonth = 12;
|
|
17
|
+
pYear -= 1;
|
|
18
|
+
}
|
|
19
|
+
const endDay = new Date(pYear, pMonth, 0).getDate();
|
|
20
|
+
const fmt = (y, m, d) => `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
21
|
+
return { start: fmt(pYear, pMonth, 1), end: fmt(pYear, pMonth, endDay) };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the five standard month-end close phases.
|
|
25
|
+
* Exposed for reuse by quarter-end and year-end generators.
|
|
26
|
+
*/
|
|
27
|
+
export function buildMonthEndPhases(startDate, endDate, year, month, phasePrefix) {
|
|
28
|
+
const pfx = phasePrefix ? `${phasePrefix} — ` : '';
|
|
29
|
+
const prior = priorMonthDates(year, month);
|
|
30
|
+
let order = 0;
|
|
31
|
+
const step = (partial) => ({ order: ++order, ...partial });
|
|
32
|
+
// Phase 1 — Pre-Close Preparation
|
|
33
|
+
const preClose = {
|
|
34
|
+
name: `${pfx}Phase 1: Pre-Close Preparation`,
|
|
35
|
+
description: 'Verify completeness of source data before making adjustments.',
|
|
36
|
+
steps: [
|
|
37
|
+
step({
|
|
38
|
+
description: 'Verify all invoices entered for the period',
|
|
39
|
+
category: 'verify',
|
|
40
|
+
apiCall: 'POST /invoices/search',
|
|
41
|
+
apiBody: {
|
|
42
|
+
filter: {
|
|
43
|
+
valueDate: { from: startDate, to: endDate },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
verification: 'Cross-check count with source documents',
|
|
47
|
+
}),
|
|
48
|
+
step({
|
|
49
|
+
description: 'Verify all bills entered for the period',
|
|
50
|
+
category: 'verify',
|
|
51
|
+
apiCall: 'POST /bills/search',
|
|
52
|
+
apiBody: {
|
|
53
|
+
filter: {
|
|
54
|
+
valueDate: { from: startDate, to: endDate },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
verification: 'Cross-check count with supplier invoices received',
|
|
58
|
+
}),
|
|
59
|
+
step({
|
|
60
|
+
description: 'Complete bank reconciliation',
|
|
61
|
+
category: 'verify',
|
|
62
|
+
apiCall: 'POST /bank-records/{accountResourceId}/search',
|
|
63
|
+
apiBody: {
|
|
64
|
+
filter: { status: 'UNRECONCILED' },
|
|
65
|
+
},
|
|
66
|
+
verification: 'Zero unreconciled items for the period',
|
|
67
|
+
notes: 'Repeat for each bank account',
|
|
68
|
+
}),
|
|
69
|
+
step({
|
|
70
|
+
description: 'Review AR aging report',
|
|
71
|
+
category: 'verify',
|
|
72
|
+
apiCall: 'POST /generate-reports/ar-report',
|
|
73
|
+
apiBody: { endDate },
|
|
74
|
+
verification: 'Flag overdue balances > 90 days',
|
|
75
|
+
}),
|
|
76
|
+
step({
|
|
77
|
+
description: 'Review AP aging report',
|
|
78
|
+
category: 'verify',
|
|
79
|
+
apiCall: 'POST /generate-reports/ap-report',
|
|
80
|
+
apiBody: { endDate },
|
|
81
|
+
verification: 'Ensure no missed supplier payments',
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
// Phase 2 — Accruals & Adjustments
|
|
86
|
+
const accruals = {
|
|
87
|
+
name: `${pfx}Phase 2: Accruals & Adjustments`,
|
|
88
|
+
description: 'Record accruals, amortizations, and depreciation for the period.',
|
|
89
|
+
steps: [
|
|
90
|
+
step({
|
|
91
|
+
description: 'Record accrued expenses',
|
|
92
|
+
category: 'accrue',
|
|
93
|
+
apiCall: 'POST /journals',
|
|
94
|
+
recipeRef: 'accrued-expenses',
|
|
95
|
+
conditional: 'If unbilled expenses exist',
|
|
96
|
+
notes: 'Reverse in the following period',
|
|
97
|
+
}),
|
|
98
|
+
step({
|
|
99
|
+
description: 'Amortize prepaid expenses',
|
|
100
|
+
category: 'accrue',
|
|
101
|
+
apiCall: 'POST /journals/search',
|
|
102
|
+
recipeRef: 'prepaid-amortization',
|
|
103
|
+
calcCommand: 'jaz calc prepaid-expense',
|
|
104
|
+
conditional: 'If prepaid capsules exist',
|
|
105
|
+
verification: 'Prepaid balance matches remaining schedule',
|
|
106
|
+
}),
|
|
107
|
+
step({
|
|
108
|
+
description: 'Recognize deferred revenue',
|
|
109
|
+
category: 'accrue',
|
|
110
|
+
recipeRef: 'deferred-revenue',
|
|
111
|
+
calcCommand: 'jaz calc deferred-revenue',
|
|
112
|
+
conditional: 'If deferred revenue exists',
|
|
113
|
+
verification: 'Deferred revenue balance matches remaining obligation',
|
|
114
|
+
}),
|
|
115
|
+
step({
|
|
116
|
+
description: 'Record depreciation for the period',
|
|
117
|
+
category: 'accrue',
|
|
118
|
+
recipeRef: 'declining-balance',
|
|
119
|
+
calcCommand: 'jaz calc depreciation',
|
|
120
|
+
conditional: 'If fixed assets exist',
|
|
121
|
+
verification: 'Accumulated depreciation matches schedule',
|
|
122
|
+
}),
|
|
123
|
+
step({
|
|
124
|
+
description: 'Accrue employee benefits (leave, CPF)',
|
|
125
|
+
category: 'accrue',
|
|
126
|
+
recipeRef: 'employee-accruals',
|
|
127
|
+
conditional: 'If tracking leave or benefit obligations',
|
|
128
|
+
}),
|
|
129
|
+
step({
|
|
130
|
+
description: 'Accrue loan interest for the period',
|
|
131
|
+
category: 'accrue',
|
|
132
|
+
recipeRef: 'bank-loan',
|
|
133
|
+
calcCommand: 'jaz calc loan',
|
|
134
|
+
conditional: 'If active loans',
|
|
135
|
+
verification: 'Interest accrual matches amortization schedule',
|
|
136
|
+
}),
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
// Phase 3 — Period-End Valuations
|
|
140
|
+
const valuations = {
|
|
141
|
+
name: `${pfx}Phase 3: Period-End Valuations`,
|
|
142
|
+
description: 'Revalue balances to reflect period-end rates and provisions.',
|
|
143
|
+
steps: [
|
|
144
|
+
step({
|
|
145
|
+
description: 'FX revaluation of foreign currency balances',
|
|
146
|
+
category: 'value',
|
|
147
|
+
recipeRef: 'fx-revaluation',
|
|
148
|
+
calcCommand: 'jaz calc fx-reval',
|
|
149
|
+
conditional: 'If multi-currency org',
|
|
150
|
+
verification: 'Unrealised gain/loss posted to P&L',
|
|
151
|
+
}),
|
|
152
|
+
step({
|
|
153
|
+
description: 'Bad debt provision (ECL assessment)',
|
|
154
|
+
category: 'value',
|
|
155
|
+
recipeRef: 'bad-debt-provision',
|
|
156
|
+
calcCommand: 'jaz calc ecl',
|
|
157
|
+
conditional: 'If material AR balance change',
|
|
158
|
+
verification: 'Provision balance reflects current ECL estimate',
|
|
159
|
+
}),
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
// Phase 4 — Verification
|
|
163
|
+
const verification = {
|
|
164
|
+
name: `${pfx}Phase 4: Verification`,
|
|
165
|
+
description: 'Generate financial reports and compare to prior period.',
|
|
166
|
+
steps: [
|
|
167
|
+
step({
|
|
168
|
+
description: 'Review trial balance',
|
|
169
|
+
category: 'report',
|
|
170
|
+
apiCall: 'POST /generate-reports/trial-balance',
|
|
171
|
+
apiBody: { startDate, endDate },
|
|
172
|
+
verification: 'Total debits equal total credits',
|
|
173
|
+
}),
|
|
174
|
+
step({
|
|
175
|
+
description: 'Generate profit & loss statement',
|
|
176
|
+
category: 'report',
|
|
177
|
+
apiCall: 'POST /generate-reports/profit-and-loss',
|
|
178
|
+
apiBody: { primarySnapshotDate: endDate, secondarySnapshotDate: startDate },
|
|
179
|
+
}),
|
|
180
|
+
step({
|
|
181
|
+
description: 'Generate balance sheet',
|
|
182
|
+
category: 'report',
|
|
183
|
+
apiCall: 'POST /generate-reports/balance-sheet',
|
|
184
|
+
apiBody: { primarySnapshotDate: endDate },
|
|
185
|
+
verification: 'Assets = Liabilities + Equity',
|
|
186
|
+
}),
|
|
187
|
+
step({
|
|
188
|
+
description: 'Compare P&L to prior month',
|
|
189
|
+
category: 'report',
|
|
190
|
+
apiCall: 'POST /generate-reports/profit-and-loss',
|
|
191
|
+
apiBody: {
|
|
192
|
+
primarySnapshotDate: endDate,
|
|
193
|
+
secondarySnapshotDate: prior.start,
|
|
194
|
+
},
|
|
195
|
+
notes: `Prior month: ${prior.start} to ${prior.end}`,
|
|
196
|
+
verification: 'Investigate material variances (> 10%)',
|
|
197
|
+
}),
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
// Phase 5 — Close & Lock
|
|
201
|
+
const closeLock = {
|
|
202
|
+
name: `${pfx}Phase 5: Close & Lock`,
|
|
203
|
+
description: 'Lock the period to prevent further changes.',
|
|
204
|
+
steps: [
|
|
205
|
+
step({
|
|
206
|
+
description: `Set accounting lock date to ${endDate}`,
|
|
207
|
+
category: 'lock',
|
|
208
|
+
notes: 'Navigate to Settings > General > Lock Date and move it forward to ' +
|
|
209
|
+
`${endDate}. This prevents any transactions from being posted to this period.`,
|
|
210
|
+
}),
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
return [preClose, accruals, valuations, verification, closeLock];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Generate a month-end close blueprint for a given period.
|
|
217
|
+
*
|
|
218
|
+
* @param opts.period - Month in YYYY-MM format (e.g. "2025-01")
|
|
219
|
+
* @param opts.currency - Optional base currency code (e.g. "SGD")
|
|
220
|
+
*/
|
|
221
|
+
export function generateMonthEndBlueprint(opts) {
|
|
222
|
+
const parsed = parseMonthPeriod(opts.period);
|
|
223
|
+
const phases = buildMonthEndPhases(parsed.startDate, parsed.endDate, parsed.year, parsed.month);
|
|
224
|
+
return {
|
|
225
|
+
jobType: 'month-end-close',
|
|
226
|
+
period: parsed.label,
|
|
227
|
+
currency: opts.currency ?? 'SGD',
|
|
228
|
+
mode: 'standalone',
|
|
229
|
+
phases,
|
|
230
|
+
summary: buildSummary(phases),
|
|
231
|
+
};
|
|
232
|
+
}
|