@mudraforge/statutory 2.12.1 → 2.14.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +19 -0
  3. package/okf/corporate_ma_restructuring/business_combinations_indas_103/ast.json +20 -0
  4. package/okf/corporate_ma_restructuring/impairment_of_assets_indas_36/ast.json +20 -0
  5. package/okf/corporate_ma_restructuring/loss_carryforward_sec79/ast.json +33 -0
  6. package/okf/corporate_ma_restructuring/share_based_payments_indas_102/ast.json +20 -0
  7. package/okf/corporate_ma_restructuring/slump_sale_50b/ast.json +21 -0
  8. package/okf/corporate_ma_restructuring/tax_clearance_sec281/ast.json +21 -0
  9. package/okf/direct_tax/corporate_tax_115baa_115jb/ast.json +23 -0
  10. package/okf/direct_tax/income_taxes_deferred_tax_indas_12/ast.json +20 -0
  11. package/okf/direct_tax/special_rates_non_filer_206ab/ast.json +26 -0
  12. package/okf/direct_tax/statutory_disallowance_43b/ast.json +35 -0
  13. package/okf/direct_tax/tcs_goods_206c_1h/ast.json +69 -0
  14. package/okf/direct_tax/tds_commission_194h/ast.json +60 -0
  15. package/okf/direct_tax/tds_contractor_194c/ast.json +71 -0
  16. package/okf/direct_tax/tds_ecommerce_194o/ast.json +48 -0
  17. package/okf/direct_tax/tds_goods_194q/ast.json +71 -0
  18. package/okf/direct_tax/tds_goods_393/ast.json +59 -0
  19. package/okf/direct_tax/tds_individual_contract_194m/ast.json +59 -0
  20. package/okf/direct_tax/tds_interest_194a/ast.json +59 -0
  21. package/okf/direct_tax/tds_perquisites_194r/ast.json +47 -0
  22. package/okf/direct_tax/tds_property_194ia/ast.json +70 -0
  23. package/okf/direct_tax/tds_rent_194i/ast.json +60 -0
  24. package/okf/direct_tax/tds_rent_individual_194ib/ast.json +59 -0
  25. package/okf/direct_tax/tds_services_194j/ast.json +60 -0
  26. package/okf/indirect_tax_gst/blocked_itc_17_5/ast.json +26 -0
  27. package/okf/indirect_tax_gst/common_itc_reversal_rule42_43/ast.json +26 -0
  28. package/okf/indirect_tax_gst/composite_mixed_supplies_sec8/ast.json +21 -0
  29. package/okf/indirect_tax_gst/composition_levy_sec10/ast.json +23 -0
  30. package/okf/indirect_tax_gst/credit_debit_notes_sec34/ast.json +21 -0
  31. package/okf/indirect_tax_gst/e_invoicing_rule48_4/ast.json +20 -0
  32. package/okf/indirect_tax_gst/export_services_lut_sec16/ast.json +20 -0
  33. package/okf/indirect_tax_gst/gst_tds_sec51/ast.json +23 -0
  34. package/okf/indirect_tax_gst/itc_eligibility_16_2/ast.json +20 -0
  35. package/okf/indirect_tax_gst/itc_reversal_rule37_37a/ast.json +45 -0
  36. package/okf/indirect_tax_gst/rcm_liability_sec9_3/ast.json +26 -0
  37. package/okf/international_tax/cross_border_remittance_195/ast.json +39 -0
  38. package/okf/international_tax/financial_instruments_ecl_indas_109/ast.json +24 -0
  39. package/okf/international_tax/foreign_exchange_indas_21_109/ast.json +20 -0
  40. package/okf/international_tax/foreign_tax_credit_irc_901_904/ast.json +20 -0
  41. package/okf/international_tax/transfer_pricing_alp_92c/ast.json +25 -0
  42. package/okf/manifest.json +49 -3
  43. package/okf/msme_commercial/delayed_payments_15_16/ast.json +38 -0
  44. package/okf/msme_commercial/enterprise_classification_sec7/ast.json +25 -0
  45. package/okf/msme_commercial/lease_accounting_indas_116/ast.json +20 -0
  46. package/okf/msme_commercial/mca_msme_1_filing/ast.json +20 -0
  47. package/okf/msme_commercial/provisions_contingencies_indas_37/ast.json +20 -0
  48. package/okf/msme_commercial/revenue_recognition_indas_115/ast.json +20 -0
  49. package/okf/msme_commercial/statutory_disclosures_sec22/ast.json +21 -0
  50. package/package.json +16 -4
  51. package/src/calculators.js +139 -3
  52. package/src/constants.js +1 -1
  53. package/src/index.js +16 -2
  54. package/src/payroll.js +506 -0
  55. package/src/usvm/evaluation_vm.js +504 -0
  56. package/src/usvm/index.js +65 -0
  57. package/src/usvm/math_rational.js +165 -0
  58. package/src/usvm/rules/sec_115baa_115jb_mat.json +23 -0
  59. package/src/usvm/rules/sec_192_salary_tds.json +23 -0
  60. package/src/usvm/rules/sec_194a_interest.json +59 -0
  61. package/src/usvm/rules/sec_194c_contractors.json +71 -0
  62. package/src/usvm/rules/sec_194h_commission.json +60 -0
  63. package/src/usvm/rules/sec_194i_rent.json +60 -0
  64. package/src/usvm/rules/sec_194ia_property.json +70 -0
  65. package/src/usvm/rules/sec_194ib_rent_ind.json +59 -0
  66. package/src/usvm/rules/sec_194j_services.json +60 -0
  67. package/src/usvm/rules/sec_194m_contract_ind.json +59 -0
  68. package/src/usvm/rules/sec_194o_ecommerce.json +48 -0
  69. package/src/usvm/rules/sec_194q_goods.json +71 -0
  70. package/src/usvm/rules/sec_194r_perquisites.json +47 -0
  71. package/src/usvm/rules/sec_195_cross_border.json +39 -0
  72. package/src/usvm/rules/sec_206ab_non_filer.json +26 -0
  73. package/src/usvm/rules/sec_206c1h_tcs.json +69 -0
  74. package/src/usvm/rules/sec_281_tax_clearance.json +21 -0
  75. package/src/usvm/rules/sec_393_goods.json +59 -0
  76. package/src/usvm/rules/sec_43bh_disallowance.json +35 -0
  77. package/src/usvm/rules/sec_50b_slump_sale.json +21 -0
  78. package/src/usvm/rules/sec_79_loss_continuity.json +33 -0
  79. package/src/usvm/rules/sec_92c_transfer_pricing.json +25 -0
  80. package/src/usvm/rules/sec_ftc_dtaa_relief.json +20 -0
  81. package/src/usvm/rules/sec_gst_blocked_itc_17_5.json +26 -0
  82. package/src/usvm/rules/sec_gst_composite_mixed_8.json +21 -0
  83. package/src/usvm/rules/sec_gst_composition_levy_10.json +23 -0
  84. package/src/usvm/rules/sec_gst_credit_debit_notes_34.json +21 -0
  85. package/src/usvm/rules/sec_gst_einvoicing_rule48_4.json +20 -0
  86. package/src/usvm/rules/sec_gst_ewaybill_rule138.json +21 -0
  87. package/src/usvm/rules/sec_gst_export_lut_16.json +20 -0
  88. package/src/usvm/rules/sec_gst_itc_eligibility_16_2.json +20 -0
  89. package/src/usvm/rules/sec_gst_itc_reversal_rule37_37a.json +45 -0
  90. package/src/usvm/rules/sec_gst_rcm_liability_9_3.json +26 -0
  91. package/src/usvm/rules/sec_gst_rule42_43_apportionment.json +26 -0
  92. package/src/usvm/rules/sec_gst_tds_sec51.json +23 -0
  93. package/src/usvm/rules/sec_indas102_esop.json +20 -0
  94. package/src/usvm/rules/sec_indas103_mna.json +20 -0
  95. package/src/usvm/rules/sec_indas109_ecl.json +24 -0
  96. package/src/usvm/rules/sec_indas115_revenue.json +20 -0
  97. package/src/usvm/rules/sec_indas116_leases.json +20 -0
  98. package/src/usvm/rules/sec_indas12_deferred_tax.json +20 -0
  99. package/src/usvm/rules/sec_indas21_fx_reval.json +20 -0
  100. package/src/usvm/rules/sec_indas36_impairment.json +20 -0
  101. package/src/usvm/rules/sec_indas37_provisions.json +20 -0
  102. package/src/usvm/rules/sec_msme_classification_sec7.json +25 -0
  103. package/src/usvm/rules/sec_msme_delayed_payments_15_16.json +38 -0
  104. package/src/usvm/rules/sec_msme_disclosures_sec22.json +21 -0
  105. package/src/usvm/rules/sec_msme_mca_form_1.json +20 -0
  106. package/src/usvm/schemas/statutory_ast_schema.js +203 -0
  107. package/src/usvm/usvm_domain_evaluators.js +1021 -0
  108. package/types/index.d.ts +134 -0
  109. package/conformance/README.md +0 -54
  110. package/conformance/fixtures/invalid_malformed_temporal_dates.json +0 -10
  111. package/conformance/fixtures/invalid_missing_effective_period.json +0 -6
  112. package/conformance/fixtures/invalid_missing_section_code.json +0 -9
  113. package/conformance/fixtures/valid_circular_frontmatter.md +0 -16
  114. package/conformance/fixtures/valid_full_section.json +0 -35
  115. package/conformance/fixtures/valid_minimal_section.json +0 -11
  116. package/conformance/test_conformance.js +0 -129
  117. package/docs/ACCOUNTING_ASSURANCE_AUDIT_DOSSIER.md +0 -276
  118. package/docs/ADESHA_ASSURANCE_OVERVIEW.md +0 -166
  119. package/docs/ARCHITECTURE.md +0 -750
  120. package/docs/ARCHITECTURE_DECISIONS.md +0 -138
  121. package/docs/CASE_01_FORM_TEXT.txt +0 -48
  122. package/docs/CATALOG.md +0 -1901
  123. package/docs/CA_PILOT_BENCHMARK_CASES.md +0 -282
  124. package/docs/CORPUS_QUALITY.md +0 -106
  125. package/docs/DATA_LICENSING.md +0 -73
  126. package/docs/DOMAIN_BREADTH_AND_DEPTH_COMPARISON.md +0 -209
  127. package/docs/ENTERPRISE_GOVERNANCE.md +0 -159
  128. package/docs/OKF_MAINTENANCE_SOP.md +0 -286
  129. package/docs/PILOT_EVALUATION_GUIDE.md +0 -214
  130. package/docs/PROVENANCE.md +0 -33
  131. package/docs/VERIFICATION.md +0 -106
  132. package/examples/README.md +0 -56
  133. package/examples/invoice_deduction_trace.json +0 -149
  134. package/examples/simulate_end_to_end_trace.js +0 -106
package/src/payroll.js ADDED
@@ -0,0 +1,506 @@
1
+ /**
2
+ * DETERMINISTIC STATUTORY PAYROLL & SALARY WITHHOLDING CALCULATORS
3
+ * ==============================================================================
4
+ * Implements pure mathematical functions for Indian payroll compliance:
5
+ * 1. Salary TDS under Income-tax Act, 1961 Section 192 & Income-tax Act, 2025 Section 392(1)
6
+ * - Default Section 115BAC(1A) New Tax Regime with ₹75,000 standard deduction
7
+ * - Old Tax Regime opt-out with ₹50,000 standard deduction and Form 12BB deductions
8
+ * - Section 87A rebate and 4% Health & Education Cess
9
+ * 2. Employees' Provident Fund (EPF & EPS) under EPF Act, 1952
10
+ * - ₹15,000 standard wage ceiling vs. Paragraph 26(6) joint higher wage declarations
11
+ * - Age 58 EPS cessation rule
12
+ * 3. Employees' State Insurance (ESI) under ESI Act, 1948
13
+ * - ₹21,000 gross wage threshold & ₹176/day exempt employee floor
14
+ * 4. Professional Tax (PT) across Indian state statutory slabs
15
+ *
16
+ * Statutory Invariant: Every calculator strictly returns isAuthorizedToDisburse: false.
17
+ * Zero external npm dependencies. Integer paise arithmetic to prevent float drift.
18
+ * ==============================================================================
19
+ */
20
+
21
+ import { roundToPaisa, roundSection288B } from './calculators.js';
22
+
23
+ export const TAX_REGIMES = Object.freeze({
24
+ NEW: 'NEW',
25
+ OLD: 'OLD'
26
+ });
27
+
28
+ export const NEW_REGIME_SLABS_AY2025_26 = Object.freeze([
29
+ { min: 0, max: 300000, ratePercent: 0 },
30
+ { min: 300000, max: 700000, ratePercent: 5 },
31
+ { min: 700000, max: 1000000, ratePercent: 10 },
32
+ { min: 1000000, max: 1200000, ratePercent: 15 },
33
+ { min: 1200000, max: 1500000, ratePercent: 20 },
34
+ { min: 1500000, max: Infinity, ratePercent: 30 }
35
+ ]);
36
+
37
+ export const OLD_REGIME_SLABS = Object.freeze([
38
+ { min: 0, max: 250000, ratePercent: 0 },
39
+ { min: 250000, max: 500000, ratePercent: 5 },
40
+ { min: 500000, max: 1000000, ratePercent: 20 },
41
+ { min: 1000000, max: Infinity, ratePercent: 30 }
42
+ ]);
43
+
44
+ export const EPF_STATUTORY_CONSTANTS = Object.freeze({
45
+ STANDARD_WAGE_CEILING_INR: 15000,
46
+ EMPLOYEE_RATE_PERCENT: 12.0,
47
+ EMPLOYER_TOTAL_RATE_PERCENT: 12.0,
48
+ EPS_RATE_PERCENT: 8.33,
49
+ EPS_MAX_MONTHLY_CAP_INR: 1250,
50
+ EPS_SUPERANNUATION_AGE_YEARS: 58
51
+ });
52
+
53
+ export const ESI_STATUTORY_CONSTANTS = Object.freeze({
54
+ GROSS_WAGE_CEILING_INR: 21000,
55
+ EMPLOYEE_RATE_PERCENT: 0.75,
56
+ EMPLOYER_RATE_PERCENT: 3.25,
57
+ DAILY_EXEMPT_WAGE_FLOOR_INR: 176
58
+ });
59
+
60
+ /**
61
+ * Evaluates annual and monthly Salary TDS under Section 192 (1961 Act)
62
+ * or Section 392(1) (2025 Act).
63
+ */
64
+ export function evaluateSalaryTds({
65
+ annualGrossSalaryInr,
66
+ taxRegime = TAX_REGIMES.NEW,
67
+ form12BBClaims = {},
68
+ standardDeductionOverride = null,
69
+ monthsElapsedInFy = 0,
70
+ priorTdsDeductedInr = 0,
71
+ transactionDate = null,
72
+ isSeniorCitizen = false,
73
+ isSuperSeniorCitizen = false
74
+ } = {}) {
75
+ const gross = Math.max(0, Number(annualGrossSalaryInr) || 0);
76
+ const dateStr = transactionDate ? String(transactionDate).slice(0, 10) : new Date().toISOString().slice(0, 10);
77
+ const isAct2025 = Boolean(dateStr >= '2026-04-01');
78
+
79
+ const normalizedRegime = String(taxRegime || '').toUpperCase().trim() === TAX_REGIMES.OLD
80
+ ? TAX_REGIMES.OLD
81
+ : TAX_REGIMES.NEW;
82
+
83
+ // Standard Deduction: ₹75,000 for New Regime (Finance (No. 2) Act, 2024), ₹50,000 for Old Regime
84
+ let standardDeductionInr = standardDeductionOverride !== null && standardDeductionOverride !== undefined
85
+ ? Math.max(0, Number(standardDeductionOverride) || 0)
86
+ : (normalizedRegime === TAX_REGIMES.NEW ? 75000 : 50000);
87
+
88
+ let allowedDeductionsInr = standardDeductionInr;
89
+ const deductionsBreakdown = {
90
+ standardDeductionInr
91
+ };
92
+
93
+ if (normalizedRegime === TAX_REGIMES.OLD) {
94
+ // Section 80C capped at ₹1,50,000
95
+ const claim80C = Math.min(150000, Math.max(0, Number(form12BBClaims.section80CInr) || 0));
96
+ // Section 80D (Health Insurance) capped at ₹25,000 (or ₹50,000 for senior citizen)
97
+ const cap80D = isSeniorCitizen ? 50000 : 25000;
98
+ const claim80D = Math.min(cap80D, Math.max(0, Number(form12BBClaims.section80DInr) || 0));
99
+ // Section 24(b) Home loan interest capped at ₹2,00,000
100
+ const claimHomeLoan = Math.min(200000, Math.max(0, Number(form12BBClaims.homeLoanInterestSec24Inr) || 0));
101
+ // HRA Exemption under Section 10(13A)
102
+ const claimHra = Math.max(0, Number(form12BBClaims.hraExemptionInr) || 0);
103
+ // LTA Exemption under Section 10(5)
104
+ const claimLta = Math.max(0, Number(form12BBClaims.ltaExemptionInr) || 0);
105
+ // Other eligible deductions
106
+ const claimOther = Math.max(0, Number(form12BBClaims.otherDeductionsInr) || 0);
107
+
108
+ deductionsBreakdown.section80CInr = claim80C;
109
+ deductionsBreakdown.section80DInr = claim80D;
110
+ deductionsBreakdown.homeLoanInterestSec24Inr = claimHomeLoan;
111
+ deductionsBreakdown.hraExemptionInr = claimHra;
112
+ deductionsBreakdown.ltaExemptionInr = claimLta;
113
+ deductionsBreakdown.otherDeductionsInr = claimOther;
114
+
115
+ allowedDeductionsInr += (claim80C + claim80D + claimHomeLoan + claimHra + claimLta + claimOther);
116
+ }
117
+
118
+ const netTaxableIncomeInr = Math.max(0, gross - allowedDeductionsInr);
119
+
120
+ // Compute Tax on Slabs
121
+ let taxBeforeRebate = 0;
122
+ const slabs = normalizedRegime === TAX_REGIMES.NEW
123
+ ? NEW_REGIME_SLABS_AY2025_26
124
+ : OLD_REGIME_SLABS;
125
+
126
+ let previousMax = 0;
127
+ if (normalizedRegime === TAX_REGIMES.OLD && isSuperSeniorCitizen) {
128
+ previousMax = 500000;
129
+ } else if (normalizedRegime === TAX_REGIMES.OLD && isSeniorCitizen) {
130
+ previousMax = 300000;
131
+ }
132
+
133
+ for (const slab of slabs) {
134
+ const slabFloor = Math.max(slab.min, previousMax);
135
+ if (netTaxableIncomeInr > slabFloor) {
136
+ const taxableInThisSlab = Math.min(netTaxableIncomeInr, slab.max) - slabFloor;
137
+ if (taxableInThisSlab > 0 && slab.ratePercent > 0) {
138
+ taxBeforeRebate += taxableInThisSlab * (slab.ratePercent / 100);
139
+ }
140
+ }
141
+ }
142
+
143
+ // Section 87A Rebate
144
+ // New Regime: Taxable income up to ₹7,00,000 gets full rebate (₹25,000 max rebate)
145
+ // Old Regime: Taxable income up to ₹5,00,000 gets full rebate (₹12,500 max rebate)
146
+ let section87ARebateInr = 0;
147
+ if (normalizedRegime === TAX_REGIMES.NEW && netTaxableIncomeInr <= 700000) {
148
+ section87ARebateInr = Math.min(taxBeforeRebate, 25000);
149
+ } else if (normalizedRegime === TAX_REGIMES.OLD && netTaxableIncomeInr <= 500000) {
150
+ section87ARebateInr = Math.min(taxBeforeRebate, 12500);
151
+ }
152
+
153
+ const taxAfterRebate = Math.max(0, taxBeforeRebate - section87ARebateInr);
154
+
155
+ // 4% Health and Education Cess
156
+ const cessInr = taxAfterRebate > 0 ? taxAfterRebate * 0.04 : 0;
157
+ const totalAnnualTdsInr = roundSection288B(taxAfterRebate + cessInr);
158
+
159
+ // Monthly Withholding Amortization
160
+ const validElapsed = Math.min(11, Math.max(0, Number(monthsElapsedInFy) || 0));
161
+ const remainingMonths = Math.max(1, 12 - validElapsed);
162
+ const priorDeducted = Math.max(0, Number(priorTdsDeductedInr) || 0);
163
+ const unrecoveredTax = Math.max(0, totalAnnualTdsInr - priorDeducted);
164
+ const monthlyTdsDeductionInr = roundSection288B(unrecoveredTax / remainingMonths);
165
+
166
+ const governingSection = isAct2025
167
+ ? 'Income-tax Act, 2025 Section 392(1)'
168
+ : (normalizedRegime === TAX_REGIMES.NEW
169
+ ? 'Income-tax Act, 1961 Section 192 read with Section 115BAC'
170
+ : 'Income-tax Act, 1961 Section 192 (Old Tax Regime Opt-Out)');
171
+
172
+ return {
173
+ status: 'SUCCESS',
174
+ governingSection,
175
+ isAct2025,
176
+ taxRegime: normalizedRegime,
177
+ annualGrossSalaryInr: roundToPaisa(gross),
178
+ totalDeductionsAllowedInr: roundToPaisa(allowedDeductionsInr),
179
+ deductionsBreakdown,
180
+ netTaxableIncomeInr: roundToPaisa(netTaxableIncomeInr),
181
+ taxBeforeRebateInr: roundToPaisa(taxBeforeRebate),
182
+ section87ARebateInr: roundToPaisa(section87ARebateInr),
183
+ taxAfterRebateInr: roundToPaisa(taxAfterRebate),
184
+ healthAndEducationCessInr: roundToPaisa(cessInr),
185
+ totalAnnualTdsInr,
186
+ priorTdsDeductedInr: roundToPaisa(priorDeducted),
187
+ remainingMonthsInFy: remainingMonths,
188
+ monthlyTdsDeductionInr,
189
+ averageTaxRatePercent: gross > 0 ? roundToPaisa((totalAnnualTdsInr / gross) * 100) : 0,
190
+ isAuthorizedToDisburse: false
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Evaluates Employees' Provident Fund (EPF) and Employees' Pension Scheme (EPS)
196
+ * contributions under the Employees' Provident Funds and Miscellaneous Provisions Act, 1952.
197
+ */
198
+ export function evaluateEpfContribution({
199
+ monthlyBasicAndDaInr,
200
+ epfOnActualWages = false,
201
+ employeeAgeYears = 30,
202
+ isInternationalWorker = false,
203
+ optedOutOfEpf = false
204
+ } = {}) {
205
+ if (optedOutOfEpf) {
206
+ return {
207
+ status: 'OPTED_OUT',
208
+ statutoryWageCeilingInr: EPF_STATUTORY_CONSTANTS.STANDARD_WAGE_CEILING_INR,
209
+ effectiveEpfWageInr: 0,
210
+ employeeContributionInr: 0,
211
+ employerEpsContributionInr: 0,
212
+ employerEpfContributionInr: 0,
213
+ totalEmployerContributionInr: 0,
214
+ totalEpfRemittanceInr: 0,
215
+ isAuthorizedToDisburse: false
216
+ };
217
+ }
218
+
219
+ const wage = Math.max(0, Number(monthlyBasicAndDaInr) || 0);
220
+ const age = Number(employeeAgeYears) || 30;
221
+
222
+ // Wage ceiling applies unless employee has joint declaration under Para 26(6) or is International Worker
223
+ const isCeilingApplicable = !epfOnActualWages && !isInternationalWorker;
224
+ const effectiveEpfWageInr = isCeilingApplicable
225
+ ? Math.min(wage, EPF_STATUTORY_CONSTANTS.STANDARD_WAGE_CEILING_INR)
226
+ : wage;
227
+
228
+ // Employee Share: Exactly 12% of effective EPF wage
229
+ const employeeContributionInr = roundToPaisa(effectiveEpfWageInr * (EPF_STATUTORY_CONSTANTS.EMPLOYEE_RATE_PERCENT / 100));
230
+
231
+ // Employer Share: Exactly 12% total, split into EPS (8.33%) and EPF (3.67%)
232
+ let employerEpsContributionInr = 0;
233
+ let employerEpfContributionInr = 0;
234
+
235
+ if (age >= EPF_STATUTORY_CONSTANTS.EPS_SUPERANNUATION_AGE_YEARS) {
236
+ // EPS membership ceases at age 58; entire 12% employer share goes to EPF
237
+ employerEpsContributionInr = 0;
238
+ employerEpfContributionInr = employeeContributionInr;
239
+ } else {
240
+ // EPS is calculated at 8.33% of capped wage (max ₹1,250/month)
241
+ const epsWage = Math.min(effectiveEpfWageInr, EPF_STATUTORY_CONSTANTS.STANDARD_WAGE_CEILING_INR);
242
+ // Under EPFO Rule 38(1), EPS contribution rounds to nearest integer rupee (e.g. 15,000 * 8.33% = 1,249.50 -> 1,250)
243
+ employerEpsContributionInr = Math.min(
244
+ EPF_STATUTORY_CONSTANTS.EPS_MAX_MONTHLY_CAP_INR,
245
+ Math.round(epsWage * (EPF_STATUTORY_CONSTANTS.EPS_RATE_PERCENT / 100))
246
+ );
247
+ employerEpfContributionInr = roundToPaisa(employeeContributionInr - employerEpsContributionInr);
248
+ }
249
+
250
+ const totalEmployerContributionInr = roundToPaisa(employerEpsContributionInr + employerEpfContributionInr);
251
+ const totalEpfRemittanceInr = roundToPaisa(employeeContributionInr + totalEmployerContributionInr);
252
+
253
+ return {
254
+ status: 'SUCCESS',
255
+ governingStatute: "Employees' Provident Funds and Miscellaneous Provisions Act, 1952",
256
+ statutoryWageCeilingInr: isCeilingApplicable ? EPF_STATUTORY_CONSTANTS.STANDARD_WAGE_CEILING_INR : null,
257
+ epfOnActualWages: Boolean(epfOnActualWages || isInternationalWorker),
258
+ effectiveEpfWageInr: roundToPaisa(effectiveEpfWageInr),
259
+ employeeContributionInr,
260
+ employerEpsContributionInr,
261
+ employerEpfContributionInr,
262
+ totalEmployerContributionInr,
263
+ totalEpfRemittanceInr,
264
+ isSuperannuationAge: age >= EPF_STATUTORY_CONSTANTS.EPS_SUPERANNUATION_AGE_YEARS,
265
+ isAuthorizedToDisburse: false
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Evaluates Employees' State Insurance (ESI) contributions under the ESI Act, 1948.
271
+ */
272
+ export function evaluateEsiContribution({
273
+ monthlyGrossSalaryInr,
274
+ workingDaysInMonth = 26,
275
+ optedOutOfEsi = false
276
+ } = {}) {
277
+ const gross = Math.max(0, Number(monthlyGrossSalaryInr) || 0);
278
+
279
+ if (optedOutOfEsi || gross > ESI_STATUTORY_CONSTANTS.GROSS_WAGE_CEILING_INR) {
280
+ return {
281
+ status: gross > ESI_STATUTORY_CONSTANTS.GROSS_WAGE_CEILING_INR ? 'EXCEEDS_WAGE_CEILING_EXEMPT' : 'OPTED_OUT',
282
+ isCovered: false,
283
+ monthlyGrossSalaryInr: roundToPaisa(gross),
284
+ wageCeilingInr: ESI_STATUTORY_CONSTANTS.GROSS_WAGE_CEILING_INR,
285
+ employeeContributionInr: 0,
286
+ employerContributionInr: 0,
287
+ totalEsiRemittanceInr: 0,
288
+ isAuthorizedToDisburse: false
289
+ };
290
+ }
291
+
292
+ // Exempt Employee Check: Daily average wage <= ₹176 pays 0% employee share
293
+ const days = Math.max(1, Number(workingDaysInMonth) || 26);
294
+ const dailyAverageWage = gross / days;
295
+ const isExemptEmployee = dailyAverageWage <= ESI_STATUTORY_CONSTANTS.DAILY_EXEMPT_WAGE_FLOOR_INR;
296
+
297
+ const employeeRate = isExemptEmployee ? 0 : (ESI_STATUTORY_CONSTANTS.EMPLOYEE_RATE_PERCENT / 100);
298
+ const employerRate = ESI_STATUTORY_CONSTANTS.EMPLOYER_RATE_PERCENT / 100;
299
+
300
+ const employeeContributionInr = roundToPaisa(gross * employeeRate);
301
+ const employerContributionInr = roundToPaisa(gross * employerRate);
302
+ const totalEsiRemittanceInr = roundToPaisa(employeeContributionInr + employerContributionInr);
303
+
304
+ return {
305
+ status: 'SUCCESS',
306
+ governingStatute: "Employees' State Insurance Act, 1948",
307
+ isCovered: true,
308
+ isExemptEmployee,
309
+ monthlyGrossSalaryInr: roundToPaisa(gross),
310
+ employeeContributionInr,
311
+ employerContributionInr,
312
+ totalEsiRemittanceInr,
313
+ isAuthorizedToDisburse: false
314
+ };
315
+ }
316
+
317
+ /**
318
+ * Evaluates State Professional Tax (PT) across major Indian jurisdictions.
319
+ */
320
+ export function evaluateProfessionalTax({
321
+ monthlyGrossSalaryInr,
322
+ stateCode = '27', // Default Maharashtra ('27')
323
+ salaryMonth = 1, // 1-12 (Month 2 = February has special slab in MH)
324
+ gender = 'MALE'
325
+ } = {}) {
326
+ const gross = Math.max(0, Number(monthlyGrossSalaryInr) || 0);
327
+ const cleanState = String(stateCode || '27').trim().toUpperCase();
328
+ const cleanGender = String(gender || 'MALE').trim().toUpperCase();
329
+ const month = Number(salaryMonth) || 1;
330
+
331
+ let ptInr = 0;
332
+ let stateName = 'Other';
333
+
334
+ // Maharashtra (27 / MH)
335
+ if (cleanState === '27' || cleanState === 'MH' || cleanState === 'MAHARASHTRA') {
336
+ stateName = 'Maharashtra';
337
+ // Women earning <= ₹25,000 are exempt in Maharashtra w.e.f. 2023 amendment
338
+ if (cleanGender === 'FEMALE' && gross <= 25000) {
339
+ ptInr = 0;
340
+ } else if (gross <= 7500) {
341
+ ptInr = 0;
342
+ } else if (gross <= 10000) {
343
+ ptInr = 175;
344
+ } else {
345
+ // ₹200 for 11 months, ₹300 in February
346
+ ptInr = (month === 2) ? 300 : 200;
347
+ }
348
+ }
349
+ // Karnataka (29 / KA)
350
+ else if (cleanState === '29' || cleanState === 'KA' || cleanState === 'KARNATAKA') {
351
+ stateName = 'Karnataka';
352
+ // Gross > ₹25,000 pays ₹200/month (w.e.f. 2023 amendment)
353
+ ptInr = gross > 25000 ? 200 : 0;
354
+ }
355
+ // West Bengal (19 / WB)
356
+ else if (cleanState === '19' || cleanState === 'WB' || cleanState === 'WEST BENGAL') {
357
+ stateName = 'West Bengal';
358
+ if (gross <= 10000) ptInr = 0;
359
+ else if (gross <= 15000) ptInr = 110;
360
+ else if (gross <= 20000) ptInr = 130;
361
+ else if (gross <= 40000) ptInr = 150;
362
+ else ptInr = 200;
363
+ }
364
+ // Gujarat (24 / GJ)
365
+ else if (cleanState === '24' || cleanState === 'GJ' || cleanState === 'GUJARAT') {
366
+ stateName = 'Gujarat';
367
+ if (gross <= 12000) ptInr = 0;
368
+ else ptInr = 200;
369
+ }
370
+ // Telangana (36 / TS) & Andhra Pradesh (37 / AP)
371
+ else if (cleanState === '36' || cleanState === 'TS' || cleanState === '37' || cleanState === 'AP') {
372
+ stateName = 'Telangana / Andhra Pradesh';
373
+ if (gross <= 15000) ptInr = 0;
374
+ else if (gross <= 20000) ptInr = 150;
375
+ else ptInr = 200;
376
+ }
377
+ // Delhi (07), Haryana (06), UP (09) do not levy employee PT
378
+ else {
379
+ stateName = 'Exempt / No PT Jurisdiction';
380
+ ptInr = 0;
381
+ }
382
+
383
+ return {
384
+ status: 'SUCCESS',
385
+ stateName,
386
+ stateCode: cleanState,
387
+ monthlyGrossSalaryInr: roundToPaisa(gross),
388
+ salaryMonth: month,
389
+ professionalTaxInr: ptInr,
390
+ isAuthorizedToDisburse: false
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Composite Line-Item Evaluator for an Individual Employee
396
+ */
397
+ export function evaluateEmployeeSalaryPackage({
398
+ employeeId,
399
+ employeeName,
400
+ pan,
401
+ bankAccountNumber,
402
+ bankIfscCode,
403
+ monthlyGrossSalaryInr,
404
+ monthlyBasicAndDaInr = null,
405
+ annualGrossSalaryInr = null,
406
+ taxRegime = TAX_REGIMES.NEW,
407
+ form12BBClaims = {},
408
+ epfOnActualWages = false,
409
+ employeeAgeYears = 30,
410
+ stateCode = '27',
411
+ salaryMonth = 1,
412
+ gender = 'MALE',
413
+ otherDeductionsInr = 0,
414
+ transactionDate = null
415
+ } = {}) {
416
+ const grossMonthly = Math.max(0, Number(monthlyGrossSalaryInr) || 0);
417
+ const basicMonthly = monthlyBasicAndDaInr !== null && monthlyBasicAndDaInr !== undefined
418
+ ? Math.max(0, Number(monthlyBasicAndDaInr) || 0)
419
+ : roundToPaisa(grossMonthly * 0.50); // Default standard: Basic is 50% of gross
420
+ const grossAnnual = annualGrossSalaryInr !== null && annualGrossSalaryInr !== undefined
421
+ ? Math.max(0, Number(annualGrossSalaryInr) || 0)
422
+ : roundToPaisa(grossMonthly * 12);
423
+
424
+ // 1. Salary TDS
425
+ const tdsResult = evaluateSalaryTds({
426
+ annualGrossSalaryInr: grossAnnual,
427
+ taxRegime,
428
+ form12BBClaims,
429
+ transactionDate
430
+ });
431
+
432
+ // 2. EPF
433
+ const epfResult = evaluateEpfContribution({
434
+ monthlyBasicAndDaInr: basicMonthly,
435
+ epfOnActualWages,
436
+ employeeAgeYears
437
+ });
438
+
439
+ // 3. ESI
440
+ const esiResult = evaluateEsiContribution({
441
+ monthlyGrossSalaryInr: grossMonthly
442
+ });
443
+
444
+ // 4. Professional Tax
445
+ const ptResult = evaluateProfessionalTax({
446
+ monthlyGrossSalaryInr: grossMonthly,
447
+ stateCode,
448
+ salaryMonth,
449
+ gender
450
+ });
451
+
452
+ const otherDeductions = Math.max(0, Number(otherDeductionsInr) || 0);
453
+
454
+ const totalEmployeeDeductionsInr = roundToPaisa(
455
+ tdsResult.monthlyTdsDeductionInr +
456
+ epfResult.employeeContributionInr +
457
+ esiResult.employeeContributionInr +
458
+ ptResult.professionalTaxInr +
459
+ otherDeductions
460
+ );
461
+
462
+ const netSalaryInr = roundToPaisa(Math.max(0, grossMonthly - totalEmployeeDeductionsInr));
463
+
464
+ const totalEmployerContributionsInr = roundToPaisa(
465
+ epfResult.totalEmployerContributionInr +
466
+ esiResult.employerContributionInr
467
+ );
468
+
469
+ const totalCostToCompanyInr = roundToPaisa(grossMonthly + totalEmployerContributionsInr);
470
+
471
+ return {
472
+ status: 'SUCCESS',
473
+ employeeId: String(employeeId || '').trim(),
474
+ employeeName: String(employeeName || '').trim(),
475
+ pan: String(pan || '').toUpperCase().trim(),
476
+ bankAccountNumber: String(bankAccountNumber || '').trim(),
477
+ bankIfscCode: String(bankIfscCode || '').toUpperCase().trim(),
478
+ monthlyGrossSalaryInr: grossMonthly,
479
+ monthlyBasicAndDaInr: basicMonthly,
480
+ annualGrossSalaryInr: grossAnnual,
481
+ withholdings: {
482
+ salaryTdsMonthlyInr: tdsResult.monthlyTdsDeductionInr,
483
+ epfEmployeeInr: epfResult.employeeContributionInr,
484
+ esiEmployeeInr: esiResult.employeeContributionInr,
485
+ professionalTaxInr: ptResult.professionalTaxInr,
486
+ otherDeductionsInr: otherDeductions,
487
+ totalEmployeeDeductionsInr
488
+ },
489
+ employerContributions: {
490
+ epfEmployerTotalInr: epfResult.totalEmployerContributionInr,
491
+ epfEmployerShareInr: epfResult.employerEpfContributionInr,
492
+ epsEmployerShareInr: epfResult.employerEpsContributionInr,
493
+ esiEmployerInr: esiResult.employerContributionInr,
494
+ totalEmployerContributionsInr
495
+ },
496
+ netSalaryInr,
497
+ totalCostToCompanyInr,
498
+ statutoryDetails: {
499
+ tds: tdsResult,
500
+ epf: epfResult,
501
+ esi: esiResult,
502
+ pt: ptResult
503
+ },
504
+ isAuthorizedToDisburse: false
505
+ };
506
+ }