@trajectonomics/tax 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +48 -2
- package/dist/clamp-BKG7kjVf.cjs +509 -0
- package/dist/clamp-BKG7kjVf.cjs.map +1 -0
- package/dist/clamp-Bse4u0H-.mjs +426 -0
- package/dist/clamp-Bse4u0H-.mjs.map +1 -0
- package/dist/index.cjs +27 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +102 -0
- package/dist/index.d.mts +102 -0
- package/dist/index.mjs +15 -0
- package/dist/index.mjs.map +1 -0
- package/dist/th/index.cjs +1480 -0
- package/dist/th/index.cjs.map +1 -0
- package/dist/th/index.d.cts +636 -0
- package/dist/th/index.d.mts +636 -0
- package/dist/th/index.mjs +1473 -0
- package/dist/th/index.mjs.map +1 -0
- package/dist/types-798sLpt6.d.cts +170 -0
- package/dist/types-798sLpt6.d.mts +170 -0
- package/dist/us/index.cjs +2921 -0
- package/dist/us/index.cjs.map +1 -0
- package/dist/us/index.d.cts +649 -0
- package/dist/us/index.d.mts +649 -0
- package/dist/us/index.mjs +2897 -0
- package/dist/us/index.mjs.map +1 -0
- package/package.json +78 -5
|
@@ -0,0 +1,1473 @@
|
|
|
1
|
+
import { c as roundHalfUp, d as multiplySafeInteger, f as validateMonetaryAmount, i as thb, l as fromMinorUnits, m as TaxValidationError, n as evaluateBrackets, o as applyGrossUp, p as validateSafeIntegerCount, s as applyPercentage, t as clampCap, u as toMinorUnits } from "../clamp-Bse4u0H-.mjs";
|
|
2
|
+
// src/th/parameters-brackets.ts
|
|
3
|
+
/**
|
|
4
|
+
* Section 48(1) progressive net-income brackets for Tax Year 2026 (THB major units, rates as ratios).
|
|
5
|
+
*
|
|
6
|
+
* @statute Thailand Revenue Code Section 48(1); Royal Decree No. 650.
|
|
7
|
+
*/
|
|
8
|
+
const TH_2026_BRACKETS = [
|
|
9
|
+
{
|
|
10
|
+
upTo: 15e4,
|
|
11
|
+
rate: 0
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
upTo: 3e5,
|
|
15
|
+
rate: .05
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
upTo: 5e5,
|
|
19
|
+
rate: .1
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
upTo: 75e4,
|
|
23
|
+
rate: .15
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
upTo: 1e6,
|
|
27
|
+
rate: .2
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
upTo: 2e6,
|
|
31
|
+
rate: .25
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
upTo: 5e6,
|
|
35
|
+
rate: .3
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
upTo: null,
|
|
39
|
+
rate: .35
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
// src/th/parameters-special.ts
|
|
43
|
+
/**
|
|
44
|
+
* Section 40(4) final withholding rates for Tax Year 2026.
|
|
45
|
+
*
|
|
46
|
+
* @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 57 ter.
|
|
47
|
+
*/
|
|
48
|
+
const TH_2026_FINAL_WITHHOLDING = {
|
|
49
|
+
interestRate: .15,
|
|
50
|
+
dividendRate: .1,
|
|
51
|
+
digitalAssetRate: .15
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Section 48(5) separate sheet parameters for Tax Year 2026: exemption, expenses, and schedule.
|
|
55
|
+
* The schedule is the Section 48(1) ladder without its 0% tier (same upper bounds and rates).
|
|
56
|
+
*
|
|
57
|
+
* @statute Thailand Revenue Code Section 48(5), Section 42(1).
|
|
58
|
+
*/
|
|
59
|
+
const TH_2026_SEPARATE_SHEET = {
|
|
60
|
+
minYearsOfService: 5,
|
|
61
|
+
exemptionWageDays: 400,
|
|
62
|
+
exemptionCeiling: 6e5,
|
|
63
|
+
expensePerYearOfService: 7e3,
|
|
64
|
+
remainderExpenseRate: .5,
|
|
65
|
+
brackets: TH_2026_BRACKETS.filter((bracket) => bracket.rate > 0)
|
|
66
|
+
};
|
|
67
|
+
// src/th/parameters.ts
|
|
68
|
+
/**
|
|
69
|
+
* Thailand Statutory Tax Parameters for Tax Year 2026 (Revenue Code ภ.ง.ด.90/91).
|
|
70
|
+
*
|
|
71
|
+
* @statute Thailand Revenue Code Sections 40, 42 bis, 47, 47 bis, 48(1)–(5); Royal Decrees No. 11, 650.
|
|
72
|
+
*/
|
|
73
|
+
const TH_2026_PARAMETERS = {
|
|
74
|
+
taxYear: 2026,
|
|
75
|
+
currency: "THB",
|
|
76
|
+
expenses: {
|
|
77
|
+
section40_1_2: {
|
|
78
|
+
rate: .5,
|
|
79
|
+
ceiling: 1e5
|
|
80
|
+
},
|
|
81
|
+
section40_3: {
|
|
82
|
+
rate: .5,
|
|
83
|
+
ceiling: 1e5
|
|
84
|
+
},
|
|
85
|
+
section40_4: { rate: 0 },
|
|
86
|
+
section40_5: {
|
|
87
|
+
building: .3,
|
|
88
|
+
agriculturalLand: .2,
|
|
89
|
+
nonAgriculturalLand: .15,
|
|
90
|
+
vehicles: .3,
|
|
91
|
+
other: .1
|
|
92
|
+
},
|
|
93
|
+
section40_6: {
|
|
94
|
+
medical: .6,
|
|
95
|
+
other: .3
|
|
96
|
+
},
|
|
97
|
+
section40_7: { rate: .6 },
|
|
98
|
+
section40_8: {
|
|
99
|
+
generalRate: .6,
|
|
100
|
+
entertainerFirstTierRate: .6,
|
|
101
|
+
entertainerFirstTierThreshold: 3e5,
|
|
102
|
+
entertainerSecondTierRate: .4,
|
|
103
|
+
entertainerCeiling: 6e5
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
allowances: {
|
|
107
|
+
personal: 6e4,
|
|
108
|
+
spouse: 6e4,
|
|
109
|
+
child: {
|
|
110
|
+
basePerChild: 3e4,
|
|
111
|
+
bonusSecondPlusBorn2018OrLater: 3e4,
|
|
112
|
+
bonusCutoffYear: 2018
|
|
113
|
+
},
|
|
114
|
+
parent: {
|
|
115
|
+
perPerson: 3e4,
|
|
116
|
+
maxCount: 4
|
|
117
|
+
},
|
|
118
|
+
disabledPerPerson: 6e4,
|
|
119
|
+
socialSecurityFundCeiling: 10500,
|
|
120
|
+
lifeInsurance: {
|
|
121
|
+
combinedCeiling: 1e5,
|
|
122
|
+
healthCeiling: 25e3
|
|
123
|
+
},
|
|
124
|
+
parentHealthInsuranceCeiling: 15e3,
|
|
125
|
+
spouseLifeInsuranceCeiling: 1e4,
|
|
126
|
+
mortgageInterestCeiling: 1e5,
|
|
127
|
+
retirementCluster: {
|
|
128
|
+
combinedCeiling: 5e5,
|
|
129
|
+
rmf: {
|
|
130
|
+
maxRate: .3,
|
|
131
|
+
ceiling: 5e5
|
|
132
|
+
},
|
|
133
|
+
ssf: {
|
|
134
|
+
maxRate: .3,
|
|
135
|
+
ceiling: 2e5
|
|
136
|
+
},
|
|
137
|
+
pvd: {
|
|
138
|
+
maxRate: .15,
|
|
139
|
+
ceiling: 5e5
|
|
140
|
+
},
|
|
141
|
+
gpf: {
|
|
142
|
+
maxRate: .3,
|
|
143
|
+
ceiling: 5e5
|
|
144
|
+
},
|
|
145
|
+
nsf: { ceiling: 3e4 },
|
|
146
|
+
pensionLifeInsurance: {
|
|
147
|
+
maxRate: .15,
|
|
148
|
+
ceiling: 2e5
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
thaiEsg: {
|
|
152
|
+
maxRate: .3,
|
|
153
|
+
ceiling: 3e5
|
|
154
|
+
},
|
|
155
|
+
donations: {
|
|
156
|
+
donation2xMultiplier: 2,
|
|
157
|
+
donation2xMaxRate: .1,
|
|
158
|
+
donation1xMaxRate: .1
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
brackets: TH_2026_BRACKETS,
|
|
162
|
+
minimumGrossTax: {
|
|
163
|
+
rate: .005,
|
|
164
|
+
incomeThreshold: 12e4,
|
|
165
|
+
taxThreshold: 5e3
|
|
166
|
+
},
|
|
167
|
+
finalWithholding: TH_2026_FINAL_WITHHOLDING,
|
|
168
|
+
separateSheet: TH_2026_SEPARATE_SHEET,
|
|
169
|
+
realEstate: {
|
|
170
|
+
expenseTable: [
|
|
171
|
+
{
|
|
172
|
+
minYears: 1,
|
|
173
|
+
rate: .92
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
minYears: 2,
|
|
177
|
+
rate: .84
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
minYears: 3,
|
|
181
|
+
rate: .77
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
minYears: 4,
|
|
185
|
+
rate: .71
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
minYears: 5,
|
|
189
|
+
rate: .65
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
minYears: 6,
|
|
193
|
+
rate: .6
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
minYears: 7,
|
|
197
|
+
rate: .55
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
minYears: 8,
|
|
201
|
+
rate: .5
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
inheritanceExpenseRate: .5,
|
|
205
|
+
maxHoldingYears: 10,
|
|
206
|
+
brackets: TH_2026_SEPARATE_SHEET.brackets,
|
|
207
|
+
taxCapRate: .2
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
// src/th/types/special-input.ts
|
|
211
|
+
/**
|
|
212
|
+
* Election values for a Section 40(4) source: keep source withholding as final tax, or include
|
|
213
|
+
* the income in the regular progressive computation. Plain `"final"`/`"include"` strings are
|
|
214
|
+
* accepted wherever {@link ThFinalWithholdingElection} is expected.
|
|
215
|
+
*
|
|
216
|
+
* @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 57 ter.
|
|
217
|
+
*/
|
|
218
|
+
const ThFinalWithholdingElection = {
|
|
219
|
+
Final: "final",
|
|
220
|
+
Include: "include"
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Kind of Section 48(5) lump sum. Only labour-law `"severance"` carries the 400-day / 600,000 THB
|
|
224
|
+
* exemption; other `"retirement"` payments (e.g. a pension fund lump sum) get none.
|
|
225
|
+
* Plain `"severance"`/`"retirement"` strings are accepted wherever
|
|
226
|
+
* {@link ThSeparateSheetPaymentType} is expected.
|
|
227
|
+
*
|
|
228
|
+
* @statute Thailand Revenue Code Section 48(5); Section 42(1) (severance exemption).
|
|
229
|
+
*/
|
|
230
|
+
const ThSeparateSheetPaymentType = {
|
|
231
|
+
Severance: "severance",
|
|
232
|
+
Retirement: "retirement"
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* How a Section 48(4) property was acquired. Inherited or gifted property takes a flat 50%
|
|
236
|
+
* expense; commercially acquired (or other) property takes the holding-year table. The two are
|
|
237
|
+
* never combined. Plain `"commercial"`/`"inheritance_or_gift"` strings are accepted wherever
|
|
238
|
+
* {@link ThRealEstateAcquisitionType} is expected.
|
|
239
|
+
*
|
|
240
|
+
* @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.
|
|
241
|
+
*/
|
|
242
|
+
const ThRealEstateAcquisitionType = {
|
|
243
|
+
Commercial: "commercial",
|
|
244
|
+
InheritanceOrGift: "inheritance_or_gift"
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Which expense deduction a Section 48(4) computation used, reported in the breakdown.
|
|
248
|
+
*
|
|
249
|
+
* @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.
|
|
250
|
+
*/
|
|
251
|
+
const ThRealEstateExpenseBasis = {
|
|
252
|
+
HoldingPeriodTable: "holding_period_table",
|
|
253
|
+
FlatInheritance: "flat_inheritance"
|
|
254
|
+
};
|
|
255
|
+
// src/th/units.ts
|
|
256
|
+
/**
|
|
257
|
+
* Converts a THB major-unit amount to integer satang.
|
|
258
|
+
*
|
|
259
|
+
* Purpose: Single THB-bound entry point so stages never repeat the currency threading.
|
|
260
|
+
* Math: Decimal-exact conversion via `toMinorUnits` (half away from zero beyond 2 decimals).
|
|
261
|
+
* Units: THB major units in, integer satang out.
|
|
262
|
+
* Throws: TaxValidationError if the amount is negative (unless allowed), non-finite, or unsafe.
|
|
263
|
+
*
|
|
264
|
+
* @param amount Amount in THB.
|
|
265
|
+
* @param field Diagnostic field name reported on failure.
|
|
266
|
+
* @statute Thailand Revenue Code Section 40 (amounts expressed in baht).
|
|
267
|
+
*/
|
|
268
|
+
function toSatang(amount, field) {
|
|
269
|
+
return toMinorUnits(amount, TH_2026_PARAMETERS.currency, field);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Converts integer satang back to THB major units.
|
|
273
|
+
*
|
|
274
|
+
* Purpose: Counterpart of {@link toSatang}.
|
|
275
|
+
* Math: `satang / 100`, fixed to two decimals.
|
|
276
|
+
* Units: Integer satang in, THB major units out.
|
|
277
|
+
* Throws: TaxValidationError if the input is not a safe integer (or is negative without
|
|
278
|
+
* `allowNegative`).
|
|
279
|
+
*
|
|
280
|
+
* @param satang Integer satang.
|
|
281
|
+
* @param field Diagnostic field name reported on failure.
|
|
282
|
+
* @param options Set `allowNegative` for refund balances.
|
|
283
|
+
* @statute Thailand Revenue Code Section 40 (amounts expressed in baht).
|
|
284
|
+
*/
|
|
285
|
+
function fromSatang(satang, field, options) {
|
|
286
|
+
return fromMinorUnits(satang, TH_2026_PARAMETERS.currency, field, options);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Sums non-negative THB amounts exactly in integer satang.
|
|
290
|
+
*
|
|
291
|
+
* Purpose: Avoids binary float drift when aggregating gross amounts.
|
|
292
|
+
* Math: Converts each amount to satang, adds, converts back to major units.
|
|
293
|
+
* Units: THB major units in and out.
|
|
294
|
+
* Throws: TaxValidationError if any amount is negative or non-finite, or the sum is not a safe integer.
|
|
295
|
+
*
|
|
296
|
+
* @param values Amounts in THB.
|
|
297
|
+
* @param field Diagnostic field name reported on failure.
|
|
298
|
+
* @statute Thailand Revenue Code Section 40 (gross income aggregation).
|
|
299
|
+
*/
|
|
300
|
+
function sumThAmounts(values, field) {
|
|
301
|
+
return fromSatang(values.reduce((sum, value) => sum + toSatang(value, field), 0), field);
|
|
302
|
+
}
|
|
303
|
+
// src/th/stages/allowances-donations.ts
|
|
304
|
+
/**
|
|
305
|
+
* Calculates qualified 2x and general 1x donation allowances.
|
|
306
|
+
*
|
|
307
|
+
* Math: 2x donations capped at 10% of net before donations; 1x donations capped at 10% of remaining net.
|
|
308
|
+
* Units: THB major units.
|
|
309
|
+
*
|
|
310
|
+
* @statute Thailand Revenue Code Section 47(7); Royal Decrees No. 428, 558.
|
|
311
|
+
*/
|
|
312
|
+
function calculateDonationAllowances(donation2x, donation1x, netBeforeDonations) {
|
|
313
|
+
const p = TH_2026_PARAMETERS.allowances.donations;
|
|
314
|
+
const net = Math.max(0, netBeforeDonations);
|
|
315
|
+
const raw2x = donation2x ?? 0;
|
|
316
|
+
validateMonetaryAmount(raw2x, "allowance.donation_2x");
|
|
317
|
+
const eligible2x = raw2x * p.donation2xMultiplier;
|
|
318
|
+
const cap2x = Math.floor(net * p.donation2xMaxRate);
|
|
319
|
+
const allowed2x = Math.min(eligible2x, cap2x);
|
|
320
|
+
const clamped2x = {
|
|
321
|
+
requested: raw2x,
|
|
322
|
+
allowed: allowed2x,
|
|
323
|
+
cap: cap2x,
|
|
324
|
+
wasClamped: eligible2x > cap2x
|
|
325
|
+
};
|
|
326
|
+
const remainingNet = Math.max(0, net - allowed2x);
|
|
327
|
+
const raw1x = donation1x ?? 0;
|
|
328
|
+
validateMonetaryAmount(raw1x, "allowance.donation_1x");
|
|
329
|
+
const cap1x = Math.floor(remainingNet * p.donation1xMaxRate);
|
|
330
|
+
const allowed1x = Math.min(raw1x, cap1x);
|
|
331
|
+
const breakdown = {
|
|
332
|
+
donation2x: clamped2x,
|
|
333
|
+
donation1x: {
|
|
334
|
+
requested: raw1x,
|
|
335
|
+
allowed: allowed1x,
|
|
336
|
+
cap: cap1x,
|
|
337
|
+
wasClamped: raw1x > cap1x
|
|
338
|
+
},
|
|
339
|
+
total: allowed2x + allowed1x
|
|
340
|
+
};
|
|
341
|
+
const netTaxableIncome = Math.max(0, remainingNet - allowed1x);
|
|
342
|
+
return {
|
|
343
|
+
breakdown,
|
|
344
|
+
allowedDonationsTotal: breakdown.total,
|
|
345
|
+
netTaxableIncome
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
// src/th/stages/allowances-family.ts
|
|
349
|
+
const unclamped = (n) => ({
|
|
350
|
+
requested: n,
|
|
351
|
+
allowed: n,
|
|
352
|
+
cap: n,
|
|
353
|
+
wasClamped: false
|
|
354
|
+
});
|
|
355
|
+
function countBonusChildren(children, cutoff) {
|
|
356
|
+
const sorted = [...children].sort((a, b) => (a.birthYear ?? 0) - (b.birthYear ?? 0));
|
|
357
|
+
let bonus = 0;
|
|
358
|
+
for (let i = 1; i < sorted.length; i++) if ((sorted[i]?.birthYear ?? 0) >= cutoff) bonus++;
|
|
359
|
+
return bonus;
|
|
360
|
+
}
|
|
361
|
+
function computeChildAllowance(children) {
|
|
362
|
+
if (!children) return unclamped(0);
|
|
363
|
+
const p = TH_2026_PARAMETERS.allowances.child;
|
|
364
|
+
if (Array.isArray(children)) {
|
|
365
|
+
const bonus = countBonusChildren(children, p.bonusCutoffYear);
|
|
366
|
+
return unclamped(children.length * p.basePerChild + bonus * p.bonusSecondPlusBorn2018OrLater);
|
|
367
|
+
}
|
|
368
|
+
const counts = children;
|
|
369
|
+
validateMonetaryAmount(counts.totalCount ?? 0, "allowance.children");
|
|
370
|
+
const count = Math.max(0, Math.floor(counts.totalCount ?? 0));
|
|
371
|
+
const bonusReq = Math.max(0, Math.floor(counts.secondPlusBorn2018OrLaterCount ?? 0));
|
|
372
|
+
const bonus = Math.min(bonusReq, Math.max(0, count - 1));
|
|
373
|
+
return unclamped(count * p.basePerChild + bonus * p.bonusSecondPlusBorn2018OrLater);
|
|
374
|
+
}
|
|
375
|
+
function computeParentCount(parents, status) {
|
|
376
|
+
if (parents === void 0) return 0;
|
|
377
|
+
if (typeof parents === "number") {
|
|
378
|
+
validateMonetaryAmount(parents, "allowance.parents");
|
|
379
|
+
return Math.floor(parents);
|
|
380
|
+
}
|
|
381
|
+
let count = (parents.taxpayerFather ? 1 : 0) + (parents.taxpayerMother ? 1 : 0);
|
|
382
|
+
if (status === "married_no_income_spouse") count += (parents.spouseFather ? 1 : 0) + (parents.spouseMother ? 1 : 0);
|
|
383
|
+
return count;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Calculates family allowances (personal, spouse, children, parents, disabled).
|
|
387
|
+
*
|
|
388
|
+
* @statute Thailand Revenue Code Section 47(1)(a)–(f).
|
|
389
|
+
*/
|
|
390
|
+
function calculateFamilyAllowances(input = {}, filingStatus = "single") {
|
|
391
|
+
const p = TH_2026_PARAMETERS.allowances;
|
|
392
|
+
const personalReq = input.personal ?? p.personal;
|
|
393
|
+
const personal = clampCap(personalReq, p.personal, "allowance.personal");
|
|
394
|
+
const isSpouseEligible = filingStatus === "married_no_income_spouse" || filingStatus === "married_joint";
|
|
395
|
+
const spouseRequested = input.spouse ?? (isSpouseEligible ? p.spouse : 0);
|
|
396
|
+
const spouse = isSpouseEligible ? clampCap(spouseRequested, p.spouse, "allowance.spouse") : {
|
|
397
|
+
requested: spouseRequested,
|
|
398
|
+
allowed: 0,
|
|
399
|
+
cap: 0,
|
|
400
|
+
wasClamped: spouseRequested > 0
|
|
401
|
+
};
|
|
402
|
+
const children = computeChildAllowance(input.children);
|
|
403
|
+
const parentsRequested = computeParentCount(input.parents, filingStatus) * p.parent.perPerson;
|
|
404
|
+
const parents = clampCap(parentsRequested, p.parent.perPerson * p.parent.maxCount, "allowance.parents");
|
|
405
|
+
const disabledRequested = (input.disabledCareCount ?? 0) * p.disabledPerPerson;
|
|
406
|
+
validateMonetaryAmount(disabledRequested, "allowance.disabled");
|
|
407
|
+
return {
|
|
408
|
+
personal,
|
|
409
|
+
spouse,
|
|
410
|
+
children,
|
|
411
|
+
parents,
|
|
412
|
+
disabled: unclamped(disabledRequested)
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
// src/th/stages/allowances-insurance.ts
|
|
416
|
+
/**
|
|
417
|
+
* Calculates insurance, social security, and housing mortgage interest allowances.
|
|
418
|
+
*
|
|
419
|
+
* @statute Thailand Revenue Code Section 47(1)(d), (g), (h), (j).
|
|
420
|
+
*/
|
|
421
|
+
function calculateInsuranceAllowances(input = {}, filingStatus = "single") {
|
|
422
|
+
const p = TH_2026_PARAMETERS.allowances;
|
|
423
|
+
const socialSecurity = clampCap(input.socialSecurity ?? 0, p.socialSecurityFundCeiling, "allowance.social_security");
|
|
424
|
+
const healthClamped = clampCap(input.healthInsurance ?? 0, p.lifeInsurance.healthCeiling, "allowance.health_insurance");
|
|
425
|
+
const rawLife = input.lifeInsurance ?? 0;
|
|
426
|
+
if (input.lifeInsurance !== void 0) validateMonetaryAmount(rawLife, "allowance.life_health_combined");
|
|
427
|
+
const lifeCombinedRequested = rawLife + (input.healthInsurance ?? 0);
|
|
428
|
+
const lifeEffectiveRequested = rawLife + healthClamped.allowed;
|
|
429
|
+
const lifeCombined = clampCap(lifeEffectiveRequested, p.lifeInsurance.combinedCeiling, "allowance.life_health_combined");
|
|
430
|
+
const lifeInsurance = {
|
|
431
|
+
requested: lifeCombinedRequested,
|
|
432
|
+
allowed: lifeCombined.allowed,
|
|
433
|
+
cap: p.lifeInsurance.combinedCeiling,
|
|
434
|
+
wasClamped: lifeCombinedRequested > lifeCombined.allowed
|
|
435
|
+
};
|
|
436
|
+
const parentHealthInsurance = clampCap(input.parentHealthInsurance ?? 0, p.parentHealthInsuranceCeiling, "allowance.parent_health_insurance");
|
|
437
|
+
const isSpouseEligible = filingStatus === "married_no_income_spouse";
|
|
438
|
+
const spouseLifeReq = input.spouseLifeInsurance ?? 0;
|
|
439
|
+
return {
|
|
440
|
+
socialSecurity,
|
|
441
|
+
lifeInsurance,
|
|
442
|
+
parentHealthInsurance,
|
|
443
|
+
spouseLifeInsurance: isSpouseEligible ? clampCap(spouseLifeReq, p.spouseLifeInsuranceCeiling, "allowance.spouse_life_insurance") : {
|
|
444
|
+
requested: spouseLifeReq,
|
|
445
|
+
allowed: 0,
|
|
446
|
+
cap: 0,
|
|
447
|
+
wasClamped: spouseLifeReq > 0
|
|
448
|
+
},
|
|
449
|
+
mortgageInterest: clampCap(input.mortgageInterest ?? 0, p.mortgageInterestCeiling, "allowance.mortgage_interest")
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
// src/th/stages/allowances-retirement.ts
|
|
453
|
+
/**
|
|
454
|
+
* Calculates Retirement Cluster and ThaiESG allowances.
|
|
455
|
+
*
|
|
456
|
+
* @statute Thailand Revenue Code Section 47; Ministerial Regulations No. 126, 390.
|
|
457
|
+
*/
|
|
458
|
+
function calculateRetirementAllowances(input = {}, assessableIncome, wageBase) {
|
|
459
|
+
const p = TH_2026_PARAMETERS.allowances.retirementCluster;
|
|
460
|
+
const currency = TH_2026_PARAMETERS.currency;
|
|
461
|
+
const wage = Math.max(wageBase, 0);
|
|
462
|
+
const rmf = clampCap(input.rmf ?? 0, {
|
|
463
|
+
base: assessableIncome,
|
|
464
|
+
rate: p.rmf.maxRate,
|
|
465
|
+
ceiling: p.rmf.ceiling,
|
|
466
|
+
currency
|
|
467
|
+
}, "allowance.retirement.rmf");
|
|
468
|
+
const ssf = clampCap(input.ssf ?? 0, {
|
|
469
|
+
base: assessableIncome,
|
|
470
|
+
rate: p.ssf.maxRate,
|
|
471
|
+
ceiling: p.ssf.ceiling,
|
|
472
|
+
currency
|
|
473
|
+
}, "allowance.retirement.ssf");
|
|
474
|
+
const pvd = clampCap(input.pvd ?? 0, {
|
|
475
|
+
base: wage,
|
|
476
|
+
rate: p.pvd.maxRate,
|
|
477
|
+
ceiling: p.pvd.ceiling,
|
|
478
|
+
currency
|
|
479
|
+
}, "allowance.retirement.pvd");
|
|
480
|
+
const gpf = clampCap(input.gpf ?? 0, {
|
|
481
|
+
base: wage,
|
|
482
|
+
rate: p.gpf.maxRate,
|
|
483
|
+
ceiling: p.gpf.ceiling,
|
|
484
|
+
currency
|
|
485
|
+
}, "allowance.retirement.gpf");
|
|
486
|
+
const nsf = clampCap(input.nsf ?? 0, p.nsf.ceiling, "allowance.retirement.nsf");
|
|
487
|
+
const pensionLifeInsurance = clampCap(input.pensionLifeInsurance ?? 0, {
|
|
488
|
+
base: assessableIncome,
|
|
489
|
+
rate: p.pensionLifeInsurance.maxRate,
|
|
490
|
+
ceiling: p.pensionLifeInsurance.ceiling,
|
|
491
|
+
currency
|
|
492
|
+
}, "allowance.retirement.pension_life");
|
|
493
|
+
const individualSum = rmf.allowed + ssf.allowed + pvd.allowed + gpf.allowed + nsf.allowed + pensionLifeInsurance.allowed;
|
|
494
|
+
const combined = clampCap(individualSum, p.combinedCeiling, "allowance.retirement_cluster");
|
|
495
|
+
const retirementCluster = {
|
|
496
|
+
rmf,
|
|
497
|
+
ssf,
|
|
498
|
+
pvd,
|
|
499
|
+
gpf,
|
|
500
|
+
nsf,
|
|
501
|
+
pensionLifeInsurance,
|
|
502
|
+
combined
|
|
503
|
+
};
|
|
504
|
+
const thaiEsgParam = TH_2026_PARAMETERS.allowances.thaiEsg;
|
|
505
|
+
const thaiEsg = clampCap(input.thaiEsg ?? 0, {
|
|
506
|
+
base: assessableIncome,
|
|
507
|
+
rate: thaiEsgParam.maxRate,
|
|
508
|
+
ceiling: thaiEsgParam.ceiling,
|
|
509
|
+
currency
|
|
510
|
+
}, "allowance.thai_esg");
|
|
511
|
+
return {
|
|
512
|
+
retirementCluster,
|
|
513
|
+
thaiEsg,
|
|
514
|
+
allowedClusterTotal: combined.allowed,
|
|
515
|
+
allowedThaiEsgTotal: thaiEsg.allowed
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
// src/th/stages/allowances.ts
|
|
519
|
+
/**
|
|
520
|
+
* Aggregates all personal, family, insurance, retirement, and donation allowances.
|
|
521
|
+
*
|
|
522
|
+
* @statute Thailand Revenue Code Section 47.
|
|
523
|
+
*/
|
|
524
|
+
function calculateAllowances(input = {}, filingStatus = "single", assessableTotal, expensesTotal, wageBase) {
|
|
525
|
+
const fam = calculateFamilyAllowances(input, filingStatus);
|
|
526
|
+
const ins = calculateInsuranceAllowances(input, filingStatus);
|
|
527
|
+
const ret = calculateRetirementAllowances(input, assessableTotal, wageBase);
|
|
528
|
+
const nonDonationTotal = fam.personal.allowed + fam.spouse.allowed + fam.children.allowed + fam.parents.allowed + fam.disabled.allowed + ins.socialSecurity.allowed + ins.lifeInsurance.allowed + ins.parentHealthInsurance.allowed + ins.spouseLifeInsurance.allowed + ins.mortgageInterest.allowed + ret.allowedClusterTotal + ret.allowedThaiEsgTotal;
|
|
529
|
+
const netBeforeDonations = Math.max(0, assessableTotal - expensesTotal - nonDonationTotal);
|
|
530
|
+
const don = calculateDonationAllowances(input.donation2x, input.donation1x, netBeforeDonations);
|
|
531
|
+
const total = nonDonationTotal + don.allowedDonationsTotal;
|
|
532
|
+
return {
|
|
533
|
+
breakdown: {
|
|
534
|
+
personal: fam.personal,
|
|
535
|
+
spouse: fam.spouse,
|
|
536
|
+
children: fam.children,
|
|
537
|
+
parents: fam.parents,
|
|
538
|
+
disabled: fam.disabled,
|
|
539
|
+
socialSecurity: ins.socialSecurity,
|
|
540
|
+
lifeInsurance: ins.lifeInsurance,
|
|
541
|
+
parentHealthInsurance: ins.parentHealthInsurance,
|
|
542
|
+
spouseLifeInsurance: ins.spouseLifeInsurance,
|
|
543
|
+
mortgageInterest: ins.mortgageInterest,
|
|
544
|
+
retirementCluster: ret.retirementCluster,
|
|
545
|
+
thaiEsg: ret.thaiEsg,
|
|
546
|
+
donations: don.breakdown,
|
|
547
|
+
total
|
|
548
|
+
},
|
|
549
|
+
netTaxableIncome: don.netTaxableIncome
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
// src/th/stages/assessable.ts
|
|
553
|
+
function extractElectableAmount(val, field) {
|
|
554
|
+
if (val === void 0) return 0;
|
|
555
|
+
const amount = typeof val === "number" ? val : val.amount;
|
|
556
|
+
validateMonetaryAmount(amount, field);
|
|
557
|
+
if (typeof val === "object" && val.actualExpense !== void 0) validateMonetaryAmount(val.actualExpense, `${field}.actualExpense`);
|
|
558
|
+
return amount;
|
|
559
|
+
}
|
|
560
|
+
function sumSection40_5(input) {
|
|
561
|
+
if (!input) return 0;
|
|
562
|
+
return extractElectableAmount(input.building, "income.section40_5.building") + extractElectableAmount(input.agriculturalLand, "income.section40_5.agriculturalLand") + extractElectableAmount(input.nonAgriculturalLand, "income.section40_5.nonAgriculturalLand") + extractElectableAmount(input.vehicles, "income.section40_5.vehicles") + extractElectableAmount(input.other, "income.section40_5.other");
|
|
563
|
+
}
|
|
564
|
+
function sumSection40_6(input) {
|
|
565
|
+
if (!input) return 0;
|
|
566
|
+
return extractElectableAmount(input.medical, "income.section40_6.medical") + extractElectableAmount(input.law, "income.section40_6.law") + extractElectableAmount(input.engineering, "income.section40_6.engineering") + extractElectableAmount(input.architecture, "income.section40_6.architecture") + extractElectableAmount(input.accounting, "income.section40_6.accounting") + extractElectableAmount(input.fineArts, "income.section40_6.fineArts") + extractElectableAmount(input.other, "income.section40_6.other");
|
|
567
|
+
}
|
|
568
|
+
function sumSection40_8(input) {
|
|
569
|
+
if (input === void 0) return 0;
|
|
570
|
+
if (typeof input === "number" || "amount" in input) return extractElectableAmount(input, "income.section40_8");
|
|
571
|
+
return extractElectableAmount(input.general, "income.section40_8.general") + extractElectableAmount(input.entertainer, "income.section40_8.entertainer");
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Normalizes and validates assessable income across Section 40(1)–40(8).
|
|
575
|
+
*
|
|
576
|
+
* @statute Thailand Revenue Code Section 40(1)–(8).
|
|
577
|
+
*/
|
|
578
|
+
function calculateAssessableIncome(input = {}) {
|
|
579
|
+
const s1 = extractElectableAmount(input.section40_1, "income.section40_1");
|
|
580
|
+
const s2 = extractElectableAmount(input.section40_2, "income.section40_2");
|
|
581
|
+
const s3 = extractElectableAmount(input.section40_3, "income.section40_3");
|
|
582
|
+
const s4 = extractElectableAmount(typeof input.section40_4 === "number" ? input.section40_4 : input.section40_4?.amount, "income.section40_4");
|
|
583
|
+
const s5 = sumSection40_5(input.section40_5);
|
|
584
|
+
const s6 = sumSection40_6(input.section40_6);
|
|
585
|
+
const s7 = extractElectableAmount(input.section40_7, "income.section40_7");
|
|
586
|
+
const s8 = sumSection40_8(input.section40_8);
|
|
587
|
+
return {
|
|
588
|
+
section40_1: s1,
|
|
589
|
+
section40_2: s2,
|
|
590
|
+
section40_3: s3,
|
|
591
|
+
section40_4: s4,
|
|
592
|
+
section40_5: s5,
|
|
593
|
+
section40_6: s6,
|
|
594
|
+
section40_7: s7,
|
|
595
|
+
section40_8: s8,
|
|
596
|
+
total: s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8,
|
|
597
|
+
grossNonEmploymentIncome: s2 + s3 + s4 + s5 + s6 + s7 + s8
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
// src/th/stages/bracket-eval.ts
|
|
601
|
+
/**
|
|
602
|
+
* Evaluates an arbitrary Thai statutory bracket schedule (major-unit bounds) in minor units.
|
|
603
|
+
*
|
|
604
|
+
* Math: Converts bounds and incomes to satang, slices through `evaluateBrackets`, converts back.
|
|
605
|
+
* Units: Inputs and outputs in THB major units; arithmetic in integer satang.
|
|
606
|
+
* Throws: TaxValidationError if incomes are negative or non-finite.
|
|
607
|
+
*
|
|
608
|
+
* @statute Thailand Revenue Code Sections 48(1), 48(4), 48(5).
|
|
609
|
+
*/
|
|
610
|
+
function evaluateThBracketSet(taxableIncome, grossIncome, brackets) {
|
|
611
|
+
const currency = TH_2026_PARAMETERS.currency;
|
|
612
|
+
const minorBrackets = brackets.map((b) => ({
|
|
613
|
+
upTo: b.upTo === null ? null : toMinorUnits(b.upTo, currency),
|
|
614
|
+
rate: b.rate
|
|
615
|
+
}));
|
|
616
|
+
const taxableMinor = toMinorUnits(taxableIncome, currency, "taxableIncome");
|
|
617
|
+
const grossMinor = toMinorUnits(grossIncome, currency, "grossIncome");
|
|
618
|
+
const res = evaluateBrackets(taxableMinor, minorBrackets, grossMinor);
|
|
619
|
+
const slices = res.slices.map((s) => ({
|
|
620
|
+
bracketIndex: s.bracketIndex,
|
|
621
|
+
lowerBound: fromMinorUnits(s.lowerBound, currency),
|
|
622
|
+
upperBound: s.upperBound !== null ? fromMinorUnits(s.upperBound, currency) : null,
|
|
623
|
+
rate: s.rate,
|
|
624
|
+
taxableAmount: fromMinorUnits(s.taxableAmount, currency),
|
|
625
|
+
tax: fromMinorUnits(s.tax, currency)
|
|
626
|
+
}));
|
|
627
|
+
const totalTax = fromMinorUnits(res.totalTax, currency);
|
|
628
|
+
return {
|
|
629
|
+
totalTax,
|
|
630
|
+
brackets: {
|
|
631
|
+
totalTax,
|
|
632
|
+
slices,
|
|
633
|
+
effectiveRate: res.effectiveRate,
|
|
634
|
+
marginalRate: res.marginalRate
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Evaluates the regular progressive tax brackets for Thailand PIT.
|
|
640
|
+
*
|
|
641
|
+
* Math: Slices taxable income through `TH_2026_PARAMETERS.brackets`.
|
|
642
|
+
* Units: THB major units.
|
|
643
|
+
* Throws: TaxValidationError if incomes are negative or non-finite.
|
|
644
|
+
*
|
|
645
|
+
* @statute Thailand Revenue Code Section 48(1).
|
|
646
|
+
*/
|
|
647
|
+
function evaluateThBrackets(taxableIncome, grossIncome) {
|
|
648
|
+
return evaluateThBracketSet(taxableIncome, grossIncome, TH_2026_PARAMETERS.brackets);
|
|
649
|
+
}
|
|
650
|
+
// src/th/stages/combine.ts
|
|
651
|
+
/**
|
|
652
|
+
* Combines regular, separate sheet, real estate, and final withholding taxes into one liability.
|
|
653
|
+
*
|
|
654
|
+
* Purpose: Produces the single audited total and the amount still payable (or refundable).
|
|
655
|
+
* Math: totalCombinedTax = regular + separate sheet + real estate + final withholding (before credits);
|
|
656
|
+
* netTaxPayable = totalCombinedTax - final withholding (already withheld at source)
|
|
657
|
+
* - Section 47 bis dividend credit - withholding tax paid. May be negative (refund).
|
|
658
|
+
* Units: Inputs and outputs in THB major units; arithmetic in integer satang.
|
|
659
|
+
* Throws: TaxValidationError if any component is negative or non-finite.
|
|
660
|
+
*
|
|
661
|
+
* @param input Component taxes, credits, and payments in THB.
|
|
662
|
+
* @statute Thailand Revenue Code Sections 40(4), 47 bis, 48(1)–(5).
|
|
663
|
+
*/
|
|
664
|
+
function combineThLiability(input) {
|
|
665
|
+
const regular = toSatang(input.regularTax, "regularTax");
|
|
666
|
+
const separateSheet = toSatang(input.separateSheetTax, "separateSheetTax");
|
|
667
|
+
const realEstate = toSatang(input.realEstateTax, "realEstateTax");
|
|
668
|
+
const finalWithholding = toSatang(input.finalWithholdingTax, "finalWithholdingTax");
|
|
669
|
+
const dividendCredit = toSatang(input.dividendTaxCredit, "dividendTaxCredit");
|
|
670
|
+
const withheld = toSatang(input.withholdingTax, "withholdingTax");
|
|
671
|
+
const total = regular + separateSheet + realEstate + finalWithholding;
|
|
672
|
+
const credits = finalWithholding + dividendCredit + withheld;
|
|
673
|
+
return {
|
|
674
|
+
totalCombinedTax: fromSatang(total, "totalTax"),
|
|
675
|
+
netTaxPayable: fromSatang(total - credits, "netTaxPayable", { allowNegative: true })
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
// src/th/stages/expenses-40-1-2.ts
|
|
679
|
+
/**
|
|
680
|
+
* Calculates shared Section 40(1) and Section 40(2) expense deduction.
|
|
681
|
+
*
|
|
682
|
+
* Math: 50% of combined 40(1) + 40(2) income, clamped to 100,000 THB ceiling.
|
|
683
|
+
* Units: THB major units.
|
|
684
|
+
*
|
|
685
|
+
* @statute Thailand Revenue Code Section 42 bis.
|
|
686
|
+
*/
|
|
687
|
+
function calculateExpenses40_1_2(s1, s2) {
|
|
688
|
+
const combined = s1 + s2;
|
|
689
|
+
const requested = Math.floor(combined * TH_2026_PARAMETERS.expenses.section40_1_2.rate);
|
|
690
|
+
return clampCap(requested, TH_2026_PARAMETERS.expenses.section40_1_2.ceiling, "expense.40_1_2");
|
|
691
|
+
}
|
|
692
|
+
// src/th/stages/expenses-40-3.ts
|
|
693
|
+
/**
|
|
694
|
+
* Calculates Section 40(3) royalties/goodwill/copyright expense deduction.
|
|
695
|
+
*
|
|
696
|
+
* Math: Standard 50% capped at 100,000 THB or actual expenses capped at income.
|
|
697
|
+
* Units: THB major units.
|
|
698
|
+
*
|
|
699
|
+
* @statute Thailand Revenue Code Section 42 ter; Royal Decree No. 11 Section 4.
|
|
700
|
+
*/
|
|
701
|
+
function calculateExpenses40_3(input, incomeAmount) {
|
|
702
|
+
if (incomeAmount <= 0) return {
|
|
703
|
+
requested: 0,
|
|
704
|
+
allowed: 0,
|
|
705
|
+
cap: 0,
|
|
706
|
+
wasClamped: false
|
|
707
|
+
};
|
|
708
|
+
if (typeof input === "object" && input.expenseElection === "actual") {
|
|
709
|
+
const requested = input.actualExpense ?? 0;
|
|
710
|
+
return clampCap(requested, incomeAmount, "expense.40_3");
|
|
711
|
+
}
|
|
712
|
+
const requested = Math.floor(incomeAmount * TH_2026_PARAMETERS.expenses.section40_3.rate);
|
|
713
|
+
return clampCap(requested, TH_2026_PARAMETERS.expenses.section40_3.ceiling, "expense.40_3");
|
|
714
|
+
}
|
|
715
|
+
// src/th/stages/expenses-40-4.ts
|
|
716
|
+
/**
|
|
717
|
+
* Calculates Section 40(4) expense deduction (0% statutory).
|
|
718
|
+
*
|
|
719
|
+
* Math: Thailand Revenue Code permits 0% expense deductions for interest, dividends, crypto.
|
|
720
|
+
* Units: THB major units.
|
|
721
|
+
*
|
|
722
|
+
* @statute Thailand Revenue Code Section 40(4).
|
|
723
|
+
*/
|
|
724
|
+
function calculateExpenses40_4(_incomeAmount) {
|
|
725
|
+
return {
|
|
726
|
+
requested: 0,
|
|
727
|
+
allowed: 0,
|
|
728
|
+
cap: 0,
|
|
729
|
+
wasClamped: false
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
// src/th/stages/electable-expense.ts
|
|
733
|
+
/**
|
|
734
|
+
* Computes expense deduction for an electable income item (standard % vs actual).
|
|
735
|
+
*
|
|
736
|
+
* Math: If actual, clamp to item income. If standard, apply percentage of item income.
|
|
737
|
+
* Units: THB major units.
|
|
738
|
+
*
|
|
739
|
+
* @param item Income item number or configuration object.
|
|
740
|
+
* @param standardRate Statutory percentage rate for standard deduction.
|
|
741
|
+
* @param field Diagnostic field name.
|
|
742
|
+
*/
|
|
743
|
+
function calculateElectableItemExpense(item, standardRate, field) {
|
|
744
|
+
if (!item) return {
|
|
745
|
+
requested: 0,
|
|
746
|
+
allowed: 0,
|
|
747
|
+
cap: 0,
|
|
748
|
+
wasClamped: false
|
|
749
|
+
};
|
|
750
|
+
const amount = typeof item === "number" ? item : item.amount;
|
|
751
|
+
if (amount <= 0) return {
|
|
752
|
+
requested: 0,
|
|
753
|
+
allowed: 0,
|
|
754
|
+
cap: 0,
|
|
755
|
+
wasClamped: false
|
|
756
|
+
};
|
|
757
|
+
if (typeof item === "object" && item.expenseElection === "actual") {
|
|
758
|
+
const req = item.actualExpense ?? 0;
|
|
759
|
+
return clampCap(req, amount, field);
|
|
760
|
+
}
|
|
761
|
+
const req = Math.floor(amount * standardRate);
|
|
762
|
+
return {
|
|
763
|
+
requested: req,
|
|
764
|
+
allowed: req,
|
|
765
|
+
cap: amount,
|
|
766
|
+
wasClamped: false
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
// src/th/stages/expenses-40-5.ts
|
|
770
|
+
/**
|
|
771
|
+
* Calculates Section 40(5) rental expense deductions across property sub-categories.
|
|
772
|
+
*
|
|
773
|
+
* Math: Standard percentage per property class or actual expenses capped at income.
|
|
774
|
+
* Units: THB major units.
|
|
775
|
+
*
|
|
776
|
+
* @statute Thailand Royal Decree No. 11 Section 5.
|
|
777
|
+
*/
|
|
778
|
+
function calculateExpenses40_5(input) {
|
|
779
|
+
if (!input) return {
|
|
780
|
+
requested: 0,
|
|
781
|
+
allowed: 0,
|
|
782
|
+
cap: 0,
|
|
783
|
+
wasClamped: false
|
|
784
|
+
};
|
|
785
|
+
const rates = TH_2026_PARAMETERS.expenses.section40_5;
|
|
786
|
+
const b = calculateElectableItemExpense(input.building, rates.building, "expense.40_5.building");
|
|
787
|
+
const a = calculateElectableItemExpense(input.agriculturalLand, rates.agriculturalLand, "expense.40_5.agri");
|
|
788
|
+
const na = calculateElectableItemExpense(input.nonAgriculturalLand, rates.nonAgriculturalLand, "expense.40_5.nonAgri");
|
|
789
|
+
const v = calculateElectableItemExpense(input.vehicles, rates.vehicles, "expense.40_5.vehicles");
|
|
790
|
+
const o = calculateElectableItemExpense(input.other, rates.other, "expense.40_5.other");
|
|
791
|
+
const requested = b.requested + a.requested + na.requested + v.requested + o.requested;
|
|
792
|
+
const allowed = b.allowed + a.allowed + na.allowed + v.allowed + o.allowed;
|
|
793
|
+
return {
|
|
794
|
+
requested,
|
|
795
|
+
allowed,
|
|
796
|
+
cap: b.cap + a.cap + na.cap + v.cap + o.cap,
|
|
797
|
+
wasClamped: requested > allowed
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
// src/th/stages/expenses-40-6.ts
|
|
801
|
+
/**
|
|
802
|
+
* Calculates Section 40(6) liberal professions expense deduction.
|
|
803
|
+
*
|
|
804
|
+
* Math: Medical 60%, law/engineering/architecture/accounting/fine arts/other 30%, or actual.
|
|
805
|
+
* Units: THB major units.
|
|
806
|
+
*
|
|
807
|
+
* @statute Thailand Royal Decree No. 11 Section 6.
|
|
808
|
+
*/
|
|
809
|
+
function calculateExpenses40_6(input) {
|
|
810
|
+
if (!input) return {
|
|
811
|
+
requested: 0,
|
|
812
|
+
allowed: 0,
|
|
813
|
+
cap: 0,
|
|
814
|
+
wasClamped: false
|
|
815
|
+
};
|
|
816
|
+
const rates = TH_2026_PARAMETERS.expenses.section40_6;
|
|
817
|
+
const med = calculateElectableItemExpense(input.medical, rates.medical, "expense.40_6.medical");
|
|
818
|
+
const law = calculateElectableItemExpense(input.law, rates.other, "expense.40_6.law");
|
|
819
|
+
const eng = calculateElectableItemExpense(input.engineering, rates.other, "expense.40_6.eng");
|
|
820
|
+
const arch = calculateElectableItemExpense(input.architecture, rates.other, "expense.40_6.arch");
|
|
821
|
+
const acc = calculateElectableItemExpense(input.accounting, rates.other, "expense.40_6.acc");
|
|
822
|
+
const art = calculateElectableItemExpense(input.fineArts, rates.other, "expense.40_6.art");
|
|
823
|
+
const oth = calculateElectableItemExpense(input.other, rates.other, "expense.40_6.other");
|
|
824
|
+
const requested = med.requested + law.requested + eng.requested + arch.requested + acc.requested + art.requested + oth.requested;
|
|
825
|
+
const allowed = med.allowed + law.allowed + eng.allowed + arch.allowed + acc.allowed + art.allowed + oth.allowed;
|
|
826
|
+
return {
|
|
827
|
+
requested,
|
|
828
|
+
allowed,
|
|
829
|
+
cap: med.cap + law.cap + eng.cap + arch.cap + acc.cap + art.cap + oth.cap,
|
|
830
|
+
wasClamped: requested > allowed
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
// src/th/stages/expenses-40-7.ts
|
|
834
|
+
/**
|
|
835
|
+
* Calculates Section 40(7) contracting expense deduction.
|
|
836
|
+
*
|
|
837
|
+
* Math: Standard 60% or actual expenses capped at income.
|
|
838
|
+
* Units: THB major units.
|
|
839
|
+
*
|
|
840
|
+
* @statute Thailand Royal Decree No. 11 Section 7.
|
|
841
|
+
*/
|
|
842
|
+
function calculateExpenses40_7(input) {
|
|
843
|
+
return calculateElectableItemExpense(input, TH_2026_PARAMETERS.expenses.section40_7.rate, "expense.40_7");
|
|
844
|
+
}
|
|
845
|
+
// src/th/stages/expenses-40-8.ts
|
|
846
|
+
function calculateEntertainerExpense(item) {
|
|
847
|
+
if (!item) return {
|
|
848
|
+
requested: 0,
|
|
849
|
+
allowed: 0,
|
|
850
|
+
cap: 0,
|
|
851
|
+
wasClamped: false
|
|
852
|
+
};
|
|
853
|
+
const amount = typeof item === "number" ? item : item.amount;
|
|
854
|
+
if (amount <= 0) return {
|
|
855
|
+
requested: 0,
|
|
856
|
+
allowed: 0,
|
|
857
|
+
cap: 0,
|
|
858
|
+
wasClamped: false
|
|
859
|
+
};
|
|
860
|
+
if (typeof item === "object" && item.expenseElection === "actual") return clampCap(item.actualExpense ?? 0, amount, "expense.40_8.entertainer");
|
|
861
|
+
const p = TH_2026_PARAMETERS.expenses.section40_8;
|
|
862
|
+
const tier1 = Math.min(amount, p.entertainerFirstTierThreshold);
|
|
863
|
+
const tier2 = Math.max(0, amount - p.entertainerFirstTierThreshold);
|
|
864
|
+
const computed = Math.floor(tier1 * p.entertainerFirstTierRate) + Math.floor(tier2 * p.entertainerSecondTierRate);
|
|
865
|
+
return clampCap(computed, p.entertainerCeiling, "expense.40_8.entertainer");
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Calculates Section 40(8) business, commerce, and entertainer expense deduction.
|
|
869
|
+
*
|
|
870
|
+
* Math: General 60% or actual; entertainer tiered 60%/40% up to 600,000 THB or actual.
|
|
871
|
+
* Units: THB major units.
|
|
872
|
+
*
|
|
873
|
+
* @statute Thailand Royal Decree No. 11 Section 8.
|
|
874
|
+
*/
|
|
875
|
+
function calculateExpenses40_8(input) {
|
|
876
|
+
if (!input) return {
|
|
877
|
+
requested: 0,
|
|
878
|
+
allowed: 0,
|
|
879
|
+
cap: 0,
|
|
880
|
+
wasClamped: false
|
|
881
|
+
};
|
|
882
|
+
if (typeof input === "number" || "amount" in input) return calculateElectableItemExpense(input, TH_2026_PARAMETERS.expenses.section40_8.generalRate, "expense.40_8.general");
|
|
883
|
+
const gen = calculateElectableItemExpense(input.general, TH_2026_PARAMETERS.expenses.section40_8.generalRate, "expense.40_8.general");
|
|
884
|
+
const ent = calculateEntertainerExpense(input.entertainer);
|
|
885
|
+
const requested = gen.requested + ent.requested;
|
|
886
|
+
const allowed = gen.allowed + ent.allowed;
|
|
887
|
+
return {
|
|
888
|
+
requested,
|
|
889
|
+
allowed,
|
|
890
|
+
cap: gen.cap + ent.cap,
|
|
891
|
+
wasClamped: requested > allowed
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
// src/th/stages/expenses.ts
|
|
895
|
+
/**
|
|
896
|
+
* Aggregates all Section 40(1)–40(8) statutory expense deductions.
|
|
897
|
+
*
|
|
898
|
+
* @statute Thailand Revenue Code Sections 42 bis, 42 ter, 43–46; Royal Decree No. 11.
|
|
899
|
+
*/
|
|
900
|
+
function calculateExpenseDeductions(income = {}, assessable) {
|
|
901
|
+
const e1_2 = calculateExpenses40_1_2(assessable.section40_1, assessable.section40_2);
|
|
902
|
+
const e3 = calculateExpenses40_3(income.section40_3, assessable.section40_3);
|
|
903
|
+
const e4 = calculateExpenses40_4(assessable.section40_4);
|
|
904
|
+
const e5 = calculateExpenses40_5(income.section40_5);
|
|
905
|
+
const e6 = calculateExpenses40_6(income.section40_6);
|
|
906
|
+
const e7 = calculateExpenses40_7(income.section40_7);
|
|
907
|
+
const e8 = calculateExpenses40_8(income.section40_8);
|
|
908
|
+
return {
|
|
909
|
+
section40_1_2: e1_2,
|
|
910
|
+
section40_3: e3,
|
|
911
|
+
section40_4: e4,
|
|
912
|
+
section40_5: e5,
|
|
913
|
+
section40_6: e6,
|
|
914
|
+
section40_7: e7,
|
|
915
|
+
section40_8: e8,
|
|
916
|
+
total: e1_2.allowed + e3.allowed + e4.allowed + e5.allowed + e6.allowed + e7.allowed + e8.allowed
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
// src/th/stages/minimum-tax.ts
|
|
920
|
+
/**
|
|
921
|
+
* Calculates Section 48(2) Minimum Gross Tax on Section 40(2)–40(8) income.
|
|
922
|
+
*
|
|
923
|
+
* Math: 0.5% of gross Section 40(2)–(8) if gross >= 120,000 THB; payable only if the 0.5% tax exceeds 5,000 THB
|
|
924
|
+
* and exceeds the progressive tax.
|
|
925
|
+
* Units: THB major units.
|
|
926
|
+
*
|
|
927
|
+
* @statute Thailand Revenue Code Section 48(2).
|
|
928
|
+
*/
|
|
929
|
+
function calculateMinimumGrossTax(grossNonEmploymentIncome, progressiveTax) {
|
|
930
|
+
const p = TH_2026_PARAMETERS.minimumGrossTax;
|
|
931
|
+
if (grossNonEmploymentIncome < p.incomeThreshold) return {
|
|
932
|
+
grossEligibleIncome: grossNonEmploymentIncome,
|
|
933
|
+
rate: p.rate,
|
|
934
|
+
calculatedTax: 0,
|
|
935
|
+
threshold: p.taxThreshold,
|
|
936
|
+
isApplicable: false,
|
|
937
|
+
wasApplied: false
|
|
938
|
+
};
|
|
939
|
+
const currency = TH_2026_PARAMETERS.currency;
|
|
940
|
+
const minorIncome = toMinorUnits(grossNonEmploymentIncome, currency, "minimumGrossTax");
|
|
941
|
+
const minorTax = applyPercentage(minorIncome, p.rate);
|
|
942
|
+
const calculatedTax = fromMinorUnits(minorTax, currency);
|
|
943
|
+
const isApplicable = calculatedTax > p.taxThreshold;
|
|
944
|
+
const wasApplied = isApplicable && calculatedTax > progressiveTax;
|
|
945
|
+
return {
|
|
946
|
+
grossEligibleIncome: grossNonEmploymentIncome,
|
|
947
|
+
rate: p.rate,
|
|
948
|
+
calculatedTax,
|
|
949
|
+
threshold: p.taxThreshold,
|
|
950
|
+
isApplicable,
|
|
951
|
+
wasApplied
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
// src/th/validate.ts
|
|
955
|
+
/**
|
|
956
|
+
* Validates a Thai corporate income tax rate used for the Section 47 bis gross-up.
|
|
957
|
+
*
|
|
958
|
+
* Purpose: Keeps `rate / (1 - rate)` finite before any stage uses the rate.
|
|
959
|
+
* Math: Requires `0 <= rate < 1`.
|
|
960
|
+
* Units: Rate ratio (0.2 = 20%).
|
|
961
|
+
* Throws: TaxValidationError if rate is NaN, non-finite, negative, or at least 1.
|
|
962
|
+
*
|
|
963
|
+
* @param rate Corporate income tax rate ratio.
|
|
964
|
+
* @statute Thailand Revenue Code Section 47 bis.
|
|
965
|
+
*/
|
|
966
|
+
function validateCorpTaxRate(rate) {
|
|
967
|
+
if (!Number.isFinite(rate) || rate < 0 || rate >= 1) throw new TaxValidationError("income.section40_4.dividends.corpTaxRate", rate, "Corporate tax rate must be finite and satisfy 0 <= rate < 1");
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Resolves an optional typed choice (election, payment type, acquisition type) at runtime.
|
|
971
|
+
*
|
|
972
|
+
* Purpose: TypeScript unions are erased at runtime, so untyped callers could otherwise pass an
|
|
973
|
+
* unknown string and silently get the default behaviour.
|
|
974
|
+
* Math: Returns `fallback` when `value` is `undefined`; otherwise requires membership in `choices`.
|
|
975
|
+
* Units: Dimensionless string enum.
|
|
976
|
+
* Throws: TaxValidationError (reported against `field`) if `value` is not one of `choices`.
|
|
977
|
+
*
|
|
978
|
+
* @param value Caller-supplied value, possibly outside the declared union.
|
|
979
|
+
* @param choices Typed const object whose values are the permitted choices.
|
|
980
|
+
* @param fallback Choice used when `value` is `undefined`.
|
|
981
|
+
* @param field Diagnostic field path reported on failure.
|
|
982
|
+
* @statute Jurisdiction-neutral input bound (AGENTS rule 8); no statutory citation.
|
|
983
|
+
*/
|
|
984
|
+
function resolveChoice(value, choices, fallback, field) {
|
|
985
|
+
if (value === void 0) return fallback;
|
|
986
|
+
const match = Object.values(choices).find((choice) => choice === value);
|
|
987
|
+
if (match === void 0) throw new TaxValidationError(field, value, "Value is not a supported choice");
|
|
988
|
+
return match;
|
|
989
|
+
}
|
|
990
|
+
// src/th/stages/dividend-credit.ts
|
|
991
|
+
/**
|
|
992
|
+
* Calculates the Section 47 bis dividend tax credit for dividends included in regular income.
|
|
993
|
+
*
|
|
994
|
+
* Purpose: Recovers the corporate tax already paid on dividends taxed again in the regular schedule.
|
|
995
|
+
* Math: credit = dividend * corpTaxRate / (1 - corpTaxRate), exact in satang and rounded half-up.
|
|
996
|
+
* The grossed-up amount (dividend + credit) is assessable; the credit offsets tax payable.
|
|
997
|
+
* Units: Inputs and outputs in THB major units; arithmetic in integer satang.
|
|
998
|
+
* Throws: TaxValidationError if dividend is negative/non-finite or the rate is invalid.
|
|
999
|
+
*
|
|
1000
|
+
* @param includedDividends Dividends elected `"include"`, in THB.
|
|
1001
|
+
* @param corpTaxRate Corporate tax rate in [0, 1); `undefined` means no credit.
|
|
1002
|
+
* @statute Thailand Revenue Code Section 47 bis.
|
|
1003
|
+
*/
|
|
1004
|
+
function calculateDividendTaxCredit(includedDividends, corpTaxRate) {
|
|
1005
|
+
validateMonetaryAmount(includedDividends, "income.section40_4.dividends");
|
|
1006
|
+
if (corpTaxRate === void 0) return {
|
|
1007
|
+
isApplicable: false,
|
|
1008
|
+
dividendAmount: includedDividends,
|
|
1009
|
+
corpTaxRate: 0,
|
|
1010
|
+
credit: 0,
|
|
1011
|
+
grossedUpAmount: includedDividends
|
|
1012
|
+
};
|
|
1013
|
+
validateCorpTaxRate(corpTaxRate);
|
|
1014
|
+
const dividendSatang = toSatang(includedDividends, "income.section40_4.dividends");
|
|
1015
|
+
const creditSatang = applyGrossUp(dividendSatang, corpTaxRate, "income.section40_4.dividends");
|
|
1016
|
+
return {
|
|
1017
|
+
isApplicable: true,
|
|
1018
|
+
dividendAmount: includedDividends,
|
|
1019
|
+
corpTaxRate,
|
|
1020
|
+
credit: fromSatang(creditSatang, "dividendTaxCredit"),
|
|
1021
|
+
grossedUpAmount: fromSatang(dividendSatang + creditSatang, "dividendTaxCredit")
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
// src/th/stages/final-withholding-source.ts
|
|
1025
|
+
function electionOf(input, field) {
|
|
1026
|
+
return resolveChoice(typeof input === "object" ? input.election : void 0, ThFinalWithholdingElection, ThFinalWithholdingElection.Include, field);
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Resolves one Section 40(4) source: validates its amount and election, withholds if final.
|
|
1030
|
+
*
|
|
1031
|
+
* Purpose: Applies the per-source final withholding election.
|
|
1032
|
+
* Math: tax = amount * rate (satang, half-up) when the election is `"final"`, otherwise 0.
|
|
1033
|
+
* Units: Input in THB major units; `satang` and `taxSatang` in integer satang.
|
|
1034
|
+
* Throws: TaxValidationError for a negative/non-finite amount or an unknown election value.
|
|
1035
|
+
*
|
|
1036
|
+
* @param input Bare amount, source object, or undefined.
|
|
1037
|
+
* @param field Diagnostic path of the amount.
|
|
1038
|
+
* @param electionField Diagnostic path of the election.
|
|
1039
|
+
* @param rate Final withholding rate (ratio).
|
|
1040
|
+
* @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 57 ter.
|
|
1041
|
+
*/
|
|
1042
|
+
function resolveSource(input, field, electionField, rate) {
|
|
1043
|
+
const amount = extractElectableAmount(input, field);
|
|
1044
|
+
const election = electionOf(input, electionField);
|
|
1045
|
+
const satang = toSatang(amount, field);
|
|
1046
|
+
const taxSatang = election === ThFinalWithholdingElection.Final ? applyPercentage(satang, rate) : 0;
|
|
1047
|
+
return {
|
|
1048
|
+
breakdown: {
|
|
1049
|
+
election,
|
|
1050
|
+
amount,
|
|
1051
|
+
rate,
|
|
1052
|
+
tax: fromSatang(taxSatang)
|
|
1053
|
+
},
|
|
1054
|
+
satang,
|
|
1055
|
+
taxSatang
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
// src/th/stages/final-withholding.ts
|
|
1059
|
+
function toStructuredInput(input) {
|
|
1060
|
+
return typeof input === "number" ? { amount: input } : input ?? {};
|
|
1061
|
+
}
|
|
1062
|
+
function sumSatang(sources, seed = 0) {
|
|
1063
|
+
return sources.reduce((sum, source) => sum + source.satang, seed);
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Splits Section 40(4) income into regular-bracket income and final-withheld income.
|
|
1067
|
+
*
|
|
1068
|
+
* Purpose: Applies each source's election so only included income reaches the regular schedule.
|
|
1069
|
+
* Math: For each source elected `"final"`, tax = amount * rate (satang, half-up) and the income is
|
|
1070
|
+
* excluded from regular brackets. `"include"` (default) adds the income to Section 40(4)
|
|
1071
|
+
* assessable income at a 0% expense rate.
|
|
1072
|
+
* Units: Inputs and outputs in THB major units; arithmetic in integer satang.
|
|
1073
|
+
* Throws: TaxValidationError for negative/non-finite amounts or an invalid `corpTaxRate`.
|
|
1074
|
+
*
|
|
1075
|
+
* @param input Bare amount, structured sources, or undefined.
|
|
1076
|
+
* @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 56, 57 ter.
|
|
1077
|
+
*/
|
|
1078
|
+
function resolveSection40_4(input) {
|
|
1079
|
+
const section = toStructuredInput(input);
|
|
1080
|
+
const rates = TH_2026_PARAMETERS.finalWithholding;
|
|
1081
|
+
const base = extractElectableAmount(section.amount, "income.section40_4");
|
|
1082
|
+
const interest = resolveSource(section.interest, "income.section40_4.interest", "income.section40_4.interest.election", rates.interestRate);
|
|
1083
|
+
const dividends = resolveSource(section.dividends, "income.section40_4.dividends", "income.section40_4.dividends.election", rates.dividendRate);
|
|
1084
|
+
const digital = resolveSource(section.digitalAssets, "income.section40_4.digitalAssets", "income.section40_4.digitalAssets.election", rates.digitalAssetRate);
|
|
1085
|
+
const corpTaxRate = typeof section.dividends === "object" ? section.dividends.corpTaxRate : void 0;
|
|
1086
|
+
if (corpTaxRate !== void 0) validateCorpTaxRate(corpTaxRate);
|
|
1087
|
+
const sources = [
|
|
1088
|
+
interest,
|
|
1089
|
+
dividends,
|
|
1090
|
+
digital
|
|
1091
|
+
];
|
|
1092
|
+
const isFinal = (source) => source.breakdown.election === ThFinalWithholdingElection.Final;
|
|
1093
|
+
const finalSources = sources.filter(isFinal);
|
|
1094
|
+
const includedSatang = sumSatang(sources.filter((source) => !isFinal(source)), toSatang(base, "income.section40_4"));
|
|
1095
|
+
const isDividendIncluded = !isFinal(dividends);
|
|
1096
|
+
return {
|
|
1097
|
+
includedAmount: fromSatang(includedSatang),
|
|
1098
|
+
includedDividends: isDividendIncluded ? dividends.breakdown.amount : 0,
|
|
1099
|
+
corpTaxRate: isDividendIncluded ? corpTaxRate : void 0,
|
|
1100
|
+
finalWithholding: {
|
|
1101
|
+
interest: interest.breakdown,
|
|
1102
|
+
dividends: dividends.breakdown,
|
|
1103
|
+
digitalAssets: digital.breakdown,
|
|
1104
|
+
totalAmount: fromSatang(sumSatang(finalSources)),
|
|
1105
|
+
totalTax: fromSatang(finalSources.reduce((sum, source) => sum + source.taxSatang, 0))
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
// src/th/stages/real-estate-expense.ts
|
|
1110
|
+
/**
|
|
1111
|
+
* Caps the holding period used by Section 48(4) at the statutory maximum.
|
|
1112
|
+
*
|
|
1113
|
+
* Purpose: More than 10 years of holding counts as 10 years.
|
|
1114
|
+
* Math: min(requestedYears, `realEstate.maxHoldingYears`).
|
|
1115
|
+
* Units: Dimensionless year count (callers round part-years up).
|
|
1116
|
+
* Throws: Never; `requestedYears` is validated by the caller.
|
|
1117
|
+
*
|
|
1118
|
+
* @param requestedYears Validated positive safe integer holding period.
|
|
1119
|
+
* @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.
|
|
1120
|
+
*/
|
|
1121
|
+
function effectiveHoldingYears(requestedYears) {
|
|
1122
|
+
return Math.min(requestedYears, TH_2026_PARAMETERS.realEstate.maxHoldingYears);
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Selects the Section 48(4) expense rate for the acquisition type.
|
|
1126
|
+
*
|
|
1127
|
+
* Purpose: Inherited or gifted property takes a flat rate; commercial or other property takes the
|
|
1128
|
+
* holding-year table. The two are never combined.
|
|
1129
|
+
* Math: Flat `inheritanceExpenseRate`, or the last `expenseTable` row with `minYears <= years`.
|
|
1130
|
+
* Units: Rate ratio; `years` is the effective (capped) holding period.
|
|
1131
|
+
* Throws: Never; the acquisition type is validated by the caller.
|
|
1132
|
+
*
|
|
1133
|
+
* @param acquisitionType Validated acquisition type.
|
|
1134
|
+
* @param years Effective holding years.
|
|
1135
|
+
* @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.
|
|
1136
|
+
*/
|
|
1137
|
+
function resolveRealEstateExpense(acquisitionType, years) {
|
|
1138
|
+
const rules = TH_2026_PARAMETERS.realEstate;
|
|
1139
|
+
if (acquisitionType === ThRealEstateAcquisitionType.InheritanceOrGift) return {
|
|
1140
|
+
basis: ThRealEstateExpenseBasis.FlatInheritance,
|
|
1141
|
+
rate: rules.inheritanceExpenseRate
|
|
1142
|
+
};
|
|
1143
|
+
const rate = rules.expenseTable.reduce((acc, row) => years >= row.minYears ? row.rate : acc, 0);
|
|
1144
|
+
return {
|
|
1145
|
+
basis: ThRealEstateExpenseBasis.HoldingPeriodTable,
|
|
1146
|
+
rate
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
// src/th/stages/real-estate.ts
|
|
1150
|
+
const EMPTY_BREAKDOWN = {
|
|
1151
|
+
grossTransferValue: 0,
|
|
1152
|
+
acquisitionType: ThRealEstateAcquisitionType.Commercial,
|
|
1153
|
+
expenseBasis: ThRealEstateExpenseBasis.HoldingPeriodTable,
|
|
1154
|
+
requestedHoldingYears: 0,
|
|
1155
|
+
effectiveHoldingYears: 0,
|
|
1156
|
+
expenseRate: 0,
|
|
1157
|
+
expense: 0,
|
|
1158
|
+
netGain: 0,
|
|
1159
|
+
annualGain: 0,
|
|
1160
|
+
brackets: evaluateThBrackets(0, 0).brackets,
|
|
1161
|
+
annualTax: 0,
|
|
1162
|
+
taxBeforeCap: 0,
|
|
1163
|
+
taxCap: 0,
|
|
1164
|
+
wasClamped: false,
|
|
1165
|
+
tax: 0
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* Scales every finite annualised-schedule bracket bound by the holding period.
|
|
1169
|
+
*
|
|
1170
|
+
* Math: `years * tax(net / years)` equals the tax on `net` against bounds multiplied by `years`
|
|
1171
|
+
* (each slice `rate * clip(net/years - lo, 0, width) * years = rate * clip(net - lo*years, 0, width*years)`),
|
|
1172
|
+
* so the annualisation needs no intermediate rounding.
|
|
1173
|
+
* Units: THB major units; scaling performed in integer satang.
|
|
1174
|
+
* Throws: TaxValidationError if a scaled bound exceeds the safe integer range (unreachable
|
|
1175
|
+
* for the capped holding period).
|
|
1176
|
+
*/
|
|
1177
|
+
function scaleBracketsByYears(years) {
|
|
1178
|
+
return TH_2026_PARAMETERS.realEstate.brackets.map((bracket) => {
|
|
1179
|
+
if (bracket.upTo === null) return bracket;
|
|
1180
|
+
return {
|
|
1181
|
+
upTo: fromSatang(multiplySafeInteger(toSatang(bracket.upTo), years, "realEstateTransfer.holdingYears")),
|
|
1182
|
+
rate: bracket.rate
|
|
1183
|
+
};
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Clamps real estate tax at the statutory share of the gross transfer value.
|
|
1188
|
+
*
|
|
1189
|
+
* Purpose: Applies the Section 48(4) ceiling on the separately computed tax.
|
|
1190
|
+
* Math: taxCap = grossMinor * `realEstate.taxCapRate` (20%, half-up); tax = min(taxBeforeCap, taxCap).
|
|
1191
|
+
* Units: Integer minor units (satang) in and out.
|
|
1192
|
+
* Throws: TaxValidationError if `grossMinor` is not a non-negative safe integer.
|
|
1193
|
+
* Note: Unreachable end to end (top rate 35% of at most 50% net is 17.5% < 20%); kept as the
|
|
1194
|
+
* statutory guard.
|
|
1195
|
+
*
|
|
1196
|
+
* @param taxBeforeCap Uncapped tax in satang.
|
|
1197
|
+
* @param grossMinor Gross transfer value in satang.
|
|
1198
|
+
* @statute Thailand Revenue Code Section 48(4).
|
|
1199
|
+
*/
|
|
1200
|
+
function applyRealEstateCap(taxBeforeCap, grossMinor) {
|
|
1201
|
+
const taxCap = applyPercentage(grossMinor, TH_2026_PARAMETERS.realEstate.taxCapRate);
|
|
1202
|
+
return {
|
|
1203
|
+
taxCap,
|
|
1204
|
+
tax: Math.min(taxBeforeCap, taxCap),
|
|
1205
|
+
wasClamped: taxBeforeCap > taxCap
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Calculates Section 48(4) tax on an inherited, gifted, or sold real estate transfer.
|
|
1210
|
+
*
|
|
1211
|
+
* Purpose: Taxes the gain as if earned evenly over the holding period, then scales back up.
|
|
1212
|
+
* `grossTransferValue` must be the official appraised value (ราคาประเมินทุนทรัพย์, Section 49 bis),
|
|
1213
|
+
* not the actual sale price. `holdingYears` is a positive safe integer; callers round part-years
|
|
1214
|
+
* up, and more than 10 years counts as 10.
|
|
1215
|
+
* Math: years = min(holdingYears, 10); expense = gross * 50% for inheritance/gift, otherwise
|
|
1216
|
+
* gross * table(years) (never combined); net = gross - expense; tax = years * bracketTax(net / years)
|
|
1217
|
+
* on the schedule without a 0% tier (5% from the first baht), evaluated exactly as bracketTax of
|
|
1218
|
+
* `net` against bounds multiplied by `years` (no per-year rounding); tax is clamped at 20% of gross
|
|
1219
|
+
* (which the 35% top rate on at most 50% net can never reach). `annualGain` and `annualTax` are
|
|
1220
|
+
* rounded for display.
|
|
1221
|
+
* Units: Inputs and outputs in THB major units; arithmetic in integer satang.
|
|
1222
|
+
* Throws: TaxValidationError for a negative/non-finite value, a non-positive/non-safe-integer
|
|
1223
|
+
* holding period, or an unknown `acquisitionType`.
|
|
1224
|
+
*
|
|
1225
|
+
* @statute Thailand Revenue Code Section 48(4), Section 42(5), Section 49 bis.
|
|
1226
|
+
*/
|
|
1227
|
+
function calculateRealEstateTransfer(input) {
|
|
1228
|
+
if (input === void 0) return EMPTY_BREAKDOWN;
|
|
1229
|
+
validateMonetaryAmount(input.grossTransferValue, "realEstateTransfer.grossTransferValue");
|
|
1230
|
+
validateSafeIntegerCount(input.holdingYears, "realEstateTransfer.holdingYears", 1);
|
|
1231
|
+
const acquisitionType = resolveChoice(input.acquisitionType, ThRealEstateAcquisitionType, ThRealEstateAcquisitionType.Commercial, "realEstateTransfer.acquisitionType");
|
|
1232
|
+
const years = effectiveHoldingYears(input.holdingYears);
|
|
1233
|
+
const { basis, rate: expenseRate } = resolveRealEstateExpense(acquisitionType, years);
|
|
1234
|
+
const gross = toSatang(input.grossTransferValue, "realEstateTransfer.grossTransferValue");
|
|
1235
|
+
const expense = applyPercentage(gross, expenseRate);
|
|
1236
|
+
const netGain = gross - expense;
|
|
1237
|
+
const evaluated = evaluateThBracketSet(fromSatang(netGain), fromSatang(netGain), scaleBracketsByYears(years));
|
|
1238
|
+
const taxBeforeCap = toSatang(evaluated.totalTax);
|
|
1239
|
+
const capped = applyRealEstateCap(taxBeforeCap, gross);
|
|
1240
|
+
return {
|
|
1241
|
+
grossTransferValue: input.grossTransferValue,
|
|
1242
|
+
acquisitionType,
|
|
1243
|
+
expenseBasis: basis,
|
|
1244
|
+
requestedHoldingYears: input.holdingYears,
|
|
1245
|
+
effectiveHoldingYears: years,
|
|
1246
|
+
expenseRate,
|
|
1247
|
+
expense: fromSatang(expense),
|
|
1248
|
+
netGain: fromSatang(netGain),
|
|
1249
|
+
annualGain: fromSatang(roundHalfUp(netGain / years)),
|
|
1250
|
+
brackets: evaluated.brackets,
|
|
1251
|
+
annualTax: fromSatang(roundHalfUp(taxBeforeCap / years)),
|
|
1252
|
+
taxBeforeCap: fromSatang(taxBeforeCap),
|
|
1253
|
+
taxCap: fromSatang(capped.taxCap),
|
|
1254
|
+
wasClamped: capped.wasClamped,
|
|
1255
|
+
tax: fromSatang(capped.tax)
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
// src/th/stages/regular-income.ts
|
|
1259
|
+
/**
|
|
1260
|
+
* Assembles the income that flows through the regular progressive schedule.
|
|
1261
|
+
*
|
|
1262
|
+
* Math: Section 40(1) = declared Section 40(1) + ineligible Section 48(5) lump sum;
|
|
1263
|
+
* Section 40(4) = included Section 40(4) income (final-withheld sources excluded)
|
|
1264
|
+
* + Section 47 bis gross-up. Other categories pass through untouched.
|
|
1265
|
+
* Units: THB major units; the Section 40(1) sum is performed in integer satang.
|
|
1266
|
+
* Throws: TaxValidationError if the declared Section 40(1) is negative or non-finite.
|
|
1267
|
+
*
|
|
1268
|
+
* @param income Declared Section 40 income.
|
|
1269
|
+
* @param special Lump sum, included Section 40(4) income, and dividend gross-up.
|
|
1270
|
+
* @statute Thailand Revenue Code Sections 40(1), 40(4), 47 bis, 48(5).
|
|
1271
|
+
*/
|
|
1272
|
+
function buildRegularIncome(income, special) {
|
|
1273
|
+
const declared = income?.section40_1 ?? 0;
|
|
1274
|
+
validateMonetaryAmount(declared, "income.section40_1");
|
|
1275
|
+
const section40_1 = toSatang(declared, "income.section40_1") + toSatang(special.lumpSum, "income.section40_1");
|
|
1276
|
+
const section40_4 = toSatang(special.section40_4, "income.section40_4") + toSatang(special.dividendCredit, "income.section40_4");
|
|
1277
|
+
return {
|
|
1278
|
+
...income,
|
|
1279
|
+
section40_1: fromSatang(section40_1),
|
|
1280
|
+
section40_4: fromSatang(section40_4)
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
// src/th/stages/separate-sheet.ts
|
|
1284
|
+
const EMPTY_INPUT = {
|
|
1285
|
+
amount: 0,
|
|
1286
|
+
yearsOfService: 0,
|
|
1287
|
+
lastDailyWage: 0
|
|
1288
|
+
};
|
|
1289
|
+
/**
|
|
1290
|
+
* Calculates the Section 48(5) separate sheet for one-time severance/retirement lump sums.
|
|
1291
|
+
*
|
|
1292
|
+
* Purpose: Applies the severance exemption, then taxes an eligible remainder on its own schedule
|
|
1293
|
+
* or returns the ineligible remainder for regular treatment.
|
|
1294
|
+
* Math: For `paymentType` severance (the default) at ANY length of service:
|
|
1295
|
+
* exemption = min(amount, 400 * lastDailyWage, 600,000); `"retirement"` payments get none.
|
|
1296
|
+
* remainder = amount - exemption. With yearsOfService >= 5: fixed = min(7,000 * years, remainder);
|
|
1297
|
+
* percentage expense = 50% of (remainder - fixed), half-up; net = remainder - fixed - percentage,
|
|
1298
|
+
* taxed on the separate schedule (5%..35%, no 0% tier). With fewer than 5 years only the remainder
|
|
1299
|
+
* is returned in `addedToSection40_1` for the regular computation.
|
|
1300
|
+
* Units: Inputs and outputs in THB major units; arithmetic in integer satang.
|
|
1301
|
+
* Throws: TaxValidationError for negative/non-finite money, a non-safe-integer/negative `yearsOfService`,
|
|
1302
|
+
* an unknown `paymentType`, or a wage/years value whose scaled satang amount exceeds safe integers.
|
|
1303
|
+
*
|
|
1304
|
+
* @statute Thailand Revenue Code Section 48(5); Section 42(1) (severance exemption).
|
|
1305
|
+
*/
|
|
1306
|
+
function calculateSeparateSheet(input = EMPTY_INPUT) {
|
|
1307
|
+
const rules = TH_2026_PARAMETERS.separateSheet;
|
|
1308
|
+
validateMonetaryAmount(input.amount, "separateSheet.amount");
|
|
1309
|
+
validateMonetaryAmount(input.lastDailyWage, "separateSheet.lastDailyWage");
|
|
1310
|
+
validateSafeIntegerCount(input.yearsOfService, "separateSheet.yearsOfService");
|
|
1311
|
+
const paymentType = resolveChoice(input.paymentType, ThSeparateSheetPaymentType, ThSeparateSheetPaymentType.Severance, "separateSheet.paymentType");
|
|
1312
|
+
const years = input.yearsOfService;
|
|
1313
|
+
const isEligible = years >= rules.minYearsOfService;
|
|
1314
|
+
const amountSatang = toSatang(input.amount, "separateSheet.amount");
|
|
1315
|
+
const wageSatang = toSatang(input.lastDailyWage, "separateSheet.lastDailyWage");
|
|
1316
|
+
const isSeverance = paymentType === ThSeparateSheetPaymentType.Severance;
|
|
1317
|
+
const wageCap = isSeverance ? multiplySafeInteger(wageSatang, rules.exemptionWageDays, "separateSheet.lastDailyWage") : 0;
|
|
1318
|
+
const exemption = isSeverance ? Math.min(amountSatang, wageCap, toSatang(rules.exemptionCeiling)) : 0;
|
|
1319
|
+
const remainder = amountSatang - exemption;
|
|
1320
|
+
const fixedCap = multiplySafeInteger(toSatang(rules.expensePerYearOfService), years, "separateSheet.yearsOfService");
|
|
1321
|
+
const fixed = isEligible ? Math.min(remainder, fixedCap) : 0;
|
|
1322
|
+
const percentage = isEligible ? applyPercentage(remainder - fixed, rules.remainderExpenseRate) : 0;
|
|
1323
|
+
const net = fromSatang(isEligible ? remainder - fixed - percentage : 0);
|
|
1324
|
+
const evaluated = evaluateThBracketSet(net, net, rules.brackets);
|
|
1325
|
+
return {
|
|
1326
|
+
amount: input.amount,
|
|
1327
|
+
yearsOfService: years,
|
|
1328
|
+
lastDailyWage: input.lastDailyWage,
|
|
1329
|
+
isEligible,
|
|
1330
|
+
addedToSection40_1: isEligible ? 0 : fromSatang(remainder),
|
|
1331
|
+
wageBasedCap: fromSatang(wageCap),
|
|
1332
|
+
exemptionCeiling: rules.exemptionCeiling,
|
|
1333
|
+
paymentType,
|
|
1334
|
+
exemption: fromSatang(exemption),
|
|
1335
|
+
remainder: fromSatang(remainder),
|
|
1336
|
+
fixedExpense: fromSatang(fixed),
|
|
1337
|
+
percentageExpense: fromSatang(percentage),
|
|
1338
|
+
netTaxable: net,
|
|
1339
|
+
brackets: evaluated.brackets,
|
|
1340
|
+
tax: evaluated.totalTax
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
// src/th/stages/special-regimes.ts
|
|
1344
|
+
/**
|
|
1345
|
+
* Resolves Section 40(4) elections, the Section 47 bis credit, the Section 48(5) separate sheet,
|
|
1346
|
+
* and the Section 48(4) real estate transfer, then assembles the remaining regular income.
|
|
1347
|
+
*
|
|
1348
|
+
* Math: Regular Section 40(4) = included sources + dividend gross-up; ineligible lump sum joins
|
|
1349
|
+
* Section 40(1). Gross outside the regular schedule = final-elected 40(4) + eligible lump sum (or, if
|
|
1350
|
+
* ineligible, its severance exemption) + real estate gross transfer value.
|
|
1351
|
+
* Units: THB major units; sums performed in integer satang.
|
|
1352
|
+
* Throws: TaxValidationError for any invalid special-regime input.
|
|
1353
|
+
*
|
|
1354
|
+
* @statute Thailand Revenue Code Sections 40(4), 47 bis, 48(4), 48(5).
|
|
1355
|
+
*/
|
|
1356
|
+
function resolveSpecialRegimes(input) {
|
|
1357
|
+
const section40_4 = resolveSection40_4(input.income?.section40_4);
|
|
1358
|
+
const dividendTaxCredit = calculateDividendTaxCredit(section40_4.includedDividends, section40_4.corpTaxRate);
|
|
1359
|
+
const separateSheet = calculateSeparateSheet(input.separateSheet);
|
|
1360
|
+
const realEstate = calculateRealEstateTransfer(input.realEstateTransfer);
|
|
1361
|
+
return {
|
|
1362
|
+
regularIncome: buildRegularIncome(input.income, {
|
|
1363
|
+
lumpSum: separateSheet.addedToSection40_1,
|
|
1364
|
+
section40_4: section40_4.includedAmount,
|
|
1365
|
+
dividendCredit: dividendTaxCredit.credit
|
|
1366
|
+
}),
|
|
1367
|
+
grossOutsideRegular: sumThAmounts([
|
|
1368
|
+
section40_4.finalWithholding.totalAmount,
|
|
1369
|
+
separateSheet.isEligible ? separateSheet.amount : separateSheet.exemption,
|
|
1370
|
+
realEstate.grossTransferValue
|
|
1371
|
+
], "grossIncome"),
|
|
1372
|
+
finalWithholding: section40_4.finalWithholding,
|
|
1373
|
+
dividendTaxCredit,
|
|
1374
|
+
separateSheet,
|
|
1375
|
+
realEstate
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
// src/th/stages/pipeline.ts
|
|
1379
|
+
function validateYear(year) {
|
|
1380
|
+
if (year !== void 0 && year !== TH_2026_PARAMETERS.taxYear) throw new TaxValidationError("taxYear", year, "Unsupported tax year; only 2026 is currently supported");
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Executes full Thailand PIT calculation pipeline for ภ.ง.ด.90/91.
|
|
1384
|
+
*
|
|
1385
|
+
* @statute Thailand Revenue Code Sections 40, 42 bis, 47, 47 bis, 48(1)–(5).
|
|
1386
|
+
*/
|
|
1387
|
+
function runThTaxPipeline(input = {}) {
|
|
1388
|
+
validateYear(input.taxYear);
|
|
1389
|
+
const filingStatus = input.filingStatus ?? "single";
|
|
1390
|
+
const withholding = input.withholdingTax ?? 0;
|
|
1391
|
+
validateMonetaryAmount(withholding, "withholdingTax");
|
|
1392
|
+
const special = resolveSpecialRegimes(input);
|
|
1393
|
+
const income = special.regularIncome;
|
|
1394
|
+
const assessable = calculateAssessableIncome(income);
|
|
1395
|
+
const expenses = calculateExpenseDeductions(income, assessable);
|
|
1396
|
+
const netIncomeAfterExpenses = Math.max(0, assessable.total - expenses.total);
|
|
1397
|
+
const allowancesRes = calculateAllowances(input.allowances, filingStatus, assessable.total, expenses.total, assessable.section40_1);
|
|
1398
|
+
const evalBrackets = evaluateThBrackets(allowancesRes.netTaxableIncome, assessable.total);
|
|
1399
|
+
const progressiveTax = evalBrackets.totalTax;
|
|
1400
|
+
const minimumGrossTax = calculateMinimumGrossTax(assessable.grossNonEmploymentIncome, progressiveTax);
|
|
1401
|
+
const regularTax = minimumGrossTax.wasApplied ? minimumGrossTax.calculatedTax : progressiveTax;
|
|
1402
|
+
const combined = combineThLiability({
|
|
1403
|
+
regularTax,
|
|
1404
|
+
separateSheetTax: special.separateSheet.tax,
|
|
1405
|
+
realEstateTax: special.realEstate.tax,
|
|
1406
|
+
finalWithholdingTax: special.finalWithholding.totalTax,
|
|
1407
|
+
dividendTaxCredit: special.dividendTaxCredit.credit,
|
|
1408
|
+
withholdingTax: withholding
|
|
1409
|
+
});
|
|
1410
|
+
const grossIncome = sumThAmounts([assessable.total, special.grossOutsideRegular], "grossIncome");
|
|
1411
|
+
const effectiveRate = grossIncome > 0 ? combined.totalCombinedTax / grossIncome : 0;
|
|
1412
|
+
const breakdown = {
|
|
1413
|
+
assessableIncome: assessable,
|
|
1414
|
+
expenseDeductions: expenses,
|
|
1415
|
+
netIncomeAfterExpenses,
|
|
1416
|
+
allowances: allowancesRes.breakdown,
|
|
1417
|
+
netTaxableIncome: allowancesRes.netTaxableIncome,
|
|
1418
|
+
progressiveTax,
|
|
1419
|
+
progressiveBrackets: evalBrackets.brackets,
|
|
1420
|
+
minimumGrossTax,
|
|
1421
|
+
withholdingTax: withholding,
|
|
1422
|
+
netTaxPayable: combined.netTaxPayable,
|
|
1423
|
+
regularTax,
|
|
1424
|
+
separateSheet: special.separateSheet,
|
|
1425
|
+
realEstate: special.realEstate,
|
|
1426
|
+
finalWithholding: special.finalWithholding,
|
|
1427
|
+
dividendTaxCredit: special.dividendTaxCredit,
|
|
1428
|
+
totalCombinedTax: combined.totalCombinedTax
|
|
1429
|
+
};
|
|
1430
|
+
return {
|
|
1431
|
+
jurisdiction: "TH",
|
|
1432
|
+
taxYear: TH_2026_PARAMETERS.taxYear,
|
|
1433
|
+
currency: TH_2026_PARAMETERS.currency,
|
|
1434
|
+
grossIncome: thb(grossIncome, "grossIncome"),
|
|
1435
|
+
taxableIncome: thb(allowancesRes.netTaxableIncome, "taxableIncome"),
|
|
1436
|
+
totalTax: thb(combined.totalCombinedTax, "totalTax"),
|
|
1437
|
+
effectiveRate,
|
|
1438
|
+
marginalRate: evalBrackets.brackets.marginalRate,
|
|
1439
|
+
netTaxPayable: thb(combined.netTaxPayable, "netTaxPayable", { allowNegative: true }),
|
|
1440
|
+
breakdown
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
// src/th/calculate.ts
|
|
1444
|
+
/**
|
|
1445
|
+
* Calculates Thailand Personal Income Tax (ภ.ง.ด.90/91) under Revenue Code.
|
|
1446
|
+
*
|
|
1447
|
+
* Defaults to tax year 2026 and single filing status.
|
|
1448
|
+
*
|
|
1449
|
+
* @param input Taxpayer assessable income, allowances, filing status, and withholding.
|
|
1450
|
+
* @returns Fully typed, immutable TaxResult with statutory breakdown.
|
|
1451
|
+
*
|
|
1452
|
+
* @statute Thailand Revenue Code Sections 40, 42 bis, 47, 48(1)–(2).
|
|
1453
|
+
*/
|
|
1454
|
+
function calculateThTax(input = {}) {
|
|
1455
|
+
return runThTaxPipeline(input);
|
|
1456
|
+
}
|
|
1457
|
+
// src/th/system.ts
|
|
1458
|
+
/**
|
|
1459
|
+
* Thailand Tax System implementation for 2026.
|
|
1460
|
+
*
|
|
1461
|
+
* Implements TaxSystem contract for `@trajectonomics/tax/th`.
|
|
1462
|
+
*/
|
|
1463
|
+
const th = {
|
|
1464
|
+
jurisdiction: "TH",
|
|
1465
|
+
taxYear: 2026,
|
|
1466
|
+
currency: "THB",
|
|
1467
|
+
calculate(input = {}) {
|
|
1468
|
+
return calculateThTax(input);
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
export { TH_2026_PARAMETERS, ThFinalWithholdingElection, ThRealEstateAcquisitionType, ThRealEstateExpenseBasis, ThSeparateSheetPaymentType, calculateThTax, th };
|
|
1472
|
+
|
|
1473
|
+
//# sourceMappingURL=index.mjs.map
|