@classytic/ca-tax 0.0.1 → 0.0.5
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/CHANGELOG.md +155 -0
- package/dist/forms/index.d.mts +2 -2
- package/dist/forms/index.mjs +2 -2
- package/dist/forms.mjs +497 -20
- package/dist/index.d.mts +9 -4
- package/dist/index.mjs +3 -3
- package/dist/index3.d.mts +721 -96
- package/dist/index4.d.mts +3 -3
- package/dist/t2/index.d.mts +3 -3
- package/dist/t2/index.mjs +3 -3
- package/dist/t2.mjs +620 -66
- package/package.json +4 -2
package/dist/t2.mjs
CHANGED
|
@@ -262,7 +262,8 @@ const AT1_SCHEDULES_WITH_BUILDERS = Object.freeze([
|
|
|
262
262
|
"018",
|
|
263
263
|
"020",
|
|
264
264
|
"021",
|
|
265
|
-
"029"
|
|
265
|
+
"029",
|
|
266
|
+
"4970"
|
|
266
267
|
]);
|
|
267
268
|
/**
|
|
268
269
|
* Schedules the engine computes but cannot yet file.
|
|
@@ -431,21 +432,182 @@ function schedule18Values(result) {
|
|
|
431
432
|
values
|
|
432
433
|
};
|
|
433
434
|
}
|
|
434
|
-
|
|
435
|
+
/**
|
|
436
|
+
* Field ids read off the live, TRA-certified form
|
|
437
|
+
* (`research/sources/tra-forms/pdf/AT1SCH29-*.pdf`, Rev. 2026-06):
|
|
438
|
+
*
|
|
439
|
+
* Page 1 — Eligible Expenditures, filed only when the caller supplies
|
|
440
|
+
* `eligible` (from `computeIegEligibleExpenditures` in
|
|
441
|
+
* `schedule29-eligible-expenditures.ts`):
|
|
442
|
+
* 003 federal amount of qualified/current SR&ED expenditures (T661 line
|
|
443
|
+
* 559 or 557 — see `iegT661SourceLine`)
|
|
444
|
+
* 005/007/009/011/025 the five adjustments; 031 = 005 − 007 + 009 + 011 + 025
|
|
445
|
+
* 040 primary field of science or technology (1–4), when the caller
|
|
446
|
+
* supplies `primaryFieldCode`
|
|
447
|
+
*
|
|
448
|
+
* Page 1/2 — the grant itself:
|
|
449
|
+
* 110 8% of the capped expenditures
|
|
450
|
+
* 112 12% of the increment above base — the non-associated path. Filed
|
|
451
|
+
* INSTEAD of 125, never alongside it.
|
|
452
|
+
* 125 12% of the associated path's allocated allowed amount — filed
|
|
453
|
+
* instead of 112 when `result.enhancedRateBasis === 'associated'`.
|
|
454
|
+
* 126 taxable capital the line-128 factor was computed from
|
|
455
|
+
* 128 the taxable-capital reduction factor, 0 to 1
|
|
456
|
+
* 130 (110 + 112-or-125) × 128 — the credit before recapture
|
|
457
|
+
* 132 recapture, only where the caller supplied one
|
|
458
|
+
* 134 NET IEG — the figure AT1 page 2, line 129 carries forward
|
|
459
|
+
*
|
|
460
|
+
* Page 3 — the Agreement Among Associated Corporations, filed only when the
|
|
461
|
+
* group agreed one (`agreement`, from `computeIegAgreement` in
|
|
462
|
+
* `schedule29-ieg-group.ts`); omitted entirely for a non-associated claim:
|
|
463
|
+
* 200/202/204/206/208 the longest-year member's CAN, that member's own
|
|
464
|
+
* tax year begin/end, days in that year, and the resulting $4M-pool
|
|
465
|
+
* maximum for the group
|
|
466
|
+
* Per member, occurrence incrementing in the order given (claimant first):
|
|
467
|
+
* 220 Federal Business Number, 230 Alberta CAN, 235 current taxation
|
|
468
|
+
* year end, 240 allocated expenditure limit, 245 current-year
|
|
469
|
+
* expenditures, 250 prior year 1, 260 prior year 2, 265 taxable capital
|
|
470
|
+
* (prior year), 267 individual maximum allowed amount, 268 allocated
|
|
471
|
+
* allowed amount. The form has NO line for the member's NAME — 220 is
|
|
472
|
+
* the Federal Business Number, confirmed by re-rendering page 3 of the
|
|
473
|
+
* live form (a prior version of this function filed `name` at 220,
|
|
474
|
+
* which put free text in a box the form defines as a 9-10 digit account
|
|
475
|
+
* number; `name` is not transmitted at all, matching the form).
|
|
476
|
+
* 270/275/280/290/300 Σ 240/245/250/260/265 across every member
|
|
477
|
+
* 310 the group Allowed Amount — 275 − (280 + 290) / 2
|
|
478
|
+
* 320 Σ 268
|
|
479
|
+
* 325 the claiming corporation's own 268 — this is what feeds line 125
|
|
480
|
+
* above (`associatedAllowedAmount` in `computeIeg`'s input)
|
|
481
|
+
*
|
|
482
|
+
* 128 is a RATIO, not a dollar figure — formatted to 6 decimal places, the
|
|
483
|
+
* same precision `schedule2.ts`'s Reg 402 allocation factor uses. That
|
|
484
|
+
* precision is this codebase's own convention, not something confirmed
|
|
485
|
+
* against TRA's certification validator for THIS field; verify before relying
|
|
486
|
+
* on it for a corporation whose taxable capital sits inside the $10M–$50M
|
|
487
|
+
* band. See `schedule29-ieg.ts` for why 128 exists and the one unresolved
|
|
488
|
+
* discrepancy in how 130 is meant to be computed.
|
|
489
|
+
*/
|
|
490
|
+
function schedule29Values(result, agreement, eligible, primaryFieldCode) {
|
|
435
491
|
const values = [];
|
|
436
|
-
const put = (fieldId, value) => values.push({
|
|
437
|
-
lineItemId: at1LineItemId("029", fieldId),
|
|
492
|
+
const put = (fieldId, value, occurrence = 1) => values.push({
|
|
493
|
+
lineItemId: at1LineItemId("029", fieldId, occurrence),
|
|
438
494
|
value
|
|
439
495
|
});
|
|
496
|
+
if (eligible) {
|
|
497
|
+
put("003", eligible.federalAmount);
|
|
498
|
+
put("005", eligible.albertaPortion);
|
|
499
|
+
if (eligible.federalProxyAmount > 0) put("007", eligible.federalProxyAmount);
|
|
500
|
+
if (eligible.albertaProxyAmount > 0) put("009", eligible.albertaProxyAmount);
|
|
501
|
+
if (eligible.iegReducingFederalExpenditure > 0) put("011", eligible.iegReducingFederalExpenditure);
|
|
502
|
+
if (eligible.repaymentOrContractPayment > 0) put("025", eligible.repaymentOrContractPayment);
|
|
503
|
+
put("031", eligible.totalEligibleExpenditures);
|
|
504
|
+
}
|
|
505
|
+
if (primaryFieldCode !== void 0) put("040", primaryFieldCode);
|
|
440
506
|
put("110", result.creditAtBaseRate);
|
|
441
|
-
put("112", result.creditAtEnhancedRate);
|
|
442
|
-
put("
|
|
507
|
+
put(result.enhancedRateBasis === "associated" ? "125" : "112", result.creditAtEnhancedRate);
|
|
508
|
+
put("126", result.taxableCapital);
|
|
509
|
+
put("128", result.reductionFactor.toFixed(6));
|
|
510
|
+
put("130", result.grossIeg);
|
|
511
|
+
if (result.recapture > 0) put("132", result.recapture);
|
|
512
|
+
put("134", result.ieg);
|
|
513
|
+
if (agreement) {
|
|
514
|
+
put("200", agreement.longestYearCan);
|
|
515
|
+
put("202", agreement.longestYearBegin);
|
|
516
|
+
put("204", agreement.longestYearEnd);
|
|
517
|
+
put("206", agreement.daysInLongestYear);
|
|
518
|
+
put("208", agreement.maximumExpenditureLimit);
|
|
519
|
+
agreement.members.forEach((m, i) => {
|
|
520
|
+
const n = i + 1;
|
|
521
|
+
if (m.federalBusinessNumber !== void 0) put("220", m.federalBusinessNumber, n);
|
|
522
|
+
if (m.albertaCan !== void 0) put("230", m.albertaCan, n);
|
|
523
|
+
if (m.currentTaxationYearEnd !== void 0) put("235", m.currentTaxationYearEnd, n);
|
|
524
|
+
put("240", m.allocatedExpenditureLimit, n);
|
|
525
|
+
put("245", m.currentYearExpenditures, n);
|
|
526
|
+
put("250", m.priorYear1, n);
|
|
527
|
+
put("260", m.priorYear2, n);
|
|
528
|
+
put("265", m.taxableCapitalPriorYear, n);
|
|
529
|
+
put("267", m.individualMaximumAllowedAmount, n);
|
|
530
|
+
put("268", m.allocatedAllowedAmount, n);
|
|
531
|
+
});
|
|
532
|
+
put("270", agreement.totalAllocatedLimit);
|
|
533
|
+
put("275", agreement.totalCurrentYearExpenditures);
|
|
534
|
+
put("280", agreement.totalPriorYear1);
|
|
535
|
+
put("290", agreement.totalPriorYear2);
|
|
536
|
+
put("300", agreement.totalTaxableCapitalPriorYear);
|
|
537
|
+
put("310", agreement.groupAllowedAmount);
|
|
538
|
+
put("320", agreement.totalAllocatedAllowedAmount);
|
|
539
|
+
put("325", agreement.claimantAllocatedAllowedAmount);
|
|
540
|
+
}
|
|
443
541
|
return {
|
|
444
542
|
scheduleId: "029",
|
|
445
543
|
values
|
|
446
544
|
};
|
|
447
545
|
}
|
|
448
546
|
/**
|
|
547
|
+
* A separate attachment from Schedule 29 itself, one occurrence per Alberta
|
|
548
|
+
* SR&ED project (101/103/105/107/109/111/113), a TOTAL row (repeats the same
|
|
549
|
+
* field ids at a fixed trailing occurrence — see below), and the jurisdiction
|
|
550
|
+
* table (135–170, occurrence 1, no per-jurisdiction row structure since each
|
|
551
|
+
* jurisdiction has its own field id already).
|
|
552
|
+
*
|
|
553
|
+
* ⚠ The schedule id (`'4970'`) and line-item id scheme used here are a
|
|
554
|
+
* PLACEHOLDER — TRA's own published Guide confirms the field NUMBERS
|
|
555
|
+
* (verified twice, independently) but not the NetFile wire-format encoding
|
|
556
|
+
* for this attachment. See `at4970-ieg-projects.ts` and `forms/at4970.ts`
|
|
557
|
+
* for the same caveat. Verify before relying on this for live filing.
|
|
558
|
+
*
|
|
559
|
+
* The TOTAL row is filed at occurrence `projects.length + 1` — one past the
|
|
560
|
+
* last project row — so a real project can never collide with it.
|
|
561
|
+
*/
|
|
562
|
+
function schedule4970Values(result) {
|
|
563
|
+
const values = [];
|
|
564
|
+
const put = (fieldId, value, occurrence = 1) => values.push({
|
|
565
|
+
lineItemId: `4970${fieldId}${String(occurrence).padStart(3, "0")}`,
|
|
566
|
+
value
|
|
567
|
+
});
|
|
568
|
+
result.projects.forEach((p, i) => {
|
|
569
|
+
const n = i + 1;
|
|
570
|
+
put("101", p.title, n);
|
|
571
|
+
if (p.projectCode !== void 0) put("103", p.projectCode, n);
|
|
572
|
+
put("105", p.albertaPortion, n);
|
|
573
|
+
put("107", p.otherPortion, n);
|
|
574
|
+
put("109", p.salariesAndWages, n);
|
|
575
|
+
if (p.federalProxyAmount > 0) put("111", p.federalProxyAmount, n);
|
|
576
|
+
if (p.albertaProxyAmount > 0) put("113", p.albertaProxyAmount, n);
|
|
577
|
+
});
|
|
578
|
+
const totalRow = result.projects.length + 1;
|
|
579
|
+
put("105", result.totals.albertaPortion, totalRow);
|
|
580
|
+
put("107", result.totals.otherPortion, totalRow);
|
|
581
|
+
put("109", result.totals.salariesAndWages, totalRow);
|
|
582
|
+
put("111", result.totals.federalProxyAmount, totalRow);
|
|
583
|
+
put("113", result.totals.albertaProxyAmount, totalRow);
|
|
584
|
+
const jurisdictionFields = {
|
|
585
|
+
alberta: "135",
|
|
586
|
+
britishColumbia: "137",
|
|
587
|
+
manitoba: "139",
|
|
588
|
+
newBrunswick: "141",
|
|
589
|
+
newfoundlandAndLabrador: "143",
|
|
590
|
+
northwestTerritories: "145",
|
|
591
|
+
novaScotia: "147",
|
|
592
|
+
nunavut: "149",
|
|
593
|
+
ontario: "151",
|
|
594
|
+
princeEdwardIsland: "153",
|
|
595
|
+
quebec: "155",
|
|
596
|
+
saskatchewan: "157",
|
|
597
|
+
yukon: "159",
|
|
598
|
+
other: "161"
|
|
599
|
+
};
|
|
600
|
+
for (const j of result.jurisdictions) {
|
|
601
|
+
const fieldId = jurisdictionFields[j.jurisdiction];
|
|
602
|
+
if (fieldId && j.amountIncurred > 0) put(fieldId, j.amountIncurred);
|
|
603
|
+
}
|
|
604
|
+
if (result.jurisdictionTotal > 0) put("170", result.jurisdictionTotal);
|
|
605
|
+
return {
|
|
606
|
+
scheduleId: "4970",
|
|
607
|
+
values
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
449
611
|
* The Schedule 21 → Schedule 12 carry-forwards, as the form states them beside
|
|
450
612
|
* each line:
|
|
451
613
|
*
|
|
@@ -744,6 +906,37 @@ function schedule16Values(result) {
|
|
|
744
906
|
};
|
|
745
907
|
}
|
|
746
908
|
//#endregion
|
|
909
|
+
//#region src/t2/at1/schedules/at4970-ieg-projects.ts
|
|
910
|
+
const nn$25 = (v) => Math.max(0, Math.round(v ?? 0));
|
|
911
|
+
function computeAt4970(input) {
|
|
912
|
+
const projects = input.projects.map((p) => ({
|
|
913
|
+
title: p.title,
|
|
914
|
+
...p.projectCode !== void 0 ? { projectCode: p.projectCode } : {},
|
|
915
|
+
albertaPortion: nn$25(p.albertaPortion),
|
|
916
|
+
otherPortion: nn$25(p.otherPortion),
|
|
917
|
+
salariesAndWages: nn$25(p.salariesAndWages),
|
|
918
|
+
federalProxyAmount: nn$25(p.federalProxyAmount),
|
|
919
|
+
albertaProxyAmount: nn$25(p.albertaProxyAmount)
|
|
920
|
+
}));
|
|
921
|
+
const totals = {
|
|
922
|
+
albertaPortion: projects.reduce((s, p) => s + p.albertaPortion, 0),
|
|
923
|
+
otherPortion: projects.reduce((s, p) => s + p.otherPortion, 0),
|
|
924
|
+
salariesAndWages: projects.reduce((s, p) => s + p.salariesAndWages, 0),
|
|
925
|
+
federalProxyAmount: projects.reduce((s, p) => s + p.federalProxyAmount, 0),
|
|
926
|
+
albertaProxyAmount: projects.reduce((s, p) => s + p.albertaProxyAmount, 0)
|
|
927
|
+
};
|
|
928
|
+
const jurisdictions = (input.jurisdictions ?? []).map((j) => ({
|
|
929
|
+
jurisdiction: j.jurisdiction,
|
|
930
|
+
amountIncurred: nn$25(j.amountIncurred)
|
|
931
|
+
}));
|
|
932
|
+
return {
|
|
933
|
+
projects,
|
|
934
|
+
totals,
|
|
935
|
+
jurisdictions,
|
|
936
|
+
jurisdictionTotal: jurisdictions.reduce((s, j) => s + j.amountIncurred, 0)
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
//#endregion
|
|
747
940
|
//#region src/t2/at1/schedules/schedule2.ts
|
|
748
941
|
/** Round to six decimal places — the AT1 allocation-factor precision. */
|
|
749
942
|
function round6(n) {
|
|
@@ -764,40 +957,182 @@ function computeAllocationFactor(input) {
|
|
|
764
957
|
return round6(factor);
|
|
765
958
|
}
|
|
766
959
|
//#endregion
|
|
960
|
+
//#region src/t2/at1/schedules/schedule29-eligible-expenditures.ts
|
|
961
|
+
/**
|
|
962
|
+
* AT1 Schedule 29 page 1 — Eligible Expenditures for IEG Purposes (lines
|
|
963
|
+
* 001–040). AT29's own worksheet for deriving the "eligible expenditures"
|
|
964
|
+
* figure (line 031) that everything else on this schedule — the base amount,
|
|
965
|
+
* the increment, the Agreement's per-member 245/250/260 — is built from.
|
|
966
|
+
*
|
|
967
|
+
* ── Why this module exists separately from `schedule29-ieg.ts` ─────────────
|
|
968
|
+
*
|
|
969
|
+
* `computeIeg` took `eligibleExpenditures` as a bare number input. It is not
|
|
970
|
+
* one — it is DERIVED, by a six-line formula off federal T661, and a prior
|
|
971
|
+
* version of this package never modelled that derivation at all: it skipped
|
|
972
|
+
* page 1 of the live form entirely (only pages 2 and 3 were ever rendered),
|
|
973
|
+
* so every caller had to compute line 031 by hand outside the engine, with
|
|
974
|
+
* nothing here to check the arithmetic. Confirmed missing by rendering the
|
|
975
|
+
* live, TRA-certified form (`research/sources/tra-forms/pdf/AT1SCH29-*.pdf`,
|
|
976
|
+
* page 1) and independently re-verified against two of TRA's own published
|
|
977
|
+
* worked examples (`research/sources/tra-guides/tra-guide-claiming-the-
|
|
978
|
+
* innovation-employment-grant.pdf`, Examples 3 and 4) — see
|
|
979
|
+
* `research/knowledge-base/at1-schedule-29-ieg-mechanics.md`.
|
|
980
|
+
*
|
|
981
|
+
* ── The formula ──────────────────────────────────────────────────────────
|
|
982
|
+
*
|
|
983
|
+
* 003 federal amount of qualified/current SR&ED expenditures, T661 line
|
|
984
|
+
* 559 or 557 (see below)
|
|
985
|
+
* 005 portion of 003 carried out in Alberta — normally the sum of every
|
|
986
|
+
* AT4970 project row's own 105 column (`at4970-ieg-projects.ts`)
|
|
987
|
+
* 007 deduct: federal prescribed proxy amount included in the Alberta
|
|
988
|
+
* portion of 003 — normally Σ AT4970's 111 column
|
|
989
|
+
* 009 add: Alberta proxy amount — normally Σ AT4970's 113 column
|
|
990
|
+
* 011 add: IEG that reduced the federal expenditure IN THE TAXATION YEAR
|
|
991
|
+
* — zero for a first-time current-year claim reported on the
|
|
992
|
+
* pre-deduction federal figures (see the Step 1 / Step 2 note below)
|
|
993
|
+
* 025 add: the Alberta portion of a repayment of government assistance
|
|
994
|
+
* (other than an IEG) OR A CONTRACT PAYMENT, relating to amounts in
|
|
995
|
+
* 005 from the current year or ANY PRECEDING taxation year — two
|
|
996
|
+
* independent triggers, not one
|
|
997
|
+
* 031 TOTAL = 005 − 007 + 009 + 011 + 025
|
|
998
|
+
*
|
|
999
|
+
* 031 is what `computeIeg` calls `eligibleExpenditures` and what an
|
|
1000
|
+
* Agreement member's own `currentYearExpenditures` (line 245) is — the same
|
|
1001
|
+
* formula, computed once per corporation per year, whether or not that
|
|
1002
|
+
* corporation is associated.
|
|
1003
|
+
*
|
|
1004
|
+
* ── The federal T661 line 557 vs 559 split (Fall 2026 re-certification) ────
|
|
1005
|
+
*
|
|
1006
|
+
* TRA's own certification correspondence and the live form agree: for a
|
|
1007
|
+
* taxation year ending ON OR BEFORE 2024-12-15, line 003 comes from federal
|
|
1008
|
+
* T661 line 559 (qualified SR&ED expenditures); for a taxation year ending
|
|
1009
|
+
* ON OR AFTER 2024-12-16, it comes from T661 line 557 (CURRENT SR&ED
|
|
1010
|
+
* expenditures) instead — a different federal figure, not a renumbering.
|
|
1011
|
+
* `iegT661SourceLine` below is purely informational (which federal line a
|
|
1012
|
+
* preparer should be told to copy from); it does not change the Alberta
|
|
1013
|
+
* arithmetic itself, which is identical either way.
|
|
1014
|
+
*
|
|
1015
|
+
* ── The "Step 1 / Step 2" columns in TRA's Guide are not two form fields ────
|
|
1016
|
+
*
|
|
1017
|
+
* The Guide illustrates every page-1 line twice — once using the federal
|
|
1018
|
+
* figures BEFORE the current year's own IEG is netted against them as
|
|
1019
|
+
* government assistance (T661 line 513), once AFTER, with line 011 adding
|
|
1020
|
+
* the IEG back. Both worked examples land on the IDENTICAL line 031 either
|
|
1021
|
+
* way (011 exists specifically to undo the 513 netting for ALBERTA's own
|
|
1022
|
+
* base, so it cannot shrink itself). Only ONE set of figures is filed. Every
|
|
1023
|
+
* TRA test case gives the pre-deduction ("Step 1") federal figures — use
|
|
1024
|
+
* those directly, with 011 = 0 for a first-time current-year claim. Do not
|
|
1025
|
+
* build a literal two-pass loop; there is nothing for it to converge on that
|
|
1026
|
+
* isn't already true of the Step 1 figures alone.
|
|
1027
|
+
*
|
|
1028
|
+
* Whole dollars, pure.
|
|
1029
|
+
*/
|
|
1030
|
+
/** Which federal T661 line line 003 sources from, given a taxation year end. */
|
|
1031
|
+
function iegT661SourceLine(taxationYearEnd) {
|
|
1032
|
+
return taxationYearEnd >= "2024-12-16" ? "557" : "559";
|
|
1033
|
+
}
|
|
1034
|
+
const nn$24 = (v) => Math.round(v ?? 0);
|
|
1035
|
+
/** Line 031 = 005 − 007 + 009 + 011 + 025. Every term but 005 defaults to nil. */
|
|
1036
|
+
function computeIegEligibleExpenditures(input) {
|
|
1037
|
+
const federalAmount = nn$24(input.federalAmount);
|
|
1038
|
+
const albertaPortion = nn$24(input.albertaPortion);
|
|
1039
|
+
const federalProxyAmount = nn$24(input.federalProxyAmount);
|
|
1040
|
+
const albertaProxyAmount = nn$24(input.albertaProxyAmount);
|
|
1041
|
+
const iegReducingFederalExpenditure = nn$24(input.iegReducingFederalExpenditure);
|
|
1042
|
+
const repaymentOrContractPayment = nn$24(input.repaymentOrContractPayment);
|
|
1043
|
+
return {
|
|
1044
|
+
federalAmount,
|
|
1045
|
+
albertaPortion,
|
|
1046
|
+
federalProxyAmount,
|
|
1047
|
+
albertaProxyAmount,
|
|
1048
|
+
iegReducingFederalExpenditure,
|
|
1049
|
+
repaymentOrContractPayment,
|
|
1050
|
+
totalEligibleExpenditures: albertaPortion - federalProxyAmount + albertaProxyAmount + iegReducingFederalExpenditure + repaymentOrContractPayment
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
//#endregion
|
|
767
1054
|
//#region src/t2/at1/schedules/schedule29-ieg.ts
|
|
768
1055
|
/**
|
|
769
1056
|
* AT1 Schedule 29 — Alberta Innovation Employment Grant (IEG). AT1 line 129.
|
|
770
1057
|
*
|
|
771
1058
|
* A refundable grant on eligible R&D (SR&ED) carried out in Alberta:
|
|
772
|
-
* - 8%
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
1059
|
+
* - line 110: 8% of the CAPPED eligible expenditures (the full amount, not
|
|
1060
|
+
* just the portion below base)
|
|
1061
|
+
* - line 112 (non-associated): 12% of the increment ABOVE the base level of
|
|
1062
|
+
* spending — base level = average of the prior two years'
|
|
1063
|
+
* eligible Alberta expenditures (associated group aggregated —
|
|
1064
|
+
* pass all members' figures)
|
|
1065
|
+
* - line 125 (associated, via a filed agreement): 12% of the LESSER of the
|
|
1066
|
+
* expenditure limit and the agreement's allocated allowed
|
|
1067
|
+
* amount — see `computeIegAgreement` in `schedule29-ieg-group.ts`.
|
|
1068
|
+
* A genuinely different formula, not a variant of 112's — it does
|
|
1069
|
+
* not reference actual spending at all once the agreement exists.
|
|
1070
|
+
*
|
|
1071
|
+
* Eligible expenditures are capped at a $4,000,000 annual limit (shared across an
|
|
1072
|
+
* associated group; NOT reduced by taxable capital — see below). Pure, integer
|
|
1073
|
+
* whole dollars.
|
|
1074
|
+
*
|
|
1075
|
+
* ── The taxable-capital grind applies to the CREDIT, not the limit ──────────
|
|
1076
|
+
*
|
|
1077
|
+
* A prior version of this module reduced the $4,000,000 expenditure limit itself
|
|
1078
|
+
* on a straight-line taxable-capital grind ($10M → $50M) — modelled on the
|
|
1079
|
+
* federal SBD grind's shape, but wrong for this form. Rendering the live,
|
|
1080
|
+
* TRA-certified Schedule 29 (`research/sources/tra-forms/pdf/AT1SCH29-*.pdf`,
|
|
1081
|
+
* Rev. 2026-06 — cross-checked against AuraTax's live rendering of the same
|
|
1082
|
+
* schedule, `research/validation/auratax/2026-08-25-at1-schedule1-royalty/`)
|
|
1083
|
+
* shows the grind lives at **line 128**, a REDUCTION FACTOR from 0 to 1:
|
|
1084
|
+
*
|
|
1085
|
+
* line 128 = (($40,000,000 − (line 126 − $10,000,000)) / $40,000,000)
|
|
1086
|
+
* (enter 1 if taxable capital ≤ $10,000,000)
|
|
1087
|
+
* line 130 = (line 110 + (line 112 or line 125)) × line 128
|
|
1088
|
+
*
|
|
1089
|
+
* i.e. the credit is computed in FULL against the unreduced $4M limit, and ONLY
|
|
1090
|
+
* THEN scaled by the taxable-capital factor — not the limit ground down first
|
|
1091
|
+
* and the credit computed against the smaller number. The two are NOT
|
|
1092
|
+
* equivalent except at the band's endpoints (TC ≤ $10M ⇒ both give the full
|
|
1093
|
+
* credit; TC ≥ $50M ⇒ both give nil).
|
|
1094
|
+
*
|
|
1095
|
+
* ── One thing not yet resolved, and worth raising with TRA directly ─────────
|
|
1096
|
+
*
|
|
1097
|
+
* The AT1 jacket's OWN business-rule table (`research/sources/tra-spec/
|
|
1098
|
+
* AT1-Chapter3-2025.2-full.txt`, version 2025.2 / August 2025 — ten months
|
|
1099
|
+
* OLDER than the Schedule 29 PDF above) states jacket line 129 as
|
|
1100
|
+
* `(029110 + 029112 X 029128) minus 029132` — multiplying ONLY line 112 by the
|
|
1101
|
+
* factor, not the sum of 110 and 112. That contradicts Schedule 29's own
|
|
1102
|
+
* printed caption, which the certified oracle (AuraTax) also renders as
|
|
1103
|
+
* "sum then multiply". This module follows the newer, doubly-corroborated
|
|
1104
|
+
* Schedule 29 form. Confirm with TRA before relying on this for an associated
|
|
1105
|
+
* corporation with taxable capital between $10M and $50M — the disagreement
|
|
1106
|
+
* changes the filed dollar figure.
|
|
1107
|
+
*
|
|
1108
|
+
* ── The formal Agreement (line 125) ──────────────────────────────────────────
|
|
1109
|
+
*
|
|
1110
|
+
* TRA's Test Case 3 (four associated corporations, `research/sources/
|
|
1111
|
+
* tra-test-cases/Alberta Test Case 3 - Fall 2025.txt`) is the associated path:
|
|
1112
|
+
* pass `associatedAllowedAmount` (line 325 of `computeIegAgreement`'s result)
|
|
1113
|
+
* and this function uses line 125's formula instead of line 112's. Day-of-year
|
|
1114
|
+
* proration of the $4M pool for a short or long taxation year (lines 104/108/
|
|
1115
|
+
* 206/208) is modelled in `computeIegAgreement`, not here — this function
|
|
1116
|
+
* takes the already-allocated `expenditureLimit` as an input.
|
|
786
1117
|
*/
|
|
787
1118
|
const IEG_2024 = Object.freeze({
|
|
788
1119
|
BASE_RATE: .08,
|
|
789
|
-
ENHANCED_RATE: .
|
|
1120
|
+
ENHANCED_RATE: .12,
|
|
790
1121
|
MAX_EXPENDITURE: 4e6,
|
|
791
1122
|
TC_GRIND_LOWER: 1e7,
|
|
792
1123
|
TC_GRIND_UPPER: 5e7,
|
|
793
1124
|
PRIOR_YEARS: 2
|
|
794
1125
|
});
|
|
795
|
-
/**
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1126
|
+
/**
|
|
1127
|
+
* The line-128 reduction factor: 1 at or below $10M taxable capital, straight-
|
|
1128
|
+
* line down to 0 at $50M. Multiplies the COMBINED base + enhanced credit at
|
|
1129
|
+
* line 130 — it does not touch the $4,000,000 expenditure limit.
|
|
1130
|
+
*/
|
|
1131
|
+
function computeIegReductionFactor(taxableCapital, lower = IEG_2024.TC_GRIND_LOWER, upper = IEG_2024.TC_GRIND_UPPER) {
|
|
1132
|
+
const tc = Math.max(0, taxableCapital);
|
|
1133
|
+
if (tc <= lower) return 1;
|
|
1134
|
+
const band = upper - lower;
|
|
1135
|
+
return Math.max(0, Math.min(1, (band - (tc - lower)) / band));
|
|
801
1136
|
}
|
|
802
1137
|
/**
|
|
803
1138
|
* Base level of spending = average of the prior years' eligible Alberta
|
|
@@ -813,16 +1148,26 @@ function computeIeg(input) {
|
|
|
813
1148
|
const limit = input.expenditureLimit ?? IEG_2024.MAX_EXPENDITURE;
|
|
814
1149
|
const cappedExpenditures = Math.max(0, Math.min(input.eligibleExpenditures, limit));
|
|
815
1150
|
const incrementalExpenditures = Math.max(0, cappedExpenditures - input.baseAmount);
|
|
816
|
-
const
|
|
817
|
-
const
|
|
818
|
-
const creditAtEnhancedRate = Math.round(IEG_2024.ENHANCED_RATE * incrementalExpenditures);
|
|
1151
|
+
const creditAtBaseRate = Math.round(IEG_2024.BASE_RATE * cappedExpenditures);
|
|
1152
|
+
const enhancedRateBasis = input.associatedAllowedAmount !== void 0 ? "associated" : "non-associated";
|
|
1153
|
+
const creditAtEnhancedRate = enhancedRateBasis === "associated" ? Math.round(IEG_2024.ENHANCED_RATE * Math.min(limit, Math.max(0, input.associatedAllowedAmount ?? 0))) : Math.round(IEG_2024.ENHANCED_RATE * incrementalExpenditures);
|
|
1154
|
+
const taxableCapital = Math.max(0, input.taxableCapital ?? 0);
|
|
1155
|
+
const reductionFactor = computeIegReductionFactor(taxableCapital);
|
|
1156
|
+
const grossIeg = Math.round((creditAtBaseRate + creditAtEnhancedRate) * reductionFactor);
|
|
1157
|
+
const recapture = Math.max(0, Math.round(input.recapture ?? 0));
|
|
1158
|
+
const ieg = Math.max(0, grossIeg - recapture);
|
|
819
1159
|
return {
|
|
820
1160
|
cappedExpenditures,
|
|
821
1161
|
baseAmount: input.baseAmount,
|
|
822
1162
|
incrementalExpenditures,
|
|
823
1163
|
creditAtBaseRate,
|
|
824
1164
|
creditAtEnhancedRate,
|
|
825
|
-
|
|
1165
|
+
enhancedRateBasis,
|
|
1166
|
+
taxableCapital,
|
|
1167
|
+
reductionFactor,
|
|
1168
|
+
grossIeg,
|
|
1169
|
+
recapture,
|
|
1170
|
+
ieg
|
|
826
1171
|
};
|
|
827
1172
|
}
|
|
828
1173
|
//#endregion
|
|
@@ -831,20 +1176,27 @@ function computeIeg(input) {
|
|
|
831
1176
|
* AT1 Schedule 29 — the ASSOCIATED-GROUP layer for the Innovation Employment
|
|
832
1177
|
* Grant.
|
|
833
1178
|
*
|
|
834
|
-
* `computeIeg` grants one corporation its IEG given
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
* derives
|
|
1179
|
+
* `computeIeg` grants one corporation its IEG given its own eligible Alberta
|
|
1180
|
+
* expenditures, its base level of spending, its allocated expenditure limit, and
|
|
1181
|
+
* the group's taxable capital. The base level of spending is a **group** figure,
|
|
1182
|
+
* and this module derives it (along with the group's total taxable capital, for
|
|
1183
|
+
* the line-128 reduction factor `computeIeg` applies).
|
|
838
1184
|
*
|
|
839
1185
|
* ── Why a group layer is not optional ───────────────────────────────────────
|
|
840
1186
|
*
|
|
841
|
-
* A
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
* fails an audit.
|
|
1187
|
+
* A group that has prior-year spending elsewhere gets an **understated base**
|
|
1188
|
+
* when a member is computed alone, which overstates the 12% increment. And the
|
|
1189
|
+
* taxable-capital reduction factor (line 128, see `schedule29-ieg.ts`) is driven
|
|
1190
|
+
* by the WHOLE group's taxable capital, not one member's — a member with $10M of
|
|
1191
|
+
* its own taxable capital sees no grind in isolation, but the group's combined
|
|
1192
|
+
* capital may put it well into the $10M–$50M band. Both errors run in the
|
|
1193
|
+
* taxpayer's favour, which is the direction that fails an audit.
|
|
1194
|
+
*
|
|
1195
|
+
* The $4,000,000 expenditure limit itself is NOT reduced here — see
|
|
1196
|
+
* `schedule29-ieg.ts` for why the taxable-capital grind lives at line 128, not
|
|
1197
|
+
* the limit. `groupExpenditureLimit` below is the flat $4M shared pool, prorated
|
|
1198
|
+
* for a short or long taxation year by the caller if needed (day-of-year
|
|
1199
|
+
* proration is not modelled).
|
|
848
1200
|
*
|
|
849
1201
|
* ── Which year's figures ────────────────────────────────────────────────────
|
|
850
1202
|
*
|
|
@@ -871,6 +1223,37 @@ function computeIeg(input) {
|
|
|
871
1223
|
* contributes its taxable capital to the grind. Both cases appear in the Alberta
|
|
872
1224
|
* certification material and both are modelled.
|
|
873
1225
|
*
|
|
1226
|
+
* ── ⚠ This module's "group aggregated base amount" has no line on the real form ──
|
|
1227
|
+
*
|
|
1228
|
+
* Rendering Schedule 29 page 2 in full (a prior version of this package only
|
|
1229
|
+
* ever rendered pages 2 and 3's CREDIT section, never page 1) shows the base
|
|
1230
|
+
* amount (lines 114/116/118) captioned "*If the corporation is NOT
|
|
1231
|
+
* associated with one or more corporations in the taxation year, the Base
|
|
1232
|
+
* Amount is the amount that is the average of the eligible expenditures OF
|
|
1233
|
+
* THE CORPORATION for the two immediately preceding taxation years" — the
|
|
1234
|
+
* CLAIMANT's own figures alone, no group aggregation, and used ONLY for the
|
|
1235
|
+
* non-associated path (line 112). Confirmed blank in both of TRA's own
|
|
1236
|
+
* published worked examples whenever the corporation IS associated — the
|
|
1237
|
+
* associated path (line 125) uses no base amount at all, replacing the
|
|
1238
|
+
* whole mechanism with the Agreement's Allowed Amount instead (see
|
|
1239
|
+
* `computeIegAgreement` below). The live form also states an associated
|
|
1240
|
+
* corporation MUST complete the Agreement ("If 'Yes', complete page 3") —
|
|
1241
|
+
* so there is no scenario on the real form where a group is associated but
|
|
1242
|
+
* has not filed one. `computeIegGroupFigures`'s "aggregate every member's
|
|
1243
|
+
* prior-two-years spending into one shared base amount", used regardless of
|
|
1244
|
+
* associated/non-associated status by `computeAlbertaReturn`, therefore
|
|
1245
|
+
* does not correspond to anything a preparer could actually file — it is
|
|
1246
|
+
* harmless ONLY because `computeIeg`'s associated branch ignores
|
|
1247
|
+
* `baseAmount` entirely. Left in place rather than removed: not exercised by
|
|
1248
|
+
* any of TRA's Fall 2026 certification test cases (all either non-associated
|
|
1249
|
+
* with a genuine single-corporation base, or associated with a filed
|
|
1250
|
+
* Agreement whose own figures are what actually reach the credit), so a full
|
|
1251
|
+
* rearchitecture is deferred rather than rushed. `taxableCapital` for the
|
|
1252
|
+
* associated path is already sourced from the Agreement, not from here — see
|
|
1253
|
+
* `alberta-return.ts`. See `research/knowledge-base/
|
|
1254
|
+
* at1-schedule-29-ieg-mechanics.md` for the full comparison against the live
|
|
1255
|
+
* form and both of TRA's published worked examples.
|
|
1256
|
+
*
|
|
874
1257
|
* Whole dollars, pure.
|
|
875
1258
|
*/
|
|
876
1259
|
const nn$23 = (v) => Math.max(0, Math.round(v ?? 0));
|
|
@@ -897,7 +1280,7 @@ function computeIegGroupFigures(members, priorYears = IEG_2024.PRIOR_YEARS) {
|
|
|
897
1280
|
const totalPriorYearExpenditures = detail.reduce((s, m) => s + m.priorYearTotal, 0);
|
|
898
1281
|
return {
|
|
899
1282
|
groupTaxableCapital,
|
|
900
|
-
groupExpenditureLimit:
|
|
1283
|
+
groupExpenditureLimit: IEG_2024.MAX_EXPENDITURE,
|
|
901
1284
|
totalPriorYearExpenditures,
|
|
902
1285
|
groupBaseAmount: priorYears > 0 ? Math.round(totalPriorYearExpenditures / priorYears) : 0,
|
|
903
1286
|
members: detail,
|
|
@@ -952,6 +1335,74 @@ function allocateIegEvenly(groupExpenditureLimit, names) {
|
|
|
952
1335
|
allocated: i === 0 ? each + remainder : each
|
|
953
1336
|
})));
|
|
954
1337
|
}
|
|
1338
|
+
function computeAgreementMember(m, index, issues) {
|
|
1339
|
+
const allocatedExpenditureLimit = nn$23(m.allocatedExpenditureLimit);
|
|
1340
|
+
const currentYearExpenditures = nn$23(m.currentYearExpenditures);
|
|
1341
|
+
const priorYear1 = nn$23(m.priorYear1);
|
|
1342
|
+
const priorYear2 = nn$23(m.priorYear2);
|
|
1343
|
+
const base = Math.round((priorYear1 + priorYear2) / 2);
|
|
1344
|
+
const individualMaximumAllowedAmount = currentYearExpenditures - base;
|
|
1345
|
+
const days = Math.max(0, Math.min(m.daysInTaxYear ?? 365, 366));
|
|
1346
|
+
const dayProratedLimit = Math.round(4e6 * (days / 365));
|
|
1347
|
+
const dayProratedComponent = Math.min(dayProratedLimit, currentYearExpenditures) - base;
|
|
1348
|
+
const isClaimant = index === 0;
|
|
1349
|
+
if (m.hasAlbertaPermanentEstablishment === void 0 && !isClaimant) issues.push(`Alberta Schedule 29 Agreement: ${m.name} did not state whether it has a permanent establishment in Alberta. Treated as no permanent establishment (line 268 = nil) — a member without one is not eligible for the IEG even when its own formula would allow an amount. Confirm and re-enter if this member does have an Alberta PE.`);
|
|
1350
|
+
const hasAlbertaPermanentEstablishment = m.hasAlbertaPermanentEstablishment ?? isClaimant;
|
|
1351
|
+
const allocatedAllowedAmount = hasAlbertaPermanentEstablishment ? Math.max(0, Math.min(allocatedExpenditureLimit, individualMaximumAllowedAmount, dayProratedComponent)) : 0;
|
|
1352
|
+
return {
|
|
1353
|
+
name: m.name,
|
|
1354
|
+
...m.federalBusinessNumber !== void 0 ? { federalBusinessNumber: m.federalBusinessNumber } : {},
|
|
1355
|
+
...m.albertaCan !== void 0 ? { albertaCan: m.albertaCan } : {},
|
|
1356
|
+
...m.currentTaxationYearEnd !== void 0 ? { currentTaxationYearEnd: m.currentTaxationYearEnd } : {},
|
|
1357
|
+
allocatedExpenditureLimit,
|
|
1358
|
+
currentYearExpenditures,
|
|
1359
|
+
priorYear1,
|
|
1360
|
+
priorYear2,
|
|
1361
|
+
taxableCapitalPriorYear: nn$23(m.taxableCapitalPriorYear),
|
|
1362
|
+
hasAlbertaPermanentEstablishment,
|
|
1363
|
+
individualMaximumAllowedAmount,
|
|
1364
|
+
allocatedAllowedAmount
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* The formal Agreement Among Associated Corporations. The FIRST entry in
|
|
1369
|
+
* `input.members` must be the claiming corporation — its own line 268 becomes
|
|
1370
|
+
* line 325, which is what actually reaches `computeIeg`.
|
|
1371
|
+
*/
|
|
1372
|
+
function computeIegAgreement(input) {
|
|
1373
|
+
const issues = [];
|
|
1374
|
+
if (input.members.length === 0) issues.push("Alberta Schedule 29 Agreement: no members were supplied. The claiming corporation must appear as the first row even in a two-corporation group.");
|
|
1375
|
+
const days = Math.max(0, Math.min(input.daysInLongestYear, 366));
|
|
1376
|
+
const maximumExpenditureLimit = Math.round(4e6 * (days / 365));
|
|
1377
|
+
const members = input.members.map((m, i) => computeAgreementMember(m, i, issues));
|
|
1378
|
+
const sum = (get) => members.reduce((s, m) => s + get(m), 0);
|
|
1379
|
+
const totalAllocatedLimit = sum((m) => m.allocatedExpenditureLimit);
|
|
1380
|
+
const totalCurrentYearExpenditures = sum((m) => m.currentYearExpenditures);
|
|
1381
|
+
const totalPriorYear1 = sum((m) => m.priorYear1);
|
|
1382
|
+
const totalPriorYear2 = sum((m) => m.priorYear2);
|
|
1383
|
+
const totalTaxableCapitalPriorYear = sum((m) => m.taxableCapitalPriorYear);
|
|
1384
|
+
const totalAllocatedAllowedAmount = sum((m) => m.allocatedAllowedAmount);
|
|
1385
|
+
const groupAllowedAmount = Math.round(totalCurrentYearExpenditures - (totalPriorYear1 + totalPriorYear2) / 2);
|
|
1386
|
+
if (totalAllocatedLimit > maximumExpenditureLimit) issues.push(`Alberta Schedule 29 Agreement: the group allocated ${totalAllocatedLimit} of a maximum expenditure limit of ${maximumExpenditureLimit} (line 270 exceeds line 208). The group must agree a valid split.`);
|
|
1387
|
+
if (totalAllocatedAllowedAmount > groupAllowedAmount) issues.push(`Alberta Schedule 29 Agreement: the group's allocated allowed amounts total ${totalAllocatedAllowedAmount}, exceeding the group Allowed Amount of ${groupAllowedAmount} (line 320 exceeds line 310). The group must agree a valid split.`);
|
|
1388
|
+
return {
|
|
1389
|
+
longestYearCan: input.longestYearCan,
|
|
1390
|
+
longestYearBegin: input.longestYearBegin,
|
|
1391
|
+
longestYearEnd: input.longestYearEnd,
|
|
1392
|
+
daysInLongestYear: days,
|
|
1393
|
+
maximumExpenditureLimit,
|
|
1394
|
+
members,
|
|
1395
|
+
totalAllocatedLimit,
|
|
1396
|
+
totalCurrentYearExpenditures,
|
|
1397
|
+
totalPriorYear1,
|
|
1398
|
+
totalPriorYear2,
|
|
1399
|
+
totalTaxableCapitalPriorYear,
|
|
1400
|
+
groupAllowedAmount,
|
|
1401
|
+
totalAllocatedAllowedAmount,
|
|
1402
|
+
claimantAllocatedAllowedAmount: members[0]?.allocatedAllowedAmount ?? 0,
|
|
1403
|
+
issues
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
955
1406
|
//#endregion
|
|
956
1407
|
//#region src/t2/at1/engine/alberta-return.ts
|
|
957
1408
|
/**
|
|
@@ -982,21 +1433,49 @@ function computeAlbertaReturn(input) {
|
|
|
982
1433
|
}, rates);
|
|
983
1434
|
const issues = [];
|
|
984
1435
|
let iegGroup;
|
|
1436
|
+
let iegAgreement;
|
|
1437
|
+
let iegEligibleExpenditures;
|
|
985
1438
|
let ieg;
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1439
|
+
let at4970;
|
|
1440
|
+
if (input.ieg) {
|
|
1441
|
+
if (input.ieg.at4970) at4970 = computeAt4970(input.ieg.at4970);
|
|
1442
|
+
let resolvedEligibleExpenditures = input.ieg.eligibleExpenditures ?? 0;
|
|
1443
|
+
if (input.ieg.eligible) {
|
|
1444
|
+
const e = input.ieg.eligible;
|
|
1445
|
+
const federalProxyAmount = e.federalProxyAmount ?? at4970?.totals.federalProxyAmount;
|
|
1446
|
+
const albertaProxyAmount = e.albertaProxyAmount ?? at4970?.totals.albertaProxyAmount;
|
|
1447
|
+
iegEligibleExpenditures = computeIegEligibleExpenditures({
|
|
1448
|
+
federalAmount: e.federalAmount,
|
|
1449
|
+
albertaPortion: e.albertaPortion ?? at4970?.totals.albertaPortion ?? 0,
|
|
1450
|
+
...federalProxyAmount !== void 0 ? { federalProxyAmount } : {},
|
|
1451
|
+
...albertaProxyAmount !== void 0 ? { albertaProxyAmount } : {},
|
|
1452
|
+
...e.iegReducingFederalExpenditure !== void 0 ? { iegReducingFederalExpenditure: e.iegReducingFederalExpenditure } : {},
|
|
1453
|
+
...e.repaymentOrContractPayment !== void 0 ? { repaymentOrContractPayment: e.repaymentOrContractPayment } : {}
|
|
1454
|
+
});
|
|
1455
|
+
resolvedEligibleExpenditures = iegEligibleExpenditures.totalEligibleExpenditures;
|
|
1456
|
+
}
|
|
1457
|
+
if (input.ieg.group.length === 0) issues.push("AT1 Schedule 29: no associated-group members were supplied, so no grant was claimed. Pass the claimant itself even when it has no associated corporations — an empty group would otherwise imply no taxable-capital grind and a nil base, producing the maximum possible grant on absent data.");
|
|
1458
|
+
else {
|
|
1459
|
+
iegGroup = computeIegGroupFigures(input.ieg.group);
|
|
1460
|
+
issues.push(...iegGroup.issues);
|
|
1461
|
+
const allocation = allocateIegExpenditureLimit(iegGroup.groupExpenditureLimit, [{
|
|
1462
|
+
name: "claimant",
|
|
1463
|
+
allocated: input.ieg.allocatedLimit ?? iegGroup.groupExpenditureLimit
|
|
1464
|
+
}]);
|
|
1465
|
+
issues.push(...allocation.issues);
|
|
1466
|
+
if (input.ieg.agreement) {
|
|
1467
|
+
iegAgreement = computeIegAgreement(input.ieg.agreement);
|
|
1468
|
+
issues.push(...iegAgreement.issues);
|
|
1469
|
+
}
|
|
1470
|
+
ieg = computeIeg({
|
|
1471
|
+
eligibleExpenditures: resolvedEligibleExpenditures,
|
|
1472
|
+
baseAmount: iegGroup.groupBaseAmount,
|
|
1473
|
+
expenditureLimit: allocation.allocations[0]?.allocated ?? 0,
|
|
1474
|
+
taxableCapital: iegAgreement ? iegAgreement.totalTaxableCapitalPriorYear : iegGroup.groupTaxableCapital,
|
|
1475
|
+
...input.ieg.recapture !== void 0 ? { recapture: input.ieg.recapture } : {},
|
|
1476
|
+
...iegAgreement ? { associatedAllowedAmount: iegAgreement.claimantAllocatedAllowedAmount } : {}
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1000
1479
|
}
|
|
1001
1480
|
const schedulePayloads = [];
|
|
1002
1481
|
const sched = input.schedules;
|
|
@@ -1010,13 +1489,16 @@ function computeAlbertaReturn(input) {
|
|
|
1010
1489
|
if (sched?.dispositions) schedulePayloads.push(schedule18Values(sched.dispositions));
|
|
1011
1490
|
if (sched?.donations) schedulePayloads.push(schedule20Values(sched.donations));
|
|
1012
1491
|
if (sched?.losses) schedulePayloads.push(schedule21Values(sched.losses));
|
|
1013
|
-
if (ieg) schedulePayloads.push(schedule29Values(ieg));
|
|
1492
|
+
if (ieg) schedulePayloads.push(schedule29Values(ieg, iegAgreement, iegEligibleExpenditures, input.ieg?.primaryFieldCode));
|
|
1493
|
+
if (at4970) schedulePayloads.push(schedule4970Values(at4970));
|
|
1014
1494
|
return {
|
|
1015
1495
|
allocationFactor,
|
|
1016
1496
|
albertaTax,
|
|
1017
1497
|
albertaTaxPayable: albertaTax.albertaTaxPayable,
|
|
1018
1498
|
schedulePayloads,
|
|
1019
1499
|
...iegGroup ? { iegGroup } : {},
|
|
1500
|
+
...iegAgreement ? { iegAgreement } : {},
|
|
1501
|
+
...iegEligibleExpenditures ? { iegEligibleExpenditures } : {},
|
|
1020
1502
|
...ieg ? { ieg } : {},
|
|
1021
1503
|
innovationEmploymentGrant: ieg?.ieg ?? 0,
|
|
1022
1504
|
issues
|
|
@@ -1166,6 +1648,7 @@ const AT1_MANDATORY_WITHOUT_DEFAULT = Object.freeze([
|
|
|
1166
1648
|
function assertAt1MandatoryComplete(d) {
|
|
1167
1649
|
const blank = (v) => v === void 0 || v === null || String(v).trim() === "";
|
|
1168
1650
|
const missing = AT1_MANDATORY_WITHOUT_DEFAULT.filter(([key]) => blank(d[key])).map(([, label]) => label);
|
|
1651
|
+
if (d.transmitter?.isAmended && blank(d.transmitter.amendmentDescription)) missing.push("EDI073 description of changes (required when the amended return indicator is set)");
|
|
1169
1652
|
if (missing.length > 0) throw new At1MandatoryFieldMissingError(missing);
|
|
1170
1653
|
}
|
|
1171
1654
|
/** Refuse to render when any critical mandatory field is absent. */
|
|
@@ -1525,6 +2008,16 @@ const AT1_EDI_LINE_ITEMS = [
|
|
|
1525
2008
|
id: "EDI041001",
|
|
1526
2009
|
get: (t) => t.contact.email,
|
|
1527
2010
|
fmt: "text"
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
id: "EDI071001",
|
|
2014
|
+
get: (t) => t.isAmended ? "1" : void 0,
|
|
2015
|
+
fmt: "text"
|
|
2016
|
+
},
|
|
2017
|
+
{
|
|
2018
|
+
id: "EDI073001",
|
|
2019
|
+
get: (t) => t.isAmended ? t.amendmentDescription : void 0,
|
|
2020
|
+
fmt: "text"
|
|
1528
2021
|
}
|
|
1529
2022
|
];
|
|
1530
2023
|
//#endregion
|
|
@@ -2016,8 +2509,9 @@ function albertaCurrentYearLoss(result) {
|
|
|
2016
2509
|
*
|
|
2017
2510
|
* The STRAIGHT-LINE classes (13 leasehold interests, 14 limited-life intangibles)
|
|
2018
2511
|
* are deliberately NOT here — they use their own arithmetic in Reg 1100(1)(b) and
|
|
2019
|
-
* 1100(1)(c), so `computeCcaClass`
|
|
2020
|
-
*
|
|
2512
|
+
* 1100(1)(c), so `computeCcaClass` never looks them up in this table. It handles
|
|
2513
|
+
* an EXISTING opening-UCC pool for them directly (see `schedule8.ts`); a NEW
|
|
2514
|
+
* addition needs the full layer/property mechanics in `schedules/cca-straight-line.ts`.
|
|
2021
2515
|
*
|
|
2022
2516
|
* Class 14.1 IS a declining-balance class (5%) and belongs here. Its pre-2027
|
|
2023
2517
|
* transitional additional allowance — which keeps pre-2017 expenditures moving at
|
|
@@ -2303,19 +2797,79 @@ function computeClass141AdditionalAllowance(input) {
|
|
|
2303
2797
|
* Terminal loss: if the class is EMPTIED (no assets left) with positive UCC, the
|
|
2304
2798
|
* remaining UCC is deducted as a terminal loss, no CCA, closing UCC nil.
|
|
2305
2799
|
*
|
|
2306
|
-
*
|
|
2307
|
-
*
|
|
2308
|
-
* the
|
|
2800
|
+
* Class 13/14 (straight-line, Reg 1100(1)(b)/(c)): this function accepts them
|
|
2801
|
+
* too, but ONLY as an existing opening-UCC pool being drawn down — no rate, no
|
|
2802
|
+
* half-year rule, no AIIP; the claim is capped at the UCC before CCA and
|
|
2803
|
+
* nothing else. A current-year ADDITION on one of these classes is refused
|
|
2804
|
+
* (see `UnsupportedCcaClassError`) rather than silently claimed without the
|
|
2805
|
+
* 5-year floor / remaining-life proration Schedule III and Reg 1100(1)(c)
|
|
2806
|
+
* require for a NEW layer or property — that full mechanic is
|
|
2807
|
+
* `computeClass13` / `computeClass14` in `cca-straight-line.ts`, which this
|
|
2808
|
+
* entry point does not have layer/remaining-life data to drive.
|
|
2809
|
+
*
|
|
2810
|
+
* NOT modelled (caller / other schedules): a NEW class 13/14 addition (see
|
|
2811
|
+
* above), class 14.1's transitional additional allowance, the shared $1.5M
|
|
2812
|
+
* immediate-expensing limit across an associated group (caller passes the
|
|
2813
|
+
* already-capped eligible amount), and the 2024+ AIIP phase-out factor.
|
|
2309
2814
|
*/
|
|
2310
2815
|
var UnsupportedCcaClassError = class extends Error {
|
|
2311
2816
|
ccaClass;
|
|
2312
|
-
constructor(ccaClass) {
|
|
2313
|
-
super(`CCA class ${ccaClass} is not a declining-balance class this engine computes yet (straight-line / special classes like 13, 14, 14.1 use different mechanics).`);
|
|
2817
|
+
constructor(ccaClass, reason) {
|
|
2818
|
+
super(reason ?? `CCA class ${ccaClass} is not a declining-balance class this engine computes yet (straight-line / special classes like 13, 14, 14.1 use different mechanics).`);
|
|
2314
2819
|
this.name = "UnsupportedCcaClassError";
|
|
2315
2820
|
this.ccaClass = ccaClass;
|
|
2316
2821
|
}
|
|
2317
2822
|
};
|
|
2823
|
+
/** Class 13/14 accept only an opening-UCC drawdown here — see the file docstring. */
|
|
2824
|
+
const STRAIGHT_LINE_OPENING_BALANCE_ONLY = /* @__PURE__ */ new Set(["13", "14"]);
|
|
2825
|
+
/**
|
|
2826
|
+
* Class 13/14, opening-balance drawdown only: no rate, no half-year rule, no
|
|
2827
|
+
* AIIP, no immediate expensing — none of those concepts apply to a
|
|
2828
|
+
* straight-line class. The claim is simply capped at the UCC before CCA.
|
|
2829
|
+
* Recapture and terminal loss reuse the same general rules as every other
|
|
2830
|
+
* class (s.13(1) / s.20(16) are not declining-balance-specific).
|
|
2831
|
+
*/
|
|
2832
|
+
function computeStraightLineOpeningBalanceClass(input) {
|
|
2833
|
+
if (Math.max(0, input.additions ?? 0) > 0) throw new UnsupportedCcaClassError(input.ccaClass, `CCA class ${input.ccaClass}: a current-year addition needs the detailed leasehold/limited-life schedule (computeClass13 / computeClass14 in cca-straight-line.ts), which this entry point has no lease-term / remaining-life data to drive. Drawing down an EXISTING opening balance (no addition this year) is supported directly.`);
|
|
2834
|
+
const dispositions = Math.max(0, input.dispositions ?? 0);
|
|
2835
|
+
const netAdjustments = input.netAdjustments ?? 0;
|
|
2836
|
+
const uccBeforeCca = input.openingUCC + netAdjustments - dispositions;
|
|
2837
|
+
const base = {
|
|
2838
|
+
ccaClass: input.ccaClass,
|
|
2839
|
+
rate: 0,
|
|
2840
|
+
uccBeforeCca,
|
|
2841
|
+
immediateExpensingClaim: 0,
|
|
2842
|
+
halfYearAdjustment: 0,
|
|
2843
|
+
aiipEnhancement: 0,
|
|
2844
|
+
ccaBase: 0,
|
|
2845
|
+
maxCca: 0,
|
|
2846
|
+
ccaClaimed: 0,
|
|
2847
|
+
closingUCC: 0,
|
|
2848
|
+
recapture: 0,
|
|
2849
|
+
terminalLoss: 0
|
|
2850
|
+
};
|
|
2851
|
+
if (uccBeforeCca < 0) return {
|
|
2852
|
+
...base,
|
|
2853
|
+
closingUCC: 0,
|
|
2854
|
+
recapture: -uccBeforeCca
|
|
2855
|
+
};
|
|
2856
|
+
if (input.classEmptied && uccBeforeCca > 0) return {
|
|
2857
|
+
...base,
|
|
2858
|
+
closingUCC: 0,
|
|
2859
|
+
terminalLoss: uccBeforeCca
|
|
2860
|
+
};
|
|
2861
|
+
const maxCca = uccBeforeCca;
|
|
2862
|
+
const ccaClaimed = input.claim != null ? Math.min(Math.max(0, input.claim), maxCca) : maxCca;
|
|
2863
|
+
return {
|
|
2864
|
+
...base,
|
|
2865
|
+
ccaBase: uccBeforeCca,
|
|
2866
|
+
maxCca,
|
|
2867
|
+
ccaClaimed,
|
|
2868
|
+
closingUCC: uccBeforeCca - ccaClaimed
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2318
2871
|
function computeCcaClass(input, rates = CCA_DECLINING_BALANCE_RATES_2024, prorationFactor = 1) {
|
|
2872
|
+
if (STRAIGHT_LINE_OPENING_BALANCE_ONLY.has(input.ccaClass)) return computeStraightLineOpeningBalanceClass(input);
|
|
2319
2873
|
const rate = rates[input.ccaClass];
|
|
2320
2874
|
if (rate === void 0) throw new UnsupportedCcaClassError(input.ccaClass);
|
|
2321
2875
|
const additions = Math.max(0, input.additions ?? 0);
|
|
@@ -6173,4 +6727,4 @@ function computeMpDeduction(input, rates = MP_RATES_2024) {
|
|
|
6173
6727
|
};
|
|
6174
6728
|
}
|
|
6175
6729
|
//#endregion
|
|
6176
|
-
export { computeProvincialAllocation as $, at1TaxPayableDeductions as $n, CEC_DEDUCTION_RATE as $t, runConformance as A, reconcileAlbertaNetIncome as An,
|
|
6730
|
+
export { computeProvincialAllocation as $, at1TaxPayableDeductions as $n, CEC_DEDUCTION_RATE as $t, runConformance as A, reconcileAlbertaNetIncome as An, schedule29Values as Ar, computeCcpcActiveBusinessTax as At, computeGrip as B, formatRsiText as Bn, earliestRateYear as Br, AT1_DONATION_GAIN_RATE as Bt, computeQuebecTax as C, albertaCcaScheduleAdjustments as Cn, schedule13Values as Cr, blendProvinceRateTable as Ct, T2_LINE_META as D, albertaReserveDifference as Dn, schedule1Values as Dr, dividendsDeductibleS112 as Dt, resolveQuebecTaxRates as E, albertaRecaptureDifference as En, schedule18Values as Er, computeTaxableIncome as Et, normalizeSchedule88 as F, RSI_NEGATIVE_PREFIX as Fn, AB_GENERAL_RATE_BANDS as Fr, CORP_TAX_RATE_BOOK as Ft, ITC_RECAPTURE_PERIOD_YEARS as G, AT1_CRITICAL_MANDATORY_FIELDS as Gn, SECTION_34_2_GROSS_UP as Gt, LARGE_CORPORATION_THRESHOLD as H, renderRsiHeader as Hn, hasExactRateYear as Hr, computeDonationMaximum as Ht, computeSchedule55 as I, RSI_WORD_GAP as In, computeDayWeightedGeneralTax as Ir, resolveCorpTaxRates as It, allocateEvenly as J, At1TaxPayableMismatchError as Jn, AT1_RESERVE_LINES as Jt, computeItcRecapture as K, At1CriticalFieldMissingError as Kn, computeAlbertaSchedule18 as Kt, LRIP_INVESTMENT_CORPORATION_MULTIPLE as L, RsiLineItemError as Ln, AB_TAX_2024 as Lr, T2_CERTIFICATION_FIXTURES as Lt, computeFederalT2 as M, computeLossCarryback as Mn, schedule4970Values as Mr, computeBusinessLimit as Mt, computeSchedule101 as N, RSI_COLUMN_GAP as Nn, computeAlbertaTax as Nr, computeSBD as Nt, foldT2Lines as O, albertaTerminalLossDifference as On, schedule20Values as Or, netCapitalLossApplied as Ot, SCHEDULE_88_MAX_URLS as P, RSI_DELIMITER as Pn, computeAlbertaSbd as Pr, CORP_TAX_2024 as Pt, computeSchedule6 as Q, assertCriticalFields as Qn, computeAlbertaSchedule16 as Qt, LRIP_INVESTMENT_INCOME_FACTOR as R, formatRsiAmount as Rn, AB_TAX_RATE_BOOK as Rr, computeLossSchedule as Rt, computeQuebecAllocationFactor as S, albertaCcaDifference as Sn, schedule12Values as Sr, resolveProvinceRates as St, QC_TAX_RATE_BOOK as T, albertaDispositionAdjustments as Tn, schedule17Values as Tr, charitableDonationsDeduction as Tt, computeTaxableCapital as U, renderRsiLineItem as Un, latestRateYear as Ur, computeSchedule20 as Ut, computeSchedule43 as V, renderAt1Rsi as Vn, extendRateBook as Vr, AT1_DONATION_INCOME_RATE as Vt, computeSchedule31 as W, renderAt1NetFile as Wn, resolveRates as Wr, AT1_DISPOSITION_CATEGORIES as Wt, computeSchedule21 as X, assertAt1MandatoryComplete as Xn, computeAlbertaSchedule17 as Xt, computeBusinessLimitAllocation as Y, albertaBalanceUnpaid as Yn, AT1_RESERVE_TOTAL_LINES as Yt, computeSchedule13 as Z, assertAt1TaxPayableReconciles as Zn, assistanceFrom as Zt, renderT2DraftReturn as _, CCA_RATE_BOOK as _n, AT1_SCHEDULES_WITHOUT_BUILDERS as _r, EIFEL_EFFECTIVE_FROM as _t, PART_VI_1_DEDUCTION_BANDS as a, computeCcaClass as an, allocateIegExpenditureLimit as ar, computeSchedule2 as at, co17Engine as b, albertaAbilDifference as bn, schedule10Values as br, PROVINCE_RATE_BOOK as bt, EIFEL_FIRST_YEAR_START as c, CLASS_14_1_MINIMUM_DEDUCTION as cn, IEG_2024 as cr, assertSchedule1Fileable as ct, EIFEL_TRANSITIONAL_RATIO as d, MIN_LEASEHOLD_PERIODS as dn, computeIegReductionFactor as dr, deferredIncomeTaxProvisionAddBack as dt, CEC_INCLUSION_RATE as en, at1YesNo as er, computeSchedule5 as et, computeEifelLimitation as f, computeClass13 as fn, computeIegEligibleExpenditures as fr, findSchedule1LineDefects as ft, computeT2Settlement as g, CCA_DECLINING_BALANCE_RATES_2024 as gn, computeAt4970 as gr, terminalLossDeduction as gt, computeAdjustedTaxableIncome as h, leaseholdPeriods as hn, computeAllocationFactor as hr, recaptureAddBack as ht, computeMpDeduction as i, UnsupportedCcaClassError as in, allocateIegEvenly as ir, computePart4Rdtoh as it, runConformanceSuite as j, LossCarrybackError as jn, schedule2Values as jr, computePartITax as jt, formatConformanceReport as k, computeSchedule12 as kn, schedule21Values as kr, nonCapitalLossApplied as kt, EIFEL_STANDARD_RATIO as l, CLASS_14_1_TRANSITIONAL_RATE as ln, computeIeg as lr, ccaDeduction as lt, FOREIGN_TAX_CREDIT_GROSS_UP as m, computeClass141AdditionalAllowance as mn, SINGLE_JURISDICTION_ALBERTA_FACTOR as mr, mealsAndEntertainmentAddBack as mt, MP_GROSS_REVENUE_THRESHOLD as n, computeAlbertaSchedule14 as nn, at1Engine as nr, PART_IV_RATE as nt, computePartVI1Deduction as o, computeCcaSchedule as on, computeIegAgreement as or, Schedule1NotFileableError as ot, ratioOfPermissibleExpenses as p, computeClass14 as pn, iegT661SourceLine as pr, incomeTaxProvisionAddBack as pt, computeZetm as q, At1MandatoryFieldMissingError as qn, AT1_RESERVE_KINDS as qt, MP_RATES_2024 as r, computeAlbertaSchedule13 as rn, computeAlbertaReturn as rr, REFUNDABLE_PART_I_RATE as rt, partVI1DeductionMultiple as s, computeSchedule8 as sn, computeIegGroupFigures as sr, amortizationAddBack as st, MP_EXCLUDED_ACTIVITIES as t, cecScheduleAppliesToTaxYear as tn, xmlEscape as tr, computeSchedule4Losses as tt, EIFEL_STANDARD_RATIO_FROM as u, MAX_LEASEHOLD_PERIODS as un, computeIegBaseAmount as ur, computeSchedule1 as ut, t2Engine as v, isDecliningBalanceClass as vn, AT1_SCHEDULES_WITH_BUILDERS as vr, assessEifel as vt, QC_TAX_2024 as w, albertaCurrentYearLoss as wn, schedule16Values as wr, dayWeightedRate as wt, computeQuebecReturn as x, albertaCapitalGainDifference as xn, schedule12LossDeductions as xr, isSchedule5Province as xt, renderCo17DraftReturn as y, resolveCcaRates as yn, at1LineItemId as yr, PROVINCE_RATES_2024 as yt, computeSchedule54 as z, formatRsiDate as zn, resolveAlbertaTaxRates as zr, computeLossContinuity as zt };
|