@ph-dev-utils/payroll 0.1.0 β 0.2.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/README.md +96 -10
- package/data/bir-wt-monthly-train-2023.json +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/take-home.d.ts +28 -23
- package/dist/take-home.js +24 -23
- package/dist/withholding-tax.d.ts +60 -0
- package/dist/withholding-tax.js +88 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
[](https://github.com/kon2raya24/ph-payroll/blob/main/LICENSE)
|
|
5
5
|
[](https://github.com/kon2raya24)
|
|
6
6
|
|
|
7
|
-
Filipino payroll calculators for JavaScript / TypeScript β SSS, PhilHealth, Pag-IBIG monthly contributions, 13th-month pay, and pre-tax net take-home. Tables versioned by effective date.
|
|
7
|
+
Filipino payroll calculators for JavaScript / TypeScript β SSS, PhilHealth, Pag-IBIG monthly contributions, 13th-month pay, BIR monthly withholding tax (TRAIN), and pre-tax / post-tax net take-home. Tables versioned by effective date.
|
|
8
8
|
|
|
9
9
|
## β οΈ READ FIRST
|
|
10
10
|
|
|
11
|
-
This package handles **real money math**. Wrong outputs cause underpayment / overpayment in actual payroll runs. Verify outputs against official agency calculators before production use. Pin your dependency version β tables change every few years. **BIR
|
|
11
|
+
This package handles **real money math**. Wrong outputs cause underpayment / overpayment in actual payroll runs. Verify outputs against official agency calculators before production use. Pin your dependency version β tables change every few years. **BIR WT in v0.2 is monthly-only** and operates on **taxable income**, not raw gross. De minimis caps and the β±90k 13th-month annual exemption are caller-handled in v0.2.
|
|
12
12
|
|
|
13
13
|
Full disclaimer: [project README](https://github.com/kon2raya24/ph-payroll#-read-first--accuracy-disclaimer).
|
|
14
14
|
|
|
@@ -26,6 +26,7 @@ Requires Node 20+.
|
|
|
26
26
|
import { netTakeHome } from '@ph-dev-utils/payroll';
|
|
27
27
|
|
|
28
28
|
netTakeHome(30000);
|
|
29
|
+
// pre-tax (v0.1 shape β unchanged):
|
|
29
30
|
// {
|
|
30
31
|
// gross: 30000,
|
|
31
32
|
// sss: { msc: 30000, employeeShare: 1500, employerShare: 3030, ... },
|
|
@@ -34,6 +35,9 @@ netTakeHome(30000);
|
|
|
34
35
|
// totalDeductions: 2450,
|
|
35
36
|
// net: 27550
|
|
36
37
|
// }
|
|
38
|
+
|
|
39
|
+
netTakeHome(30000, { includeWT: true });
|
|
40
|
+
// adds: taxableIncome: 27550, withholdingTax: 1007.55, netAfterTax: 26542.45
|
|
37
41
|
```
|
|
38
42
|
|
|
39
43
|
## API Reference
|
|
@@ -151,14 +155,84 @@ thirteenthMonthFromMonthly(30000, 1); // 2500
|
|
|
151
155
|
|
|
152
156
|
---
|
|
153
157
|
|
|
158
|
+
### `withholdingTaxMonthly(taxableIncome, opts?)` *(v0.2+)*
|
|
159
|
+
|
|
160
|
+
Source: **BIR RR 11-2018** monthly table (TRAIN Law, second-phase rates effective 2023-01-01).
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
function withholdingTaxMonthly(
|
|
164
|
+
taxableIncome: number,
|
|
165
|
+
opts?: { year?: number }
|
|
166
|
+
): { wt: number; bracket: 1|2|3|4|5|6; marginalRate: number }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Input is taxable income, not gross.** To derive taxable income from gross, use [`taxableIncomeMonthly`](#taxableincomemonthlygross-opts-v02) (or compute manually as `gross β mandatories β nonTaxableAllowances`).
|
|
170
|
+
|
|
171
|
+
**BIR monthly brackets (TRAIN 2023+):**
|
|
172
|
+
|
|
173
|
+
| Bracket | Monthly taxable income | Tax |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| 1 | β€ β±20,833 | 0 |
|
|
176
|
+
| 2 | β±20,833 β β±33,333 | 15% of excess over β±20,833 |
|
|
177
|
+
| 3 | β±33,333 β β±66,667 | β±1,875 + 20% of excess over β±33,333 |
|
|
178
|
+
| 4 | β±66,667 β β±166,667 | β±8,541.80 + 25% of excess over β±66,667 |
|
|
179
|
+
| 5 | β±166,667 β β±666,667 | β±33,541.80 + 30% of excess over β±166,667 |
|
|
180
|
+
| 6 | > β±666,667 | β±183,541.80 + 35% of excess over β±666,667 |
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
withholdingTaxMonthly(15000); // { wt: 0, bracket: 1, marginalRate: 0 }
|
|
184
|
+
withholdingTaxMonthly(25000); // { wt: 625.05, bracket: 2, marginalRate: 0.15 }
|
|
185
|
+
withholdingTaxMonthly(50000); // { wt: 5208.4, bracket: 3, marginalRate: 0.20 }
|
|
186
|
+
withholdingTaxMonthly(100000); // { wt: 16875.05, bracket: 4, marginalRate: 0.25 }
|
|
187
|
+
withholdingTaxMonthly(200000); // { wt: 43541.7, bracket: 5, marginalRate: 0.30 }
|
|
188
|
+
withholdingTaxMonthly(1000000); // { wt: 300208.35, bracket: 6, marginalRate: 0.35 }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### `taxableIncomeMonthly(gross, opts?)` *(v0.2+)*
|
|
194
|
+
|
|
195
|
+
Derive monthly taxable income from gross. Formula:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
taxableIncome = gross β mandatoryDeductions β nonTaxableAllowances
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
function taxableIncomeMonthly(
|
|
203
|
+
gross: number,
|
|
204
|
+
opts?: {
|
|
205
|
+
year?: number;
|
|
206
|
+
mandatoryDeductions?: number; // override SSS+PH+Pag-IBIG; default: auto-computed
|
|
207
|
+
nonTaxableAllowances?: number; // de minimis within caps + tax-exempt allowances; default: 0
|
|
208
|
+
}
|
|
209
|
+
): number
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Caller-handled (not done here):
|
|
213
|
+
- **De minimis caps** β pass only the non-taxable *portion* of each benefit. Rice subsidy is exempt up to β±2,000/mo, uniform β±6,000/yr, medical β±10,000/yr (employee), etc. If a benefit exceeds its cap, the excess is taxable and should NOT appear in `nonTaxableAllowances`.
|
|
214
|
+
- **13th-month / bonus β±90k annual exemption** β applies at year level, not month. Excess goes in `gross` for the month received.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
taxableIncomeMonthly(30000); // 27550 (auto: gross β 2,450 mandatories β 0)
|
|
218
|
+
taxableIncomeMonthly(30000, { nonTaxableAllowances: 2000 }); // 25550
|
|
219
|
+
taxableIncomeMonthly(50000, { mandatoryDeductions: 3200 }); // 46800
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
154
224
|
### `netTakeHome(monthlySalary, opts?)`
|
|
155
225
|
|
|
156
|
-
|
|
226
|
+
Net take-home: gross salary minus mandatory SSS / PhilHealth / Pag-IBIG employee contributions, and (optionally, v0.2+) BIR withholding tax.
|
|
157
227
|
|
|
158
228
|
```ts
|
|
159
229
|
function netTakeHome(
|
|
160
230
|
monthlySalary: number,
|
|
161
|
-
opts?: {
|
|
231
|
+
opts?: {
|
|
232
|
+
year?: number;
|
|
233
|
+
includeWT?: boolean; // v0.2+: also compute WT and netAfterTax
|
|
234
|
+
nonTaxableAllowances?: number; // v0.2+: only used when includeWT is true
|
|
235
|
+
}
|
|
162
236
|
): NetTakeHome
|
|
163
237
|
|
|
164
238
|
interface NetTakeHome {
|
|
@@ -166,17 +240,29 @@ interface NetTakeHome {
|
|
|
166
240
|
sss: SSSContribution;
|
|
167
241
|
philHealth: PhilHealthContribution;
|
|
168
242
|
pagIbig: PagIbigContribution;
|
|
169
|
-
totalDeductions: number;
|
|
170
|
-
net: number;
|
|
243
|
+
totalDeductions: number; // sum of employee shares
|
|
244
|
+
net: number; // gross - totalDeductions (pre-tax)
|
|
245
|
+
taxableIncome?: number; // populated only when includeWT
|
|
246
|
+
withholdingTax?: number; // populated only when includeWT
|
|
247
|
+
netAfterTax?: number; // populated only when includeWT
|
|
171
248
|
}
|
|
172
249
|
```
|
|
173
250
|
|
|
174
|
-
|
|
251
|
+
By default (no `includeWT`), this preserves the v0.1 pre-tax shape. The `includeWT: true` opt-in adds three fields without removing any.
|
|
175
252
|
|
|
176
253
|
```ts
|
|
177
|
-
|
|
178
|
-
netTakeHome(
|
|
179
|
-
netTakeHome(
|
|
254
|
+
// v0.1 shape (default) β pre-tax:
|
|
255
|
+
netTakeHome(30000).net; // 27550
|
|
256
|
+
netTakeHome(8000).net; // 7190
|
|
257
|
+
netTakeHome(150000).net; // 145550
|
|
258
|
+
|
|
259
|
+
// v0.2+ with WT:
|
|
260
|
+
netTakeHome(30000, { includeWT: true }).withholdingTax; // 1007.55
|
|
261
|
+
netTakeHome(30000, { includeWT: true }).netAfterTax; // 26542.45
|
|
262
|
+
|
|
263
|
+
// v0.2+ with non-taxable allowances:
|
|
264
|
+
netTakeHome(30000, { includeWT: true, nonTaxableAllowances: 2000 }).withholdingTax;
|
|
265
|
+
// 707.55 (lower because TI dropped from 27,550 β 25,550)
|
|
180
266
|
```
|
|
181
267
|
|
|
182
268
|
---
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"effective_from": "2023-01-01",
|
|
4
|
+
"effective_until": null,
|
|
5
|
+
"applies_to_years": [2023, 2024, 2025, 2026],
|
|
6
|
+
"period": "monthly",
|
|
7
|
+
"source": "BIR Revenue Regulations No. 11-2018 (as amended by TRAIN Law) β Monthly Compensation Level (MCL) table, Annex E",
|
|
8
|
+
"source_url": "https://www.bir.gov.ph/index.php/tax-information/withholding-tax.html",
|
|
9
|
+
"authority": "Republic Act No. 10963 (Tax Reform for Acceleration and Inclusion / TRAIN Law), as further amended by RA 11534 (CREATE Law) β individual income tax brackets effective January 1, 2023 onwards (second phase of TRAIN rate reductions)",
|
|
10
|
+
"verified_on": "2026-05-20",
|
|
11
|
+
"notes": [
|
|
12
|
+
"Withholding tax is applied to TAXABLE INCOME, not gross compensation.",
|
|
13
|
+
"Taxable income = gross compensation β mandatory employee contributions (SSS, PhilHealth, Pag-IBIG) β non-taxable allowances (de minimis benefits within caps).",
|
|
14
|
+
"13th-month pay and other bonuses are non-taxable up to β±90,000 per year (excess is taxable in the year of receipt); not applied at monthly level.",
|
|
15
|
+
"Bracket bases (1875.00, 8541.80, 33541.80, 183541.80) are the BIR-published rounded monthly values derived from annualized cumulative tax (annual base Γ· 12).",
|
|
16
|
+
"This table is for MONTHLY payroll periods only. Semi-monthly, weekly, and daily payrolls use separate BIR-published tables (planned for v0.3)."
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
"brackets": [
|
|
21
|
+
{ "bracket": 1, "over": 0, "not_over": 20833, "base": 0.00, "rate_over_min": 0.00 },
|
|
22
|
+
{ "bracket": 2, "over": 20833, "not_over": 33333, "base": 0.00, "rate_over_min": 0.15 },
|
|
23
|
+
{ "bracket": 3, "over": 33333, "not_over": 66667, "base": 1875.00, "rate_over_min": 0.20 },
|
|
24
|
+
{ "bracket": 4, "over": 66667, "not_over": 166667, "base": 8541.80, "rate_over_min": 0.25 },
|
|
25
|
+
{ "bracket": 5, "over": 166667, "not_over": 666667, "base": 33541.80, "rate_over_min": 0.30 },
|
|
26
|
+
{ "bracket": 6, "over": 666667, "not_over": null, "base": 183541.80, "rate_over_min": 0.35 }
|
|
27
|
+
]
|
|
28
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export type { PhilHealthContribution, PhilHealthOptions } from './philhealth.js'
|
|
|
5
5
|
export { pagIbigContribution } from './pagibig.js';
|
|
6
6
|
export type { PagIbigContribution, PagIbigOptions } from './pagibig.js';
|
|
7
7
|
export { thirteenthMonthPay, thirteenthMonthFromMonthly } from './thirteenth-month.js';
|
|
8
|
+
export { withholdingTaxMonthly, taxableIncomeMonthly } from './withholding-tax.js';
|
|
9
|
+
export type { WithholdingTaxResult, TaxableIncomeOptions } from './withholding-tax.js';
|
|
8
10
|
export { netTakeHome } from './take-home.js';
|
|
9
11
|
export type { NetTakeHome, NetTakeHomeOptions } from './take-home.js';
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { sssContribution } from './sss.js';
|
|
|
2
2
|
export { philHealthContribution } from './philhealth.js';
|
|
3
3
|
export { pagIbigContribution } from './pagibig.js';
|
|
4
4
|
export { thirteenthMonthPay, thirteenthMonthFromMonthly } from './thirteenth-month.js';
|
|
5
|
+
export { withholdingTaxMonthly, taxableIncomeMonthly } from './withholding-tax.js';
|
|
5
6
|
export { netTakeHome } from './take-home.js';
|
package/dist/take-home.d.ts
CHANGED
|
@@ -12,37 +12,42 @@ export interface NetTakeHome {
|
|
|
12
12
|
pagIbig: PagIbigContribution;
|
|
13
13
|
/** Sum of employee deductions across SSS, PhilHealth, and Pag-IBIG. */
|
|
14
14
|
totalDeductions: number;
|
|
15
|
-
/** Gross minus totalDeductions.
|
|
15
|
+
/** Gross minus totalDeductions. Pre-tax β see `netAfterTax` for post-WT. */
|
|
16
16
|
net: number;
|
|
17
|
+
/** Monthly taxable income (only present when `includeWT: true`). */
|
|
18
|
+
taxableIncome?: number;
|
|
19
|
+
/** BIR monthly withholding tax (only present when `includeWT: true`). */
|
|
20
|
+
withholdingTax?: number;
|
|
21
|
+
/** Net minus withholdingTax (only present when `includeWT: true`). */
|
|
22
|
+
netAfterTax?: number;
|
|
17
23
|
}
|
|
18
24
|
export interface NetTakeHomeOptions {
|
|
19
25
|
year?: number;
|
|
26
|
+
/**
|
|
27
|
+
* If true, also compute BIR monthly withholding tax (v0.2+) and populate
|
|
28
|
+
* `taxableIncome`, `withholdingTax`, and `netAfterTax` on the result.
|
|
29
|
+
* Default: false (preserves v0.1 pre-tax shape).
|
|
30
|
+
*/
|
|
31
|
+
includeWT?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Total non-taxable allowances for the month. Used only when `includeWT: true`.
|
|
34
|
+
* Caller is responsible for capping per BIR de minimis rules.
|
|
35
|
+
*/
|
|
36
|
+
nonTaxableAllowances?: number;
|
|
20
37
|
}
|
|
21
38
|
/**
|
|
22
|
-
* Compute
|
|
23
|
-
*
|
|
39
|
+
* Compute net take-home: gross monthly salary minus mandatory SSS / PhilHealth /
|
|
40
|
+
* Pag-IBIG employee contributions, and (optionally, v0.2+) BIR withholding tax.
|
|
24
41
|
*
|
|
25
|
-
* **
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* annualization. A simple `gross β WT` calculator would be wrong in most real
|
|
29
|
-
* cases. WT is planned for v0.2 once the package has user feedback and
|
|
30
|
-
* accuracy validation.
|
|
31
|
-
*
|
|
32
|
-
* If you need an estimate including WT, compute taxable income manually and
|
|
33
|
-
* use your preferred tax engine.
|
|
34
|
-
*
|
|
35
|
-
* @param monthlySalary Gross monthly basic salary in pesos.
|
|
42
|
+
* By default, returns **pre-tax** net (v0.1 shape). Pass `{ includeWT: true }` to
|
|
43
|
+
* also compute monthly WT and `netAfterTax`. WT is computed off taxable income,
|
|
44
|
+
* which is derived as `gross β mandatories β nonTaxableAllowances`.
|
|
36
45
|
*
|
|
37
46
|
* @example
|
|
38
|
-
* netTakeHome(30000);
|
|
39
|
-
* // {
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* //
|
|
43
|
-
* // pagIbig: { employee: 200, ... },
|
|
44
|
-
* // totalDeductions: 2450,
|
|
45
|
-
* // net: 27550
|
|
46
|
-
* // }
|
|
47
|
+
* netTakeHome(30000); // pre-tax, v0.1 shape
|
|
48
|
+
* // { gross: 30000, ..., totalDeductions: 2450, net: 27550 }
|
|
49
|
+
*
|
|
50
|
+
* netTakeHome(30000, { includeWT: true }); // adds WT fields
|
|
51
|
+
* // { ..., net: 27550, taxableIncome: 27550, withholdingTax: 1007.55, netAfterTax: 26542.45 }
|
|
47
52
|
*/
|
|
48
53
|
export declare function netTakeHome(monthlySalary: number, opts?: NetTakeHomeOptions): NetTakeHome;
|
package/dist/take-home.js
CHANGED
|
@@ -1,35 +1,24 @@
|
|
|
1
1
|
import { sssContribution } from './sss.js';
|
|
2
2
|
import { philHealthContribution } from './philhealth.js';
|
|
3
3
|
import { pagIbigContribution } from './pagibig.js';
|
|
4
|
+
import { withholdingTaxMonthly, taxableIncomeMonthly } from './withholding-tax.js';
|
|
4
5
|
function round2(n) {
|
|
5
6
|
return Math.round(n * 100) / 100;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
|
-
* Compute
|
|
9
|
-
*
|
|
9
|
+
* Compute net take-home: gross monthly salary minus mandatory SSS / PhilHealth /
|
|
10
|
+
* Pag-IBIG employee contributions, and (optionally, v0.2+) BIR withholding tax.
|
|
10
11
|
*
|
|
11
|
-
* **
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* annualization. A simple `gross β WT` calculator would be wrong in most real
|
|
15
|
-
* cases. WT is planned for v0.2 once the package has user feedback and
|
|
16
|
-
* accuracy validation.
|
|
17
|
-
*
|
|
18
|
-
* If you need an estimate including WT, compute taxable income manually and
|
|
19
|
-
* use your preferred tax engine.
|
|
20
|
-
*
|
|
21
|
-
* @param monthlySalary Gross monthly basic salary in pesos.
|
|
12
|
+
* By default, returns **pre-tax** net (v0.1 shape). Pass `{ includeWT: true }` to
|
|
13
|
+
* also compute monthly WT and `netAfterTax`. WT is computed off taxable income,
|
|
14
|
+
* which is derived as `gross β mandatories β nonTaxableAllowances`.
|
|
22
15
|
*
|
|
23
16
|
* @example
|
|
24
|
-
* netTakeHome(30000);
|
|
25
|
-
* // {
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* //
|
|
29
|
-
* // pagIbig: { employee: 200, ... },
|
|
30
|
-
* // totalDeductions: 2450,
|
|
31
|
-
* // net: 27550
|
|
32
|
-
* // }
|
|
17
|
+
* netTakeHome(30000); // pre-tax, v0.1 shape
|
|
18
|
+
* // { gross: 30000, ..., totalDeductions: 2450, net: 27550 }
|
|
19
|
+
*
|
|
20
|
+
* netTakeHome(30000, { includeWT: true }); // adds WT fields
|
|
21
|
+
* // { ..., net: 27550, taxableIncome: 27550, withholdingTax: 1007.55, netAfterTax: 26542.45 }
|
|
33
22
|
*/
|
|
34
23
|
export function netTakeHome(monthlySalary, opts = {}) {
|
|
35
24
|
if (!Number.isFinite(monthlySalary) || monthlySalary < 0) {
|
|
@@ -40,7 +29,7 @@ export function netTakeHome(monthlySalary, opts = {}) {
|
|
|
40
29
|
const pagIbig = pagIbigContribution(monthlySalary, opts);
|
|
41
30
|
const totalDeductions = round2(sss.employeeShare + philHealth.employee + pagIbig.employee);
|
|
42
31
|
const net = round2(monthlySalary - totalDeductions);
|
|
43
|
-
|
|
32
|
+
const result = {
|
|
44
33
|
gross: monthlySalary,
|
|
45
34
|
sss,
|
|
46
35
|
philHealth,
|
|
@@ -48,4 +37,16 @@ export function netTakeHome(monthlySalary, opts = {}) {
|
|
|
48
37
|
totalDeductions,
|
|
49
38
|
net,
|
|
50
39
|
};
|
|
40
|
+
if (opts.includeWT) {
|
|
41
|
+
const taxableIncome = taxableIncomeMonthly(monthlySalary, {
|
|
42
|
+
year: opts.year,
|
|
43
|
+
mandatoryDeductions: totalDeductions,
|
|
44
|
+
nonTaxableAllowances: opts.nonTaxableAllowances,
|
|
45
|
+
});
|
|
46
|
+
const { wt } = withholdingTaxMonthly(taxableIncome, { year: opts.year });
|
|
47
|
+
result.taxableIncome = taxableIncome;
|
|
48
|
+
result.withholdingTax = wt;
|
|
49
|
+
result.netAfterTax = round2(net - wt);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
51
52
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface WithholdingTaxResult {
|
|
2
|
+
/** Computed withholding tax for the month. */
|
|
3
|
+
wt: number;
|
|
4
|
+
/** Bracket number (1β6) that the taxable income fell into. */
|
|
5
|
+
bracket: 1 | 2 | 3 | 4 | 5 | 6;
|
|
6
|
+
/** Marginal rate applied to the portion above the bracket floor. */
|
|
7
|
+
marginalRate: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TaxableIncomeOptions {
|
|
10
|
+
year?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Override mandatory deductions (SSS+PhilHealth+Pag-IBIG employee shares).
|
|
13
|
+
* If omitted, computed automatically from the gross via v0.1 contribution functions.
|
|
14
|
+
*/
|
|
15
|
+
mandatoryDeductions?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Total non-taxable allowances for the month (de minimis benefits within their caps,
|
|
18
|
+
* other tax-exempt allowances). Caller is responsible for capping per BIR rules β this
|
|
19
|
+
* function trusts the value as given.
|
|
20
|
+
*/
|
|
21
|
+
nonTaxableAllowances?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compute BIR withholding tax for a given **monthly taxable income**.
|
|
25
|
+
*
|
|
26
|
+
* Source: BIR RR 11-2018 monthly table (TRAIN Law, second phase of rate reductions
|
|
27
|
+
* effective 2023-01-01).
|
|
28
|
+
*
|
|
29
|
+
* **Important:** the input is *taxable income*, not gross compensation. To derive
|
|
30
|
+
* taxable income from gross, use {@link taxableIncomeMonthly}.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* withholdingTaxMonthly(25000);
|
|
34
|
+
* // { wt: 625, bracket: 2, marginalRate: 0.15 }
|
|
35
|
+
* // (15% Γ (25,000 β 20,833) = 625.05; rounded)
|
|
36
|
+
*/
|
|
37
|
+
export declare function withholdingTaxMonthly(taxableIncome: number, opts?: {
|
|
38
|
+
year?: number;
|
|
39
|
+
}): WithholdingTaxResult;
|
|
40
|
+
/**
|
|
41
|
+
* Derive monthly taxable income from gross compensation.
|
|
42
|
+
*
|
|
43
|
+
* Formula: `gross β mandatoryDeductions β nonTaxableAllowances`
|
|
44
|
+
*
|
|
45
|
+
* If `mandatoryDeductions` is omitted, it is auto-computed as the sum of SSS,
|
|
46
|
+
* PhilHealth, and Pag-IBIG employee shares for the given gross (using v0.1
|
|
47
|
+
* contribution functions).
|
|
48
|
+
*
|
|
49
|
+
* **Not handled here:** 13th-month + bonus β±90,000 annual exemption (that's an
|
|
50
|
+
* annual concern, not monthly). De minimis benefit caps are the caller's
|
|
51
|
+
* responsibility β pass only the *non-taxable portion* in `nonTaxableAllowances`.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* taxableIncomeMonthly(30000);
|
|
55
|
+
* // 27,550 (gross β auto-computed 2,450 mandatories β 0 allowances)
|
|
56
|
+
*
|
|
57
|
+
* taxableIncomeMonthly(50000, { nonTaxableAllowances: 2000 });
|
|
58
|
+
* // 50,000 β mandatories(50k) β 2,000
|
|
59
|
+
*/
|
|
60
|
+
export declare function taxableIncomeMonthly(gross: number, opts?: TaxableIncomeOptions): number;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import table from '../data/bir-wt-monthly-train-2023.json' with { type: 'json' };
|
|
2
|
+
import { sssContribution } from './sss.js';
|
|
3
|
+
import { philHealthContribution } from './philhealth.js';
|
|
4
|
+
import { pagIbigContribution } from './pagibig.js';
|
|
5
|
+
function round2(n) {
|
|
6
|
+
return Math.round(n * 100) / 100;
|
|
7
|
+
}
|
|
8
|
+
function assertYearSupported(year) {
|
|
9
|
+
if (year === undefined)
|
|
10
|
+
return;
|
|
11
|
+
if (!table._meta.applies_to_years.includes(year)) {
|
|
12
|
+
throw new RangeError(`withholding-tax: no BIR monthly WT table available for year ${year}. Available: ${table._meta.applies_to_years.join(', ')}. Pin to an older package version if needed.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute BIR withholding tax for a given **monthly taxable income**.
|
|
17
|
+
*
|
|
18
|
+
* Source: BIR RR 11-2018 monthly table (TRAIN Law, second phase of rate reductions
|
|
19
|
+
* effective 2023-01-01).
|
|
20
|
+
*
|
|
21
|
+
* **Important:** the input is *taxable income*, not gross compensation. To derive
|
|
22
|
+
* taxable income from gross, use {@link taxableIncomeMonthly}.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* withholdingTaxMonthly(25000);
|
|
26
|
+
* // { wt: 625, bracket: 2, marginalRate: 0.15 }
|
|
27
|
+
* // (15% Γ (25,000 β 20,833) = 625.05; rounded)
|
|
28
|
+
*/
|
|
29
|
+
export function withholdingTaxMonthly(taxableIncome, opts = {}) {
|
|
30
|
+
if (!Number.isFinite(taxableIncome) || taxableIncome < 0) {
|
|
31
|
+
throw new TypeError('withholdingTaxMonthly: taxableIncome must be a non-negative number');
|
|
32
|
+
}
|
|
33
|
+
assertYearSupported(opts.year);
|
|
34
|
+
const brackets = table.brackets;
|
|
35
|
+
let chosen = brackets[0];
|
|
36
|
+
for (const b of brackets) {
|
|
37
|
+
if (taxableIncome > b.over)
|
|
38
|
+
chosen = b;
|
|
39
|
+
else
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
const wt = round2(chosen.base + chosen.rate_over_min * (taxableIncome - chosen.over));
|
|
43
|
+
return {
|
|
44
|
+
wt,
|
|
45
|
+
bracket: chosen.bracket,
|
|
46
|
+
marginalRate: chosen.rate_over_min,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Derive monthly taxable income from gross compensation.
|
|
51
|
+
*
|
|
52
|
+
* Formula: `gross β mandatoryDeductions β nonTaxableAllowances`
|
|
53
|
+
*
|
|
54
|
+
* If `mandatoryDeductions` is omitted, it is auto-computed as the sum of SSS,
|
|
55
|
+
* PhilHealth, and Pag-IBIG employee shares for the given gross (using v0.1
|
|
56
|
+
* contribution functions).
|
|
57
|
+
*
|
|
58
|
+
* **Not handled here:** 13th-month + bonus β±90,000 annual exemption (that's an
|
|
59
|
+
* annual concern, not monthly). De minimis benefit caps are the caller's
|
|
60
|
+
* responsibility β pass only the *non-taxable portion* in `nonTaxableAllowances`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* taxableIncomeMonthly(30000);
|
|
64
|
+
* // 27,550 (gross β auto-computed 2,450 mandatories β 0 allowances)
|
|
65
|
+
*
|
|
66
|
+
* taxableIncomeMonthly(50000, { nonTaxableAllowances: 2000 });
|
|
67
|
+
* // 50,000 β mandatories(50k) β 2,000
|
|
68
|
+
*/
|
|
69
|
+
export function taxableIncomeMonthly(gross, opts = {}) {
|
|
70
|
+
if (!Number.isFinite(gross) || gross < 0) {
|
|
71
|
+
throw new TypeError('taxableIncomeMonthly: gross must be a non-negative number');
|
|
72
|
+
}
|
|
73
|
+
let mandatory = opts.mandatoryDeductions;
|
|
74
|
+
if (mandatory === undefined) {
|
|
75
|
+
const sss = sssContribution(gross, { year: opts.year });
|
|
76
|
+
const ph = philHealthContribution(gross, { year: opts.year });
|
|
77
|
+
const pi = pagIbigContribution(gross, { year: opts.year });
|
|
78
|
+
mandatory = sss.employeeShare + ph.employee + pi.employee;
|
|
79
|
+
}
|
|
80
|
+
if (!Number.isFinite(mandatory) || mandatory < 0) {
|
|
81
|
+
throw new TypeError('taxableIncomeMonthly: mandatoryDeductions must be a non-negative number');
|
|
82
|
+
}
|
|
83
|
+
const nta = opts.nonTaxableAllowances ?? 0;
|
|
84
|
+
if (!Number.isFinite(nta) || nta < 0) {
|
|
85
|
+
throw new TypeError('taxableIncomeMonthly: nonTaxableAllowances must be a non-negative number');
|
|
86
|
+
}
|
|
87
|
+
return round2(Math.max(0, gross - mandatory - nta));
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ph-dev-utils/payroll",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Filipino payroll calculators β SSS, PhilHealth, Pag-IBIG contributions; 13th month pay; net take-home. Versioned contribution tables per effective year.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Filipino payroll calculators β SSS, PhilHealth, Pag-IBIG contributions; 13th month pay; BIR monthly withholding tax (TRAIN); net take-home. Versioned contribution tables per effective year.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"sss",
|
|
31
31
|
"philhealth",
|
|
32
32
|
"pagibig",
|
|
33
|
+
"bir",
|
|
34
|
+
"withholding-tax",
|
|
35
|
+
"train-law",
|
|
33
36
|
"13th-month",
|
|
34
37
|
"contribution",
|
|
35
38
|
"calculator"
|