@classytic/ca-tax 0.0.1

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.
@@ -0,0 +1,2 @@
1
+ import { A as GST_RATE, C as CaSupplyCategory, D as classifySupply, E as PstBaseRule, F as getProvincialRegime, M as ProvincialRegime, N as ProvincialRegimeEntry, O as pstApplies, P as getCombinedRate, S as CA_SUPPLY_CATEGORIES, T as CaSupplyTreatment, _ as VEHICLE_ITC_CEILINGS, a as gst34Template, b as isSmallSupplier, c as CaSupplyType, d as ItcInput, f as PSB_SMALL_SUPPLIER_THRESHOLD_MINOR, g as SmallSupplierInput, h as SMALL_SUPPLIER_THRESHOLD_MINOR, i as gst34LedgerAdapter, j as PROVINCIAL_REGIMES, k as CaProvince, l as CaTaxLeg, m as SIMPLIFIED_DIGITAL_THRESHOLD_MINOR, n as Gst34Input, o as CaSalesTaxInput, p as PlaceOfSupplyInput, r as gst34Engine, s as CaSalesTaxResult, t as Gst34Breakdown, u as ItcExpenseKind, v as calculateCaSalesTax, w as CaSupplyClassification, x as placeOfSupply, y as claimableItc } from "../index2.mjs";
2
+ export { CA_SUPPLY_CATEGORIES, type CaProvince, type CaSalesTaxInput, type CaSalesTaxResult, type CaSupplyCategory, type CaSupplyClassification, type CaSupplyTreatment, type CaSupplyType, type CaTaxLeg, GST_RATE, type Gst34Breakdown, type Gst34Input, type ItcExpenseKind, type ItcInput, PROVINCIAL_REGIMES, PSB_SMALL_SUPPLIER_THRESHOLD_MINOR, type PlaceOfSupplyInput, type ProvincialRegime, type ProvincialRegimeEntry, type PstBaseRule, SIMPLIFIED_DIGITAL_THRESHOLD_MINOR, SMALL_SUPPLIER_THRESHOLD_MINOR, type SmallSupplierInput, VEHICLE_ITC_CEILINGS, calculateCaSalesTax, claimableItc, classifySupply, getCombinedRate, getProvincialRegime, gst34Engine, gst34LedgerAdapter, gst34Template, isSmallSupplier, placeOfSupply, pstApplies };
@@ -0,0 +1,2 @@
1
+ import { _ as getCombinedRate, a as SIMPLIFIED_DIGITAL_THRESHOLD_MINOR, c as calculateCaSalesTax, d as placeOfSupply, f as CA_SUPPLY_CATEGORIES, g as PROVINCIAL_REGIMES, h as GST_RATE, i as PSB_SMALL_SUPPLIER_THRESHOLD_MINOR, l as claimableItc, m as pstApplies, n as gst34LedgerAdapter, o as SMALL_SUPPLIER_THRESHOLD_MINOR, p as classifySupply, r as gst34Template, s as VEHICLE_ITC_CEILINGS, t as gst34Engine, u as isSmallSupplier, v as getProvincialRegime } from "../gst-hst.mjs";
2
+ export { CA_SUPPLY_CATEGORIES, GST_RATE, PROVINCIAL_REGIMES, PSB_SMALL_SUPPLIER_THRESHOLD_MINOR, SIMPLIFIED_DIGITAL_THRESHOLD_MINOR, SMALL_SUPPLIER_THRESHOLD_MINOR, VEHICLE_ITC_CEILINGS, calculateCaSalesTax, claimableItc, classifySupply, getCombinedRate, getProvincialRegime, gst34Engine, gst34LedgerAdapter, gst34Template, isSmallSupplier, placeOfSupply, pstApplies };
@@ -0,0 +1,675 @@
1
+ import { createLayeredRules } from "@classytic/tax-core/registry";
2
+ import { firstEffectiveAt, isEffectiveAt, latestEntry } from "@classytic/tax-core/effective";
3
+ //#region src/extend.ts
4
+ /**
5
+ * CA rule extensions — the boot-time hot-patch seam, composed from
6
+ * `@classytic/tax-core/registry` (same pattern as bd-tax and us-tax).
7
+ * Provincial budgets move rates and PST bases mid-year (NS's April-2025
8
+ * cut, MB's January-2026 cloud expansion are exactly this shape) —
9
+ * extensions carry dated windows so a patch is period-accurate.
10
+ */
11
+ const rateLayer = createLayeredRules();
12
+ const pstLayer = createLayeredRules();
13
+ /** Register extensions (additive; later registrations matched first). */
14
+ function extendCaTaxRules(extensions) {
15
+ if (extensions.provincialRates) rateLayer.register(extensions.provincialRates);
16
+ if (extensions.pstBaseRules) pstLayer.register(extensions.pstBaseRules);
17
+ }
18
+ /** Clear all registered extensions (tests / hot-reload). */
19
+ function resetCaTaxRuleExtensions() {
20
+ rateLayer.reset();
21
+ pstLayer.reset();
22
+ }
23
+ /** @internal */
24
+ function getCaRuleExtensions() {
25
+ return {
26
+ provincialRates: rateLayer.all(),
27
+ pstBaseRules: pstLayer.all()
28
+ };
29
+ }
30
+ //#endregion
31
+ //#region src/gst-hst/rates.ts
32
+ /** Federal GST rate (percent) — unchanged since 2008. */
33
+ const GST_RATE = 5;
34
+ const D = (iso) => new Date(iso);
35
+ const ERA = D("2024-01-01");
36
+ const PROVINCIAL_REGIMES = [
37
+ {
38
+ province: "AB",
39
+ regime: "GST",
40
+ effectiveFrom: ERA
41
+ },
42
+ {
43
+ province: "NT",
44
+ regime: "GST",
45
+ effectiveFrom: ERA
46
+ },
47
+ {
48
+ province: "NU",
49
+ regime: "GST",
50
+ effectiveFrom: ERA
51
+ },
52
+ {
53
+ province: "YT",
54
+ regime: "GST",
55
+ effectiveFrom: ERA
56
+ },
57
+ {
58
+ province: "ON",
59
+ regime: "HST",
60
+ hstRate: 13,
61
+ effectiveFrom: ERA
62
+ },
63
+ {
64
+ province: "NB",
65
+ regime: "HST",
66
+ hstRate: 15,
67
+ effectiveFrom: ERA
68
+ },
69
+ {
70
+ province: "NL",
71
+ regime: "HST",
72
+ hstRate: 15,
73
+ effectiveFrom: ERA
74
+ },
75
+ {
76
+ province: "PE",
77
+ regime: "HST",
78
+ hstRate: 15,
79
+ effectiveFrom: ERA
80
+ },
81
+ {
82
+ province: "NS",
83
+ regime: "HST",
84
+ hstRate: 15,
85
+ effectiveFrom: ERA,
86
+ effectiveTo: D("2025-03-31")
87
+ },
88
+ {
89
+ province: "NS",
90
+ regime: "HST",
91
+ hstRate: 14,
92
+ effectiveFrom: D("2025-04-01"),
93
+ notes: "Provincial part 10% → 9% (NOTICE342); 2026-27 budget maintains 14%"
94
+ },
95
+ {
96
+ province: "BC",
97
+ regime: "GST+PST",
98
+ provincialRate: 7,
99
+ provincialName: "PST",
100
+ effectiveFrom: ERA
101
+ },
102
+ {
103
+ province: "SK",
104
+ regime: "GST+PST",
105
+ provincialRate: 6,
106
+ provincialName: "PST",
107
+ effectiveFrom: ERA
108
+ },
109
+ {
110
+ province: "MB",
111
+ regime: "GST+PST",
112
+ provincialRate: 7,
113
+ provincialName: "RST",
114
+ effectiveFrom: ERA,
115
+ notes: "Rate 7% since 2019; BASE extended to cloud/SaaS 1 Jan 2026 (see taxability)"
116
+ },
117
+ {
118
+ province: "QC",
119
+ regime: "GST+QST",
120
+ provincialRate: 9.975,
121
+ provincialName: "QST",
122
+ effectiveFrom: ERA,
123
+ notes: "Charged on the GST-EXCLUSIVE price (since 2013); administered by Revenu Québec"
124
+ }
125
+ ];
126
+ /** Regime entry for a province at a date (host extensions first). Null for unknown codes. */
127
+ function getProvincialRegime(province, asOf = /* @__PURE__ */ new Date()) {
128
+ const code = province.toUpperCase();
129
+ const candidates = [...getCaRuleExtensions().provincialRates, ...PROVINCIAL_REGIMES].filter((e) => e.province === code);
130
+ if (candidates.length === 0) return null;
131
+ return firstEffectiveAt(candidates, asOf) ?? latestEntry(candidates);
132
+ }
133
+ /**
134
+ * Combined nominal rate for display (percent). NOTE: for GST+QST/PST this
135
+ * is additive on the same base (both charge on the pre-GST price) — use
136
+ * `calculateCaSalesTax` for actual leg amounts, not this sum.
137
+ */
138
+ function getCombinedRate(province, asOf = /* @__PURE__ */ new Date()) {
139
+ const r = getProvincialRegime(province, asOf);
140
+ if (!r) return null;
141
+ if (r.regime === "HST") return r.hstRate ?? null;
142
+ if (r.regime === "GST") return 5;
143
+ return 5 + (r.provincialRate ?? 0);
144
+ }
145
+ //#endregion
146
+ //#region src/gst-hst/taxability.ts
147
+ /**
148
+ * Supply classification — the VAT-family distinction the US pack could
149
+ * not exercise and BD only partially does:
150
+ *
151
+ * - TAXABLE → charge tax, claim ITCs.
152
+ * - ZERO_RATED → charge 0%, STILL claim ITCs (groceries, prescription
153
+ * drugs, medical devices, exports, agriculture).
154
+ * - EXEMPT → charge nothing, NO ITCs on related inputs
155
+ * (residential rent, health, education, financial
156
+ * services, childcare).
157
+ *
158
+ * `inputCreditAllowed` is therefore NOT derivable from the rate — 0% can
159
+ * mean either. This is the semantic tax-core's `TaxClassRate` field was
160
+ * designed to carry.
161
+ *
162
+ * PST/RST bases are narrower than GST's and province-specific; the
163
+ * headline dated rule here: MANITOBA extended RST to cloud/SaaS services
164
+ * effective 1 Jan 2026 (MLT Aikins / BDO, corroborated).
165
+ *
166
+ * Verified 4 Jul 2026 — research sheet §6 (+ the expired Dec-2024/Feb-2025
167
+ * "GST holiday", deliberately NOT modelled: it lapsed 15 Feb 2025 with no
168
+ * successor).
169
+ */
170
+ const CA_SUPPLY_CATEGORIES = [
171
+ "STANDARD",
172
+ "GROCERY",
173
+ "PRESCRIPTION_DRUGS",
174
+ "MEDICAL_DEVICE",
175
+ "EXPORT",
176
+ "AGRICULTURE_FISHING",
177
+ "FEMININE_HYGIENE",
178
+ "RESIDENTIAL_RENT",
179
+ "HEALTH_SERVICES",
180
+ "EDUCATION",
181
+ "FINANCIAL_SERVICES",
182
+ "CHILDCARE",
183
+ "SAAS",
184
+ "GENERAL_SERVICE"
185
+ ];
186
+ const ZERO_RATED = /* @__PURE__ */ new Set([
187
+ "GROCERY",
188
+ "PRESCRIPTION_DRUGS",
189
+ "MEDICAL_DEVICE",
190
+ "EXPORT",
191
+ "AGRICULTURE_FISHING",
192
+ "FEMININE_HYGIENE"
193
+ ]);
194
+ const EXEMPT = /* @__PURE__ */ new Set([
195
+ "RESIDENTIAL_RENT",
196
+ "HEALTH_SERVICES",
197
+ "EDUCATION",
198
+ "FINANCIAL_SERVICES",
199
+ "CHILDCARE"
200
+ ]);
201
+ /** Classify a category under the GST/HST/QST (VAT-side) rules. */
202
+ function classifySupply(category) {
203
+ if (ZERO_RATED.has(category)) return {
204
+ treatment: "ZERO_RATED",
205
+ itcEligible: true
206
+ };
207
+ if (EXEMPT.has(category)) return {
208
+ treatment: "EXEMPT",
209
+ itcEligible: false
210
+ };
211
+ return {
212
+ treatment: "TAXABLE",
213
+ itcEligible: true
214
+ };
215
+ }
216
+ const PST_BASE_RULES = [
217
+ {
218
+ province: "BC",
219
+ category: "SAAS",
220
+ taxable: true
221
+ },
222
+ {
223
+ province: "SK",
224
+ category: "SAAS",
225
+ taxable: true
226
+ },
227
+ {
228
+ province: "MB",
229
+ category: "SAAS",
230
+ taxable: true,
231
+ effectiveFrom: /* @__PURE__ */ new Date("2026-01-01"),
232
+ notes: "RST base extended to cloud computing (SaaS/PaaS/IaaS, storage, hosting) 1 Jan 2026"
233
+ },
234
+ {
235
+ province: "BC",
236
+ category: "GENERAL_SERVICE",
237
+ taxable: false
238
+ },
239
+ {
240
+ province: "SK",
241
+ category: "GENERAL_SERVICE",
242
+ taxable: false
243
+ },
244
+ {
245
+ province: "MB",
246
+ category: "GENERAL_SERVICE",
247
+ taxable: false
248
+ },
249
+ {
250
+ province: "BC",
251
+ category: "GROCERY",
252
+ taxable: false
253
+ },
254
+ {
255
+ province: "SK",
256
+ category: "GROCERY",
257
+ taxable: false
258
+ },
259
+ {
260
+ province: "MB",
261
+ category: "GROCERY",
262
+ taxable: false
263
+ }
264
+ ];
265
+ /**
266
+ * Does the province's PST/RST apply to this category on this date?
267
+ * Defaults: STANDARD (tangible goods) → true; zero-rated/exempt VAT
268
+ * families → false; unlisted service categories → false (PST bases are
269
+ * enumerative). Host extensions win.
270
+ */
271
+ function pstApplies(province, category, asOf = /* @__PURE__ */ new Date()) {
272
+ const code = province.toUpperCase();
273
+ const rule = [...getCaRuleExtensions().pstBaseRules, ...PST_BASE_RULES].find((r) => r.province === code && r.category === category && (r.effectiveFrom === void 0 || isEffectiveAt({
274
+ effectiveFrom: r.effectiveFrom,
275
+ effectiveTo: r.effectiveTo
276
+ }, asOf)));
277
+ if (rule) return rule.taxable;
278
+ if (category === "STANDARD") return true;
279
+ if (ZERO_RATED.has(category) || EXEMPT.has(category)) return false;
280
+ return false;
281
+ }
282
+ //#endregion
283
+ //#region src/gst-hst/calculate.ts
284
+ /**
285
+ * Place-of-supply (ETA Sch. IX; CRA Memoranda 3-3-3/-5/-6, simplified):
286
+ * goods → delivery province; services/intangibles → recipient address,
287
+ * falling back to where performed.
288
+ */
289
+ function placeOfSupply(input) {
290
+ if (input.supplyType === "goods") {
291
+ const p = input.deliveryProvince ?? input.recipientProvince;
292
+ if (!p) throw new TypeError("goods supply requires deliveryProvince (destination governs)");
293
+ return p.toUpperCase();
294
+ }
295
+ const p = input.recipientProvince ?? input.performedProvince;
296
+ if (!p) throw new TypeError("service/intangible supply requires recipientProvince (or performedProvince fallback)");
297
+ return p.toUpperCase();
298
+ }
299
+ function calculateCaSalesTax(input) {
300
+ const asOf = input.asOf ?? /* @__PURE__ */ new Date();
301
+ const province = placeOfSupply(input);
302
+ const classification = classifySupply(input.category);
303
+ const regime = getProvincialRegime(province, asOf);
304
+ if (!regime) throw new TypeError(`Unknown province/territory code: ${province}`);
305
+ const legs = [];
306
+ if (classification.treatment === "TAXABLE") if (regime.regime === "HST") legs.push({
307
+ tax: "HST",
308
+ rate: regime.hstRate ?? 0,
309
+ amountMinor: Math.round(input.amountMinor * (regime.hstRate ?? 0) / 100),
310
+ authority: "CRA",
311
+ recoverable: true
312
+ });
313
+ else {
314
+ legs.push({
315
+ tax: "GST",
316
+ rate: 5,
317
+ amountMinor: Math.round(input.amountMinor * 5 / 100),
318
+ authority: "CRA",
319
+ recoverable: true
320
+ });
321
+ if (regime.regime === "GST+QST") legs.push({
322
+ tax: "QST",
323
+ rate: regime.provincialRate ?? 0,
324
+ amountMinor: Math.round(input.amountMinor * (regime.provincialRate ?? 0) / 100),
325
+ authority: "RQ",
326
+ recoverable: true
327
+ });
328
+ else if (regime.regime === "GST+PST" && pstApplies(province, input.category, asOf)) legs.push({
329
+ tax: regime.provincialName ?? "PST",
330
+ rate: regime.provincialRate ?? 0,
331
+ amountMinor: Math.round(input.amountMinor * (regime.provincialRate ?? 0) / 100),
332
+ authority: province,
333
+ recoverable: false
334
+ });
335
+ }
336
+ return {
337
+ province,
338
+ classification,
339
+ legs,
340
+ totalTaxMinor: legs.reduce((s, l) => s + l.amountMinor, 0)
341
+ };
342
+ }
343
+ /** Small-supplier threshold: $30,000 over four consecutive calendar quarters (CRA Memo 2-2). */
344
+ const SMALL_SUPPLIER_THRESHOLD_MINOR = 3e6;
345
+ /** Public service bodies: $50,000. */
346
+ const PSB_SMALL_SUPPLIER_THRESHOLD_MINOR = 5e6;
347
+ /** Simplified digital regime (non-resident vendors/platforms): $30,000 over 12 months, no ITCs. */
348
+ const SIMPLIFIED_DIGITAL_THRESHOLD_MINOR = 3e6;
349
+ /** Is the person still a small supplier (no obligation to register/collect)? */
350
+ function isSmallSupplier(input) {
351
+ const threshold = input.publicServiceBody ? PSB_SMALL_SUPPLIER_THRESHOLD_MINOR : SMALL_SUPPLIER_THRESHOLD_MINOR;
352
+ if ((input.currentQuarterMinor ?? 0) > threshold) return false;
353
+ return input.trailingFourQuartersMinor <= threshold;
354
+ }
355
+ /** Class 10.1 passenger-vehicle ITC cost ceilings (pre-tax, cents) — dated. */
356
+ const VEHICLE_ITC_CEILINGS = [
357
+ {
358
+ ceilingMinor: 37e5,
359
+ effectiveFrom: /* @__PURE__ */ new Date("2024-01-01"),
360
+ effectiveTo: /* @__PURE__ */ new Date("2024-12-31")
361
+ },
362
+ {
363
+ ceilingMinor: 38e5,
364
+ effectiveFrom: /* @__PURE__ */ new Date("2025-01-01"),
365
+ effectiveTo: /* @__PURE__ */ new Date("2025-12-31")
366
+ },
367
+ {
368
+ ceilingMinor: 39e5,
369
+ effectiveFrom: /* @__PURE__ */ new Date("2026-01-01")
370
+ }
371
+ ];
372
+ /**
373
+ * Claimable ITC for an input (cents). Rules: inputs relating to EXEMPT
374
+ * outputs → 0; meals & entertainment → 50%; club dues → 0; passenger
375
+ * vehicles → prorated to the dated cost ceiling.
376
+ */
377
+ function claimableItc(input) {
378
+ if (input.relatedSupplyCategory) {
379
+ const { itcEligible } = classifySupply(input.relatedSupplyCategory);
380
+ if (!itcEligible) return 0;
381
+ }
382
+ switch (input.kind) {
383
+ case "club_dues": return 0;
384
+ case "meals_entertainment": return Math.round(input.taxPaidMinor * .5);
385
+ case "passenger_vehicle": {
386
+ const asOf = input.asOf ?? /* @__PURE__ */ new Date();
387
+ const ceiling = VEHICLE_ITC_CEILINGS.find((c) => asOf.getTime() >= c.effectiveFrom.getTime() && (c.effectiveTo === void 0 || asOf.getTime() <= c.effectiveTo.getTime())) ?? VEHICLE_ITC_CEILINGS[VEHICLE_ITC_CEILINGS.length - 1];
388
+ const cost = input.vehicleCostMinor ?? 0;
389
+ if (cost <= 0 || cost <= ceiling.ceilingMinor) return input.taxPaidMinor;
390
+ return Math.round(input.taxPaidMinor * ceiling.ceilingMinor / cost);
391
+ }
392
+ default: return input.taxPaidMinor;
393
+ }
394
+ }
395
+ //#endregion
396
+ //#region src/gst-hst/gst34.ts
397
+ const LINES = Object.freeze({
398
+ 101: {
399
+ line: 101,
400
+ name: "Total Sales and Other Revenue",
401
+ description: "Total sales before GST/HST",
402
+ type: "input",
403
+ section: "Sales"
404
+ },
405
+ 103: {
406
+ line: 103,
407
+ name: "GST/HST Collected or Collectible",
408
+ description: "Tax collected on taxable supplies",
409
+ type: "input",
410
+ section: "Collected"
411
+ },
412
+ 104: {
413
+ line: 104,
414
+ name: "Adjustments (increase)",
415
+ description: "Adjustments increasing net tax",
416
+ type: "manual",
417
+ section: "Collected"
418
+ },
419
+ 105: {
420
+ line: 105,
421
+ name: "Total GST/HST and Adjustments",
422
+ description: "103 + 104",
423
+ type: "calculated",
424
+ section: "Collected",
425
+ calculate: (d) => (d[103] || 0) + (d[104] || 0)
426
+ },
427
+ 106: {
428
+ line: 106,
429
+ name: "Input Tax Credits (ITCs)",
430
+ description: "ITCs for the period",
431
+ type: "input",
432
+ section: "ITCs"
433
+ },
434
+ 107: {
435
+ line: 107,
436
+ name: "Adjustments (decrease)",
437
+ description: "Adjustments decreasing net tax",
438
+ type: "manual",
439
+ section: "ITCs"
440
+ },
441
+ 108: {
442
+ line: 108,
443
+ name: "Total ITCs and Adjustments",
444
+ description: "106 + 107",
445
+ type: "calculated",
446
+ section: "ITCs",
447
+ calculate: (d) => (d[106] || 0) + (d[107] || 0)
448
+ },
449
+ 109: {
450
+ line: 109,
451
+ name: "Net Tax",
452
+ description: "105 − 108",
453
+ type: "calculated",
454
+ section: "Net Tax",
455
+ calculate: (d) => (d[105] || 0) - (d[108] || 0)
456
+ },
457
+ 110: {
458
+ line: 110,
459
+ name: "Instalments Paid",
460
+ description: "Instalments and net-tax payments already made",
461
+ type: "manual",
462
+ section: "Credits"
463
+ },
464
+ 111: {
465
+ line: 111,
466
+ name: "Rebates",
467
+ description: "Rebates claimed on this return",
468
+ type: "manual",
469
+ section: "Credits"
470
+ },
471
+ 112: {
472
+ line: 112,
473
+ name: "Total Other Credits",
474
+ description: "110 + 111",
475
+ type: "calculated",
476
+ section: "Credits",
477
+ calculate: (d) => (d[110] || 0) + (d[111] || 0)
478
+ },
479
+ 113: {
480
+ line: 113,
481
+ name: "Balance",
482
+ description: "109 − 112",
483
+ type: "calculated",
484
+ section: "Balance",
485
+ calculate: (d) => (d[109] || 0) - (d[112] || 0)
486
+ },
487
+ 205: {
488
+ line: 205,
489
+ name: "GST/HST Due on Real Property",
490
+ description: "Self-assessed on taxable real property",
491
+ type: "manual",
492
+ section: "Self-Assessed"
493
+ },
494
+ 405: {
495
+ line: 405,
496
+ name: "Other GST/HST Self-Assessed",
497
+ description: "Imported services, other self-assessments",
498
+ type: "manual",
499
+ section: "Self-Assessed"
500
+ },
501
+ "113C": {
502
+ line: "113C",
503
+ name: "Total Due or Refund",
504
+ description: "113 + 205 + 405",
505
+ type: "calculated",
506
+ section: "Result",
507
+ calculate: (d) => (d[113] || 0) + (d[205] || 0) + (d[405] || 0)
508
+ },
509
+ 114: {
510
+ line: 114,
511
+ name: "Refund Claimed",
512
+ description: "When 113C is negative",
513
+ type: "calculated",
514
+ section: "Result",
515
+ calculate: (d) => {
516
+ const t = d["113C"] || 0;
517
+ return t < 0 ? Math.abs(t) : 0;
518
+ }
519
+ },
520
+ 115: {
521
+ line: 115,
522
+ name: "Amount Owing",
523
+ description: "When 113C is positive",
524
+ type: "calculated",
525
+ section: "Result",
526
+ calculate: (d) => {
527
+ const t = d["113C"] || 0;
528
+ return t > 0 ? t : 0;
529
+ }
530
+ }
531
+ });
532
+ const CALC_ORDER = [
533
+ 105,
534
+ 108,
535
+ 109,
536
+ 112,
537
+ 113,
538
+ "113C",
539
+ 114,
540
+ 115
541
+ ];
542
+ /** The GST34 grid as a tax-core `TaxReportTemplate`. */
543
+ const gst34Template = {
544
+ name: "CRA GST/HST Return (GST34)",
545
+ lines: LINES,
546
+ calculate(inputData, manualData = {}) {
547
+ const result = {
548
+ ...inputData,
549
+ ...manualData
550
+ };
551
+ for (const line of CALC_ORDER) {
552
+ const def = LINES[line];
553
+ if (def?.calculate) result[line] = def.calculate(result);
554
+ }
555
+ return result;
556
+ },
557
+ summarize(calculated) {
558
+ return {
559
+ sales: calculated[101] || 0,
560
+ taxCollected: calculated[105] || 0,
561
+ itcs: calculated[108] || 0,
562
+ netTax: calculated[109] || 0,
563
+ balance: calculated["113C"] || 0,
564
+ refund: calculated[114] || 0,
565
+ owing: calculated[115] || 0,
566
+ isRefund: (calculated["113C"] || 0) < 0
567
+ };
568
+ }
569
+ };
570
+ function nonNegInt(value, field) {
571
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw new TypeError(`${field} must be a non-negative integer (cents), got ${String(value)}`);
572
+ return value;
573
+ }
574
+ /** The GST34 as a tax-core TaxEngine. Negative `totalOwing` = refund position (line 114). */
575
+ const gst34Engine = {
576
+ validate(input) {
577
+ const raw = input;
578
+ if (!raw || typeof raw !== "object") throw new TypeError("GST34 input must be an object");
579
+ const period = raw.period;
580
+ if (!period || !(period.start instanceof Date) || !(period.end instanceof Date) || typeof period.label !== "string" || period.start.getTime() > period.end.getTime()) throw new TypeError("period must be { start: Date, end: Date, label } with start <= end");
581
+ return {
582
+ period,
583
+ salesMinor: nonNegInt(raw.salesMinor, "salesMinor"),
584
+ taxCollectedMinor: nonNegInt(raw.taxCollectedMinor, "taxCollectedMinor"),
585
+ adjustmentsIncreaseMinor: nonNegInt(raw.adjustmentsIncreaseMinor ?? 0, "adjustmentsIncreaseMinor"),
586
+ itcsMinor: nonNegInt(raw.itcsMinor, "itcsMinor"),
587
+ adjustmentsDecreaseMinor: nonNegInt(raw.adjustmentsDecreaseMinor ?? 0, "adjustmentsDecreaseMinor"),
588
+ instalmentsMinor: nonNegInt(raw.instalmentsMinor ?? 0, "instalmentsMinor"),
589
+ rebatesMinor: nonNegInt(raw.rebatesMinor ?? 0, "rebatesMinor"),
590
+ selfAssessedRealPropertyMinor: nonNegInt(raw.selfAssessedRealPropertyMinor ?? 0, "selfAssessedRealPropertyMinor"),
591
+ selfAssessedOtherMinor: nonNegInt(raw.selfAssessedOtherMinor ?? 0, "selfAssessedOtherMinor")
592
+ };
593
+ },
594
+ compute(input) {
595
+ const lines = gst34Template.calculate({
596
+ 101: input.salesMinor,
597
+ 103: input.taxCollectedMinor,
598
+ 106: input.itcsMinor
599
+ }, {
600
+ 104: input.adjustmentsIncreaseMinor ?? 0,
601
+ 107: input.adjustmentsDecreaseMinor ?? 0,
602
+ 110: input.instalmentsMinor ?? 0,
603
+ 111: input.rebatesMinor ?? 0,
604
+ 205: input.selfAssessedRealPropertyMinor ?? 0,
605
+ 405: input.selfAssessedOtherMinor ?? 0
606
+ });
607
+ return {
608
+ jurisdiction: "CA",
609
+ category: "gst-hst",
610
+ period: input.period,
611
+ totalOwing: lines["113C"] ?? 0,
612
+ currency: "CAD",
613
+ breakdown: {
614
+ lines,
615
+ netTaxMinor: lines[109] ?? 0,
616
+ refundMinor: lines[114] ?? 0,
617
+ owingMinor: lines[115] ?? 0
618
+ }
619
+ };
620
+ },
621
+ render(obligation) {
622
+ return JSON.stringify({
623
+ form: "GST34",
624
+ ...obligation
625
+ }, null, 2);
626
+ }
627
+ };
628
+ /**
629
+ * Settlement adapter. Owing: clear the collected-liability, recognize the
630
+ * ITC receivable consumption, pay cash. Refund: net tax is negative — a
631
+ * receivable from CRA. Roles: `gst-hst-payable`, `gst-hst-itc-receivable`,
632
+ * `cash`, `cra-receivable`.
633
+ */
634
+ const gst34LedgerAdapter = { toJournalEntries(obligation, options) {
635
+ const b = obligation.breakdown;
636
+ const date = options.postingDate ?? obligation.period.end;
637
+ const collected = b.lines[105] ?? 0;
638
+ const itcs = b.lines[108] ?? 0;
639
+ const items = [];
640
+ if (collected > 0) items.push({
641
+ account: options.resolveAccount("gst-hst-payable"),
642
+ debit: collected,
643
+ credit: 0
644
+ });
645
+ if (itcs > 0) items.push({
646
+ account: options.resolveAccount("gst-hst-itc-receivable"),
647
+ debit: 0,
648
+ credit: itcs
649
+ });
650
+ if (obligation.totalOwing > 0) items.push({
651
+ account: options.resolveAccount("cash"),
652
+ debit: 0,
653
+ credit: obligation.totalOwing
654
+ });
655
+ else if (obligation.totalOwing < 0) items.push({
656
+ account: options.resolveAccount("cra-receivable"),
657
+ debit: -obligation.totalOwing,
658
+ credit: 0
659
+ });
660
+ const diff = items.reduce((s, i) => s + i.debit, 0) - items.reduce((s, i) => s + i.credit, 0);
661
+ if (diff !== 0) items.push({
662
+ account: options.resolveAccount(diff > 0 ? "cra-receivable" : "gst-hst-payable"),
663
+ debit: diff < 0 ? -diff : 0,
664
+ credit: diff > 0 ? diff : 0
665
+ });
666
+ return [{
667
+ journalType: "TAX",
668
+ date,
669
+ label: `GST/HST return (GST34) — ${obligation.period.label}`,
670
+ ...options.partnerId !== void 0 ? { partner: options.partnerId } : {},
671
+ journalItems: items
672
+ }];
673
+ } };
674
+ //#endregion
675
+ export { getCombinedRate as _, SIMPLIFIED_DIGITAL_THRESHOLD_MINOR as a, resetCaTaxRuleExtensions as b, calculateCaSalesTax as c, placeOfSupply as d, CA_SUPPLY_CATEGORIES as f, PROVINCIAL_REGIMES as g, GST_RATE as h, PSB_SMALL_SUPPLIER_THRESHOLD_MINOR as i, claimableItc as l, pstApplies as m, gst34LedgerAdapter as n, SMALL_SUPPLIER_THRESHOLD_MINOR as o, classifySupply as p, gst34Template as r, VEHICLE_ITC_CEILINGS as s, gst34Engine as t, isSmallSupplier as u, getProvincialRegime as v, extendCaTaxRules as y };