@mudraforge/statutory 2.11.0 → 2.13.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/CHANGELOG.md +172 -1
- package/CONTRIBUTING.md +2 -2
- package/README.md +14 -1
- package/SECURITY.md +2 -2
- package/SKILL.md +1 -1
- package/bin/cli.js +288 -27
- package/docs/ACCOUNTING_ASSURANCE_AUDIT_DOSSIER.md +276 -0
- package/docs/ARCHITECTURE.md +76 -53
- package/docs/ARCHITECTURE_DECISIONS.md +22 -2
- package/docs/CASE_01_FORM_TEXT.txt +48 -0
- package/docs/CATALOG.md +405 -39
- package/docs/CA_PILOT_BENCHMARK_CASES.md +282 -0
- package/docs/CORPUS_QUALITY.md +31 -22
- package/docs/DOMAIN_BREADTH_AND_DEPTH_COMPARISON.md +5 -5
- package/docs/ENTERPRISE_GOVERNANCE.md +2 -1
- package/docs/OKF_MAINTENANCE_SOP.md +5 -5
- package/docs/PILOT_EVALUATION_GUIDE.md +5 -4
- package/docs/PROVENANCE.md +6 -6
- package/docs/VERIFICATION.md +6 -6
- package/okf/corporate_ma_restructuring/business_combinations_indas_103/ast.json +20 -0
- package/okf/corporate_ma_restructuring/business_combinations_indas_103/circulars/mca_notification_indas_103_business_combinations.md +41 -0
- package/okf/corporate_ma_restructuring/business_combinations_indas_103/forms/purchase_price_allocation_schedule.json +110 -0
- package/okf/corporate_ma_restructuring/business_combinations_indas_103/section.json +65 -0
- package/okf/corporate_ma_restructuring/impairment_of_assets_indas_36/ast.json +20 -0
- package/okf/corporate_ma_restructuring/impairment_of_assets_indas_36/circulars/mca_notification_indas_36_impairment.md +45 -0
- package/okf/corporate_ma_restructuring/impairment_of_assets_indas_36/forms/cgu_impairment_testing_register.json +53 -0
- package/okf/corporate_ma_restructuring/impairment_of_assets_indas_36/section.json +61 -0
- package/okf/corporate_ma_restructuring/loss_carryforward_sec79/ast.json +33 -0
- package/okf/corporate_ma_restructuring/share_based_payments_indas_102/ast.json +20 -0
- package/okf/corporate_ma_restructuring/share_based_payments_indas_102/circulars/icai_guidance_share_based_payments.md +37 -0
- package/okf/corporate_ma_restructuring/share_based_payments_indas_102/forms/esop_graded_vesting_amortization_schedule.json +49 -0
- package/okf/corporate_ma_restructuring/share_based_payments_indas_102/section.json +60 -0
- package/okf/corporate_ma_restructuring/slump_sale_50b/ast.json +21 -0
- package/okf/corporate_ma_restructuring/tax_clearance_sec281/ast.json +21 -0
- package/okf/direct_tax/corporate_tax_115baa_115jb/ast.json +23 -0
- package/okf/direct_tax/income_taxes_deferred_tax_indas_12/ast.json +20 -0
- package/okf/direct_tax/income_taxes_deferred_tax_indas_12/circulars/mca_notification_indas_12_income_taxes.md +40 -0
- package/okf/direct_tax/income_taxes_deferred_tax_indas_12/forms/deferred_tax_asset_liability_reconciliation_schedule.json +46 -0
- package/okf/direct_tax/income_taxes_deferred_tax_indas_12/section.json +61 -0
- package/okf/direct_tax/special_rates_non_filer_206ab/ast.json +26 -0
- package/okf/direct_tax/statutory_disallowance_43b/ast.json +35 -0
- package/okf/direct_tax/tcs_goods_206c_1h/ast.json +69 -0
- package/okf/direct_tax/tds_commission_194h/ast.json +60 -0
- package/okf/direct_tax/tds_commission_194h/section.json +5 -4
- package/okf/direct_tax/tds_contractor_194c/ast.json +71 -0
- package/okf/direct_tax/tds_contractor_194c/section.json +3 -3
- package/okf/direct_tax/tds_ecommerce_194o/ast.json +48 -0
- package/okf/direct_tax/tds_ecommerce_194o/section.json +3 -3
- package/okf/direct_tax/tds_goods_194q/ast.json +71 -0
- package/okf/direct_tax/tds_goods_194q/circulars/cbdt_circular_13_2021_guidelines.md +2 -2
- package/okf/direct_tax/tds_goods_194q/section.json +9 -3
- package/okf/direct_tax/tds_goods_393/ast.json +59 -0
- package/okf/direct_tax/tds_goods_393/section.json +6 -4
- package/okf/direct_tax/tds_individual_contract_194m/ast.json +59 -0
- package/okf/direct_tax/tds_individual_contract_194m/section.json +3 -3
- package/okf/direct_tax/tds_interest_194a/ast.json +59 -0
- package/okf/direct_tax/tds_interest_194a/section.json +7 -4
- package/okf/direct_tax/tds_perquisites_194r/ast.json +47 -0
- package/okf/direct_tax/tds_perquisites_194r/section.json +1 -1
- package/okf/direct_tax/tds_property_194ia/ast.json +70 -0
- package/okf/direct_tax/tds_property_194ia/section.json +3 -3
- package/okf/direct_tax/tds_rent_194i/ast.json +60 -0
- package/okf/direct_tax/tds_rent_194i/section.json +5 -4
- package/okf/direct_tax/tds_rent_individual_194ib/ast.json +59 -0
- package/okf/direct_tax/tds_rent_individual_194ib/section.json +2 -2
- package/okf/direct_tax/tds_services_194j/ast.json +60 -0
- package/okf/direct_tax/tds_services_194j/circulars/cbdt_circular_03_2020_fts_rate_cut.md +1 -1
- package/okf/direct_tax/tds_services_194j/section.json +10 -3
- package/okf/indirect_tax_gst/blocked_itc_17_5/ast.json +26 -0
- package/okf/indirect_tax_gst/common_itc_reversal_rule42_43/ast.json +26 -0
- package/okf/indirect_tax_gst/common_itc_reversal_rule42_43/circulars/cbic_circular_170_2022_itc_reporting.md +30 -0
- package/okf/indirect_tax_gst/common_itc_reversal_rule42_43/circulars/cbic_notification_18_2022_cutoff_amendment.md +26 -0
- package/okf/indirect_tax_gst/common_itc_reversal_rule42_43/forms/gstr_3b_table_4b1_reversal_schedule.json +101 -0
- package/okf/indirect_tax_gst/common_itc_reversal_rule42_43/forms/rule_42_43_annual_reconciliation_worksheet.json +87 -0
- package/okf/indirect_tax_gst/common_itc_reversal_rule42_43/section.json +65 -0
- package/okf/indirect_tax_gst/composite_mixed_supplies_sec8/ast.json +21 -0
- package/okf/indirect_tax_gst/composition_levy_sec10/ast.json +23 -0
- package/okf/indirect_tax_gst/credit_debit_notes_sec34/ast.json +21 -0
- package/okf/indirect_tax_gst/e_invoicing_rule48_4/ast.json +20 -0
- package/okf/indirect_tax_gst/export_services_lut_sec16/ast.json +20 -0
- package/okf/indirect_tax_gst/gst_tds_sec51/ast.json +23 -0
- package/okf/indirect_tax_gst/itc_eligibility_16_2/ast.json +20 -0
- package/okf/indirect_tax_gst/itc_reversal_rule37_37a/ast.json +45 -0
- package/okf/indirect_tax_gst/itc_reversal_rule37_37a/circulars/cbic_circular_192_2023_ecl_interest_calculation.md +36 -0
- package/okf/indirect_tax_gst/itc_reversal_rule37_37a/section.json +8 -1
- package/okf/indirect_tax_gst/rcm_liability_sec9_3/ast.json +26 -0
- package/okf/indirect_tax_gst/rcm_liability_sec9_3/circulars/cbic_notification_07_2025_composition_rent_exclusion.md +41 -0
- package/okf/indirect_tax_gst/rcm_liability_sec9_3/circulars/cbic_notification_09_2024_renting_rcm.md +37 -0
- package/okf/indirect_tax_gst/rcm_liability_sec9_3/circulars/gst_council_55th_meeting_renting_rcm_regularization.md +31 -0
- package/okf/indirect_tax_gst/rcm_liability_sec9_3/section.json +24 -2
- package/okf/international_tax/cross_border_remittance_195/ast.json +39 -0
- package/okf/international_tax/financial_instruments_ecl_indas_109/ast.json +24 -0
- package/okf/international_tax/financial_instruments_ecl_indas_109/circulars/mca_notification_indas_109_financial_instruments.md +43 -0
- package/okf/international_tax/financial_instruments_ecl_indas_109/forms/indas_109_ecl_provision_matrix_schedule.json +43 -0
- package/okf/international_tax/financial_instruments_ecl_indas_109/section.json +62 -0
- package/okf/international_tax/foreign_exchange_indas_21_109/ast.json +20 -0
- package/okf/international_tax/foreign_tax_credit_irc_901_904/ast.json +20 -0
- package/okf/international_tax/transfer_pricing_alp_92c/ast.json +25 -0
- package/okf/manifest.json +117 -20
- package/okf/msme_commercial/delayed_payments_15_16/ast.json +38 -0
- package/okf/msme_commercial/enterprise_classification_sec7/ast.json +25 -0
- package/okf/msme_commercial/lease_accounting_indas_116/ast.json +20 -0
- package/okf/msme_commercial/lease_accounting_indas_116/circulars/mca_notification_indas_116_leases.md +48 -0
- package/okf/msme_commercial/lease_accounting_indas_116/forms/lease_rou_asset_and_liability_schedule.json +55 -0
- package/okf/msme_commercial/lease_accounting_indas_116/section.json +67 -0
- package/okf/msme_commercial/mca_msme_1_filing/ast.json +20 -0
- package/okf/msme_commercial/provisions_contingencies_indas_37/ast.json +20 -0
- package/okf/msme_commercial/provisions_contingencies_indas_37/circulars/mca_notification_indas_37_provisions.md +36 -0
- package/okf/msme_commercial/provisions_contingencies_indas_37/forms/provisions_and_contingent_liabilities_register.json +38 -0
- package/okf/msme_commercial/provisions_contingencies_indas_37/section.json +61 -0
- package/okf/msme_commercial/revenue_recognition_indas_115/ast.json +20 -0
- package/okf/msme_commercial/revenue_recognition_indas_115/circulars/mca_notification_indas_115_revenue_contracts.md +56 -0
- package/okf/msme_commercial/revenue_recognition_indas_115/forms/revenue_contract_5step_allocation_schedule.json +54 -0
- package/okf/msme_commercial/revenue_recognition_indas_115/section.json +69 -0
- package/okf/msme_commercial/statutory_disclosures_sec22/ast.json +21 -0
- package/package.json +22 -1
- package/src/calculators.js +2195 -260
- package/src/constants.js +1 -1
- package/src/index.js +421 -23
- package/src/payroll.js +506 -0
- package/src/tally.js +812 -0
- package/src/usvm/evaluation_vm.js +504 -0
- package/src/usvm/index.js +65 -0
- package/src/usvm/math_rational.js +165 -0
- package/src/usvm/rules/sec_115baa_115jb_mat.json +23 -0
- package/src/usvm/rules/sec_192_salary_tds.json +23 -0
- package/src/usvm/rules/sec_194a_interest.json +59 -0
- package/src/usvm/rules/sec_194c_contractors.json +71 -0
- package/src/usvm/rules/sec_194h_commission.json +60 -0
- package/src/usvm/rules/sec_194i_rent.json +60 -0
- package/src/usvm/rules/sec_194ia_property.json +70 -0
- package/src/usvm/rules/sec_194ib_rent_ind.json +59 -0
- package/src/usvm/rules/sec_194j_services.json +60 -0
- package/src/usvm/rules/sec_194m_contract_ind.json +59 -0
- package/src/usvm/rules/sec_194o_ecommerce.json +48 -0
- package/src/usvm/rules/sec_194q_goods.json +71 -0
- package/src/usvm/rules/sec_194r_perquisites.json +47 -0
- package/src/usvm/rules/sec_195_cross_border.json +39 -0
- package/src/usvm/rules/sec_206ab_non_filer.json +26 -0
- package/src/usvm/rules/sec_206c1h_tcs.json +69 -0
- package/src/usvm/rules/sec_281_tax_clearance.json +21 -0
- package/src/usvm/rules/sec_393_goods.json +59 -0
- package/src/usvm/rules/sec_43bh_disallowance.json +35 -0
- package/src/usvm/rules/sec_50b_slump_sale.json +21 -0
- package/src/usvm/rules/sec_79_loss_continuity.json +33 -0
- package/src/usvm/rules/sec_92c_transfer_pricing.json +25 -0
- package/src/usvm/rules/sec_ftc_dtaa_relief.json +20 -0
- package/src/usvm/rules/sec_gst_blocked_itc_17_5.json +26 -0
- package/src/usvm/rules/sec_gst_composite_mixed_8.json +21 -0
- package/src/usvm/rules/sec_gst_composition_levy_10.json +23 -0
- package/src/usvm/rules/sec_gst_credit_debit_notes_34.json +21 -0
- package/src/usvm/rules/sec_gst_einvoicing_rule48_4.json +20 -0
- package/src/usvm/rules/sec_gst_ewaybill_rule138.json +21 -0
- package/src/usvm/rules/sec_gst_export_lut_16.json +20 -0
- package/src/usvm/rules/sec_gst_itc_eligibility_16_2.json +20 -0
- package/src/usvm/rules/sec_gst_itc_reversal_rule37_37a.json +45 -0
- package/src/usvm/rules/sec_gst_rcm_liability_9_3.json +26 -0
- package/src/usvm/rules/sec_gst_rule42_43_apportionment.json +26 -0
- package/src/usvm/rules/sec_gst_tds_sec51.json +23 -0
- package/src/usvm/rules/sec_indas102_esop.json +20 -0
- package/src/usvm/rules/sec_indas103_mna.json +20 -0
- package/src/usvm/rules/sec_indas109_ecl.json +24 -0
- package/src/usvm/rules/sec_indas115_revenue.json +20 -0
- package/src/usvm/rules/sec_indas116_leases.json +20 -0
- package/src/usvm/rules/sec_indas12_deferred_tax.json +20 -0
- package/src/usvm/rules/sec_indas21_fx_reval.json +20 -0
- package/src/usvm/rules/sec_indas36_impairment.json +20 -0
- package/src/usvm/rules/sec_indas37_provisions.json +20 -0
- package/src/usvm/rules/sec_msme_classification_sec7.json +25 -0
- package/src/usvm/rules/sec_msme_delayed_payments_15_16.json +38 -0
- package/src/usvm/rules/sec_msme_disclosures_sec22.json +21 -0
- package/src/usvm/rules/sec_msme_mca_form_1.json +20 -0
- package/src/usvm/schemas/statutory_ast_schema.js +203 -0
- package/src/usvm/usvm_domain_evaluators.js +1021 -0
- package/src/zoho_gstr2b.js +2964 -0
- package/types/index.d.ts +515 -0
package/src/calculators.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
// Canonical OKF v0.2 Release Merkle Roots & Versions
|
|
22
|
-
export const CURRENT_RELEASE_MERKLE_ROOT = 'sha256-
|
|
22
|
+
export const CURRENT_RELEASE_MERKLE_ROOT = 'sha256-b17c37b2724aece2a28beaab5720a777ae27e661ac51f1436be6eff3ca5ef64b';
|
|
23
23
|
export const MERKLE_ROOT_OKF_V2 = CURRENT_RELEASE_MERKLE_ROOT;
|
|
24
24
|
export const STATUTORY_PACKAGE_VERSION = '2.11.0';
|
|
25
25
|
export const OKF_CORPUS_VERSION = '2.11.0';
|
|
@@ -51,26 +51,242 @@ export function getRbiBankRateForDate(dateString) {
|
|
|
51
51
|
// Statutory Multiple per MSMED Act 2006 Section 16: 3x Bank Rate
|
|
52
52
|
export const MSMED_RATE_MULTIPLIER = 3;
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Error thrown when a required numeric field cannot be parsed from input.
|
|
56
|
+
*/
|
|
57
|
+
export class MalformedNumericInputError extends Error {
|
|
58
|
+
constructor(val, fieldName = 'amount') {
|
|
59
|
+
super(`[@mudraforge/statutory] Malformed numeric input for field "${fieldName}": "${val}". Input could not be safely resolved to a finite number.`);
|
|
60
|
+
this.name = 'MalformedNumericInputError';
|
|
61
|
+
this.code = 'MALFORMED_NUMERIC_INPUT';
|
|
62
|
+
this.field = fieldName;
|
|
63
|
+
this.rawValue = val;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Error thrown when a mandatory statutory calculation parameter is missing or null.
|
|
69
|
+
*/
|
|
70
|
+
export class MissingMandatoryParameterError extends Error {
|
|
71
|
+
constructor(fieldName, functionName) {
|
|
72
|
+
super(`[@mudraforge/statutory] ${functionName}: Mandatory parameter "${fieldName}" is missing or undefined.`);
|
|
73
|
+
this.name = 'MissingMandatoryParameterError';
|
|
74
|
+
this.code = 'MISSING_MANDATORY_PARAMETER';
|
|
75
|
+
this.field = fieldName;
|
|
76
|
+
this.functionName = functionName;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates that a required statutory input parameter is present and resolves cleanly
|
|
82
|
+
* to a finite numeric value, rejecting missing, null, or malformed input without silent coercion.
|
|
83
|
+
*
|
|
84
|
+
* @param {any} val - The input value to validate
|
|
85
|
+
* @param {string} fieldName - The parameter name for diagnostics
|
|
86
|
+
* @param {string} functionName - The calling calculator function name
|
|
87
|
+
* @param {Object} [options]
|
|
88
|
+
* @param {boolean} [options.interpretCreditAsNegative=false]
|
|
89
|
+
* @returns {number} Clean normalized number
|
|
90
|
+
*/
|
|
91
|
+
export function assertMandatoryNumeric(val, fieldName, functionName, options = {}) {
|
|
92
|
+
if (val === undefined || val === null) {
|
|
93
|
+
throw new MissingMandatoryParameterError(fieldName, functionName);
|
|
94
|
+
}
|
|
95
|
+
return cleanNumericAmount(val, {
|
|
96
|
+
strict: true,
|
|
97
|
+
fieldName,
|
|
98
|
+
interpretCreditAsNegative: options.interpretCreditAsNegative || false
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Safely normalizes numeric inputs from numbers, strings with currency symbols (₹, $, €, Rs, INR),
|
|
104
|
+
* thousands separators (commas), and whitespace into valid finite numbers.
|
|
105
|
+
*
|
|
106
|
+
* Accounting conventions handled:
|
|
107
|
+
* - Parentheses: "(1,500.00)" or "(₹ 1,500.00)" -> -1500.00
|
|
108
|
+
* - Trailing minus: "1,500.00-" -> -1500.00
|
|
109
|
+
* - Leading minus with spaces: "- ₹ 1,500.00" -> -1500.00
|
|
110
|
+
* - Credit suffix: "1,500.00 CR" -> magnitude 1500.00. Negated only if interpretCreditAsNegative: true.
|
|
111
|
+
* - Negative zero (-0) normalized to unsigned 0.
|
|
112
|
+
*
|
|
113
|
+
* @param {any} val - Input number or formatted numeric string
|
|
114
|
+
* @param {Object} [options]
|
|
115
|
+
* @param {boolean} [options.strict=false] - When true, throws MalformedNumericInputError on unparseable strings
|
|
116
|
+
* @param {string} [options.fieldName='amount'] - Diagnostic field name for error reporting
|
|
117
|
+
* @param {boolean} [options.interpretCreditAsNegative=false] - Whether "CR" suffix inverts sign
|
|
118
|
+
* @returns {number} Normalized float
|
|
119
|
+
*/
|
|
120
|
+
export function cleanNumericAmount(val, options = {}) {
|
|
121
|
+
const {
|
|
122
|
+
strict = false,
|
|
123
|
+
fieldName = 'amount',
|
|
124
|
+
interpretCreditAsNegative = false,
|
|
125
|
+
defaultValue = 0
|
|
126
|
+
} = options;
|
|
127
|
+
|
|
128
|
+
if (typeof val === 'number') {
|
|
129
|
+
if (!Number.isFinite(val) || isNaN(val)) {
|
|
130
|
+
if (strict) {
|
|
131
|
+
throw new MalformedNumericInputError(val, fieldName);
|
|
132
|
+
}
|
|
133
|
+
return defaultValue;
|
|
134
|
+
}
|
|
135
|
+
return Object.is(val, -0) ? 0 : val;
|
|
136
|
+
}
|
|
137
|
+
if (val === null || val === undefined || typeof val === 'boolean') {
|
|
138
|
+
return defaultValue;
|
|
139
|
+
}
|
|
140
|
+
if (typeof val === 'string') {
|
|
141
|
+
const trimmed = val.trim();
|
|
142
|
+
if (!trimmed) return defaultValue;
|
|
143
|
+
|
|
144
|
+
let isNegative = false;
|
|
145
|
+
let s = trimmed;
|
|
146
|
+
|
|
147
|
+
// Financial malformation checks:
|
|
148
|
+
// 1. Digits separated by whitespace (e.g. "100 200" or concatenated invoice numbers)
|
|
149
|
+
// 2. Double commas or double decimal points (e.g. "1,,000" or "100..50")
|
|
150
|
+
// 3. Comma appearing after a decimal point (e.g. "100.50,25")
|
|
151
|
+
if (/\d\s+\d/.test(s) || /(?:,,|\.\.)/.test(s) || /\.\d*,/.test(s)) {
|
|
152
|
+
if (strict) {
|
|
153
|
+
throw new MalformedNumericInputError(val, fieldName);
|
|
154
|
+
}
|
|
155
|
+
return defaultValue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Strip leading currency label before parenthesis or sign: "₹ (50,000.00)" or "INR -50,000"
|
|
159
|
+
s = s.replace(/^(?:₹|\$|€|INR|Rs\.?)\s*/i, '');
|
|
160
|
+
|
|
161
|
+
// Accounting parentheses: (1,500.00) or (₹ 1,500.00)
|
|
162
|
+
if (/^\(.*\)$/.test(s)) {
|
|
163
|
+
isNegative = true;
|
|
164
|
+
s = s.slice(1, -1).trim();
|
|
165
|
+
} else if (/-\s*$/.test(s)) {
|
|
166
|
+
// Trailing minus from legacy ERP dumps: "1,500.00-"
|
|
167
|
+
isNegative = true;
|
|
168
|
+
s = s.replace(/-\s*$/, '').trim();
|
|
169
|
+
} else if (/^\s*-\s*/.test(s)) {
|
|
170
|
+
// Explicit leading minus with possible whitespace: "- ₹ 1,500.00" or "-1500"
|
|
171
|
+
isNegative = true;
|
|
172
|
+
s = s.replace(/^\s*-\s*/, '').trim();
|
|
173
|
+
} else if (/(?:\s+)?(?:cr|credit)\.?$/i.test(s)) {
|
|
174
|
+
// Accounting credit suffix: "1,500.00 CR"
|
|
175
|
+
// Note: CR means credit, whose signed interpretation depends on ledger orientation.
|
|
176
|
+
// Only negate if caller explicitly requests interpretCreditAsNegative: true.
|
|
177
|
+
if (interpretCreditAsNegative) {
|
|
178
|
+
isNegative = true;
|
|
179
|
+
}
|
|
180
|
+
s = s.replace(/(?:\s+)?(?:cr|credit)\.?$/i, '').trim();
|
|
181
|
+
} else if (/(?:\s+)?(?:dr|debit)\.?$/i.test(s)) {
|
|
182
|
+
// Accounting debit suffix: "1,500.00 DR"
|
|
183
|
+
s = s.replace(/(?:\s+)?(?:dr|debit)\.?$/i, '').trim();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Strip remaining currency symbols, commas, and currency labels
|
|
187
|
+
const cleaned = s.replace(/[₹$€,\s]|(?:INR|Rs\.?)/gi, '');
|
|
188
|
+
const n = parseFloat(cleaned);
|
|
189
|
+
|
|
190
|
+
// Check for malformed strings that had non-numeric text left over
|
|
191
|
+
const isCleanNumber = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(cleaned);
|
|
192
|
+
if (!isCleanNumber || !Number.isFinite(n) || isNaN(n)) {
|
|
193
|
+
if (strict) {
|
|
194
|
+
throw new MalformedNumericInputError(val, fieldName);
|
|
195
|
+
}
|
|
196
|
+
return defaultValue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const res = isNegative ? -Math.abs(n) : n;
|
|
200
|
+
return Object.is(res, -0) ? 0 : res;
|
|
201
|
+
}
|
|
202
|
+
return defaultValue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* High-precision conversion of rupees to integer paise.
|
|
207
|
+
* Avoids IEEE-754 floating point drift during threshold and summation operations.
|
|
208
|
+
*
|
|
209
|
+
* Safety Bounds & Precision Policy:
|
|
210
|
+
* - Bounds: Asserts absolute value does not exceed JavaScript Number.MAX_SAFE_INTEGER (53-bit integer limit).
|
|
211
|
+
* - Precision: Applies standard statutory half-up rounding (Math.round) to the nearest integer paisa.
|
|
212
|
+
* - Strict sub-paisa: When options.strictSubPaisa is true, rejects amounts having fractional sub-paisa (>2 decimals).
|
|
213
|
+
*
|
|
214
|
+
* @param {any} amount - Rupees amount or formatted string
|
|
215
|
+
* @param {string|Object} [options='amount'] - Diagnostic field name or options object
|
|
216
|
+
* @param {string} [options.fieldName='amount']
|
|
217
|
+
* @param {boolean} [options.strict=false]
|
|
218
|
+
* @param {boolean} [options.strictSubPaisa=false]
|
|
219
|
+
* @returns {number} Integer paise (e.g. 50.25 -> 5025)
|
|
220
|
+
*/
|
|
221
|
+
export function toIntegerPaise(amount, options = 'amount') {
|
|
222
|
+
const opts = typeof options === 'string' ? { fieldName: options } : (options || {});
|
|
223
|
+
const {
|
|
224
|
+
fieldName = 'amount',
|
|
225
|
+
strict = false,
|
|
226
|
+
strictSubPaisa = false
|
|
227
|
+
} = opts;
|
|
228
|
+
|
|
229
|
+
const n = cleanNumericAmount(amount, { strict, fieldName });
|
|
230
|
+
const rawPaise = n * 100;
|
|
231
|
+
|
|
232
|
+
// Safe integer boundary assertion (9 quadrillion paise / ~₹90 Trillion limit)
|
|
233
|
+
if (Math.abs(rawPaise) > Number.MAX_SAFE_INTEGER) {
|
|
234
|
+
throw new RangeError(`[@mudraforge/statutory] Amount exceeds JavaScript MAX_SAFE_INTEGER (53-bit limit) in field "${fieldName}".`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const roundedPaise = Math.round(rawPaise);
|
|
238
|
+
|
|
239
|
+
// Explicit sub-paisa fractional detection
|
|
240
|
+
if (strictSubPaisa && Math.abs(rawPaise - roundedPaise) > 1e-4) {
|
|
241
|
+
throw new MalformedNumericInputError(amount, fieldName);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return roundedPaise;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Converts integer paise back to rupees with 2 decimal places.
|
|
249
|
+
*
|
|
250
|
+
* Safety Bounds:
|
|
251
|
+
* - Asserts input paise does not exceed Number.MAX_SAFE_INTEGER.
|
|
252
|
+
*
|
|
253
|
+
* @param {number} paise - Integer paise
|
|
254
|
+
* @returns {number} Rounded rupees
|
|
255
|
+
*/
|
|
256
|
+
export function fromIntegerPaise(paise) {
|
|
257
|
+
if (!Number.isFinite(paise) || isNaN(paise)) return 0;
|
|
258
|
+
if (Math.abs(paise) > Number.MAX_SAFE_INTEGER) {
|
|
259
|
+
throw new RangeError('[@mudraforge/statutory] Integer paise exceeds JavaScript MAX_SAFE_INTEGER (53-bit limit).');
|
|
260
|
+
}
|
|
261
|
+
const rupees = paise / 100;
|
|
262
|
+
return Object.is(rupees, -0) ? 0 : Math.round(rupees * 100) / 100;
|
|
263
|
+
}
|
|
264
|
+
|
|
54
265
|
/**
|
|
55
266
|
* Standard Paisa Precision Rounding (2 decimal places) using integer math.
|
|
56
267
|
* Eliminates floating point drift (e.g. 0.00000000001).
|
|
57
268
|
* Rejects negative zero (-0 normalized to 0).
|
|
58
269
|
*/
|
|
59
270
|
export function roundToPaisa(amount) {
|
|
60
|
-
const n =
|
|
271
|
+
const n = cleanNumericAmount(amount);
|
|
61
272
|
if (!Number.isFinite(n) || isNaN(n) || Object.is(n, -0)) return 0;
|
|
62
|
-
return Math.round(n * 100)
|
|
273
|
+
return fromIntegerPaise(Math.round(n * 100));
|
|
63
274
|
}
|
|
64
275
|
|
|
65
276
|
/**
|
|
66
277
|
* Section 288B Rounding (Rounding off of tax):
|
|
67
|
-
*
|
|
68
|
-
*
|
|
278
|
+
* Under Section 288B of the Income-tax Act, 1961 (as substituted by the Taxation Laws
|
|
279
|
+
* (Amendment) Act, 2006 w.e.f. 13-07-2006), the amount of tax payable/deductible,
|
|
280
|
+
* interest, penalty or refund is rounded off to the nearest multiple of ten rupees:
|
|
281
|
+
* 1. Part of a rupee consisting of paise: >= 50 paise is rounded up to ₹1, < 50 paise ignored.
|
|
282
|
+
* 2. If the last digit of the resulting whole rupees is 5 or more, increased to next higher multiple of ₹10.
|
|
283
|
+
* 3. If the last digit is less than 5, reduced to next lower multiple of ₹10.
|
|
69
284
|
*/
|
|
70
285
|
export function roundSection288B(amount) {
|
|
71
|
-
const n =
|
|
286
|
+
const n = cleanNumericAmount(amount);
|
|
72
287
|
if (!Number.isFinite(n) || isNaN(n) || Object.is(n, -0)) return 0;
|
|
73
|
-
|
|
288
|
+
const wholeRupees = Math.round(n);
|
|
289
|
+
return Math.round(wholeRupees / 10) * 10;
|
|
74
290
|
}
|
|
75
291
|
|
|
76
292
|
/**
|
|
@@ -102,9 +318,9 @@ export function calculateMsmeCompoundInterest({
|
|
|
102
318
|
dateConvention = 'START_DATE_INCLUSIVE', // 'START_DATE_INCLUSIVE' | 'SECTION_16_DAY_AFTER_DUE' | 'FROM_APPOINTED_DAY' | 'FROM_DAY_AFTER_AGREED_DATE'
|
|
103
319
|
commenceDayAfter = false, // Legacy flag: when true, adds 1 day to dueDate per Section 16 agreed date rule
|
|
104
320
|
includePaymentDate = false // When false, payment date is the settlement day (interest ceases at start of payment date)
|
|
105
|
-
}) {
|
|
106
|
-
const p =
|
|
107
|
-
if (
|
|
321
|
+
} = {}) {
|
|
322
|
+
const p = assertMandatoryNumeric(principal, 'principal', 'calculateMsmeCompoundInterest');
|
|
323
|
+
if (p <= 0) {
|
|
108
324
|
throw new Error("Invalid principal amount for MSME interest calculation.");
|
|
109
325
|
}
|
|
110
326
|
|
|
@@ -488,7 +704,7 @@ export function evaluateSection43bH({
|
|
|
488
704
|
fyEndDate = null,
|
|
489
705
|
supplierType = 'MANUFACTURER', // 'MICRO_MANUFACTURER', 'SMALL_MANUFACTURER', 'TRADER', 'MEDIUM'
|
|
490
706
|
nicCode = null
|
|
491
|
-
}) {
|
|
707
|
+
} = {}) {
|
|
492
708
|
const baseAcceptanceDate = new Date(acceptanceDate || invoiceDate);
|
|
493
709
|
|
|
494
710
|
// Dynamically calculate fiscal year end based on invoice/acceptance date if not explicitly overridden
|
|
@@ -987,9 +1203,10 @@ export function evaluateCrossBorderRemittance({
|
|
|
987
1203
|
hasNoPe = false,
|
|
988
1204
|
hasForm10F = false,
|
|
989
1205
|
hasAoCertificate = false
|
|
990
|
-
}) {
|
|
991
|
-
const usd =
|
|
992
|
-
const
|
|
1206
|
+
} = {}) {
|
|
1207
|
+
const usd = cleanNumericAmount(amountUsd);
|
|
1208
|
+
const rate = cleanNumericAmount(fxRate) || 83.5;
|
|
1209
|
+
const amountInr = roundToPaisa(usd * rate);
|
|
993
1210
|
const txDate = new Date(transactionDate);
|
|
994
1211
|
|
|
995
1212
|
// If treaty relief is claimed (TRC, No-PE, or Form 10F) but country is missing,
|
|
@@ -1355,8 +1572,8 @@ export function resolveMissingPanPenaltyFloor({
|
|
|
1355
1572
|
const sec = String(sectionCode || '').toUpperCase().trim();
|
|
1356
1573
|
const item = String(tableItem || '').toLowerCase().trim();
|
|
1357
1574
|
|
|
1358
|
-
const isGoodsPurchase = sec === '194Q' || item === '393_8_ii' || item === 'item 8(ii)' || item === '8(ii)';
|
|
1359
|
-
const isEcommerce = sec === '194O' || sec === '194-O' || item === '393_8_v' || item === 'item 8(v)' || item === '8(v)';
|
|
1575
|
+
const isGoodsPurchase = sec === '194Q' || item === '393_8_ii' || item === '393_1_8_ii' || item === 'sl 8(ii)' || item === 'sl. no. 8(ii)' || item === 'item 8(ii)' || item === '8(ii)';
|
|
1576
|
+
const isEcommerce = sec === '194O' || sec === '194-O' || item === '393_8_v' || item === '393_1_8_i' || item === 'sl 8(i)' || item === 'sl. no. 8(i)' || item === 'item 8(v)' || item === '8(v)' || item === '8(i)';
|
|
1360
1577
|
|
|
1361
1578
|
if (isGoodsPurchase || isEcommerce) {
|
|
1362
1579
|
return {
|
|
@@ -1364,7 +1581,7 @@ export function resolveMissingPanPenaltyFloor({
|
|
|
1364
1581
|
isConcessional5PercentApplicable: true,
|
|
1365
1582
|
categoryScope: isGoodsPurchase ? 'SPECIFIED_GOODS_PURCHASE_5_PERCENT' : 'SPECIFIED_ECOMMERCE_PARTICIPANT_5_PERCENT',
|
|
1366
1583
|
governingProviso: isAct2025
|
|
1367
|
-
? `Income-tax Act, 2025 Section 397(2)(b)(i)(${isGoodsPurchase ? 'C' : 'D'}) read with Section 393(1) Table
|
|
1584
|
+
? `Income-tax Act, 2025 Section 397(2)(b)(i)(${isGoodsPurchase ? 'C' : 'D'}) read with Section 393(1), Table 1, Sl. No. ${isGoodsPurchase ? '8(ii)' : '8(i)'}`
|
|
1368
1585
|
: `Income-tax Act, 1961 Section 206AA(1) First Proviso read with Section ${sec}`
|
|
1369
1586
|
};
|
|
1370
1587
|
}
|
|
@@ -1579,11 +1796,11 @@ export function evaluateSection194Q({
|
|
|
1579
1796
|
rateEffectiveFrom = null,
|
|
1580
1797
|
rateEffectiveTo = null
|
|
1581
1798
|
} = {}) {
|
|
1582
|
-
const rawInvoiceAmount =
|
|
1583
|
-
const gstAmount =
|
|
1584
|
-
const creditNote =
|
|
1585
|
-
const advanceNetted =
|
|
1586
|
-
const priorAdvanceTds =
|
|
1799
|
+
const rawInvoiceAmount = assertMandatoryNumeric(invoiceAmount, 'invoiceAmount', 'evaluateSection194Q');
|
|
1800
|
+
const gstAmount = cleanNumericAmount(gstAmountInr);
|
|
1801
|
+
const creditNote = cleanNumericAmount(creditNoteInr);
|
|
1802
|
+
const advanceNetted = cleanNumericAmount(advanceNettedInr);
|
|
1803
|
+
const priorAdvanceTds = cleanNumericAmount(priorAdvanceTdsDeductedInr);
|
|
1587
1804
|
|
|
1588
1805
|
// CBDT Circular No. 13 of 2021 Question 3:
|
|
1589
1806
|
// Does Section 194Q apply to import of goods from non-residents?
|
|
@@ -1642,13 +1859,17 @@ export function evaluateSection194Q({
|
|
|
1642
1859
|
// Disambiguate Current FY purchases vs Preceding FY purchases:
|
|
1643
1860
|
// Section 194Q(1) statutory threshold evaluates cumulative purchases in the CURRENT previous year (current FY).
|
|
1644
1861
|
// Purchases from the preceding financial year do NOT consume the current financial year threshold.
|
|
1645
|
-
const prior = Math.max(0,
|
|
1862
|
+
const prior = Math.max(0, cleanNumericAmount(
|
|
1646
1863
|
currentFyPriorPurchasesInr !== undefined
|
|
1647
1864
|
? currentFyPriorPurchasesInr
|
|
1648
1865
|
: (priorPurchasesInCurrentFyInr !== undefined ? priorPurchasesInCurrentFyInr : priorFyPurchases)
|
|
1649
|
-
)
|
|
1650
|
-
const cumulative = prior + amount;
|
|
1866
|
+
));
|
|
1651
1867
|
const THRESHOLD = 5000000; // ₹50 Lakhs
|
|
1868
|
+
const THRESHOLD_PAISE = 500000000; // ₹50 Lakhs in integer paise
|
|
1869
|
+
const priorPaise = toIntegerPaise(prior, 'priorPurchases');
|
|
1870
|
+
const amountPaise = toIntegerPaise(amount, 'amount');
|
|
1871
|
+
const cumulativePaise = priorPaise + amountPaise;
|
|
1872
|
+
const cumulative = fromIntegerPaise(cumulativePaise);
|
|
1652
1873
|
|
|
1653
1874
|
// Buyer turnover prescription under Section 194Q(1) Explanation:
|
|
1654
1875
|
// Person whose total sales, gross receipts or turnover exceeds ₹10 Crore in preceding FY.
|
|
@@ -1672,8 +1893,10 @@ export function evaluateSection194Q({
|
|
|
1672
1893
|
// Incremental threshold absorption formula with Paisa integer precision:
|
|
1673
1894
|
// Delta = max(0, cumulative - THRESHOLD) - max(0, prior - THRESHOLD)
|
|
1674
1895
|
// Taxable base is min(amount, Delta)
|
|
1675
|
-
const
|
|
1676
|
-
const
|
|
1896
|
+
const incrementalExcessPaise = Math.max(0, cumulativePaise - THRESHOLD_PAISE) - Math.max(0, priorPaise - THRESHOLD_PAISE);
|
|
1897
|
+
const conditionalTaxableBasePaise = Math.max(0, Math.min(amountPaise, incrementalExcessPaise));
|
|
1898
|
+
const conditionalTaxableBase = fromIntegerPaise(conditionalTaxableBasePaise);
|
|
1899
|
+
const incrementalExcess = fromIntegerPaise(incrementalExcessPaise);
|
|
1677
1900
|
const taxableBase = isSection194QApplicable
|
|
1678
1901
|
? conditionalTaxableBase
|
|
1679
1902
|
: (isBuyerPrescriptionIndeterminate ? conditionalTaxableBase : 0);
|
|
@@ -1726,7 +1949,7 @@ export function evaluateSection194Q({
|
|
|
1726
1949
|
isAct2025 = effectiveDate >= '2026-04-01';
|
|
1727
1950
|
isPost206cSunset = effectiveDate >= '2025-04-01';
|
|
1728
1951
|
governingWithholdingSection = isSection194QApplicable || isBuyerPrescriptionIndeterminate
|
|
1729
|
-
? (isAct2025 ? "Income-tax Act, 2025 Section 393(1) Table
|
|
1952
|
+
? (isAct2025 ? "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 8(ii)" : "Income-tax Act, 1961 Section 194Q")
|
|
1730
1953
|
: "N/A (Buyer turnover does not exceed ₹10 Cr threshold)";
|
|
1731
1954
|
} else {
|
|
1732
1955
|
// Date is missing: preserve uncertainty rather than coercing machine date
|
|
@@ -1734,12 +1957,12 @@ export function evaluateSection194Q({
|
|
|
1734
1957
|
isAct2025 = false;
|
|
1735
1958
|
isPost206cSunset = true; // Section 206C(1H) sunset on 1 April 2025
|
|
1736
1959
|
governingWithholdingSection = isSection194QApplicable || isBuyerPrescriptionIndeterminate
|
|
1737
|
-
? "Income-tax Act, 1961 Section 194Q (or Income-tax Act, 2025 Section 393(1) Table
|
|
1960
|
+
? "Income-tax Act, 1961 Section 194Q (or Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 8(ii) if credited/paid on or after 1 April 2026)"
|
|
1738
1961
|
: "N/A (Buyer turnover does not exceed ₹10 Cr threshold)";
|
|
1739
1962
|
}
|
|
1740
1963
|
|
|
1741
1964
|
let governingPanPenaltySection = isAct2025
|
|
1742
|
-
? "Income-tax Act, 2025 Section 397(2)(b)(i)(C) read with Section 393(1) Table
|
|
1965
|
+
? "Income-tax Act, 2025 Section 397(2)(b)(i)(C) read with Section 393(1), Table 1, Sl. No. 8(ii)"
|
|
1743
1966
|
: "Income-tax Act, 1961 Section 206AA(1) First Proviso read with Section 194Q";
|
|
1744
1967
|
|
|
1745
1968
|
// Resolve certificate framework with Section 536(2) transitional savings and validity evaluation
|
|
@@ -1923,6 +2146,7 @@ export function evaluateSection194Q({
|
|
|
1923
2146
|
statutoryRatePercent,
|
|
1924
2147
|
effectiveRatePercent,
|
|
1925
2148
|
calculatedTdsInr,
|
|
2149
|
+
taxAmountRoundedSection288B: roundSection288B(calculatedTdsInr),
|
|
1926
2150
|
certifiedTdsInr,
|
|
1927
2151
|
uncoveredTdsInr,
|
|
1928
2152
|
certificateCeiling,
|
|
@@ -2344,15 +2568,15 @@ export function evaluateSection194C({
|
|
|
2344
2568
|
certificatePriorUtilizedInr = 0,
|
|
2345
2569
|
certificateSection = null
|
|
2346
2570
|
} = {}) {
|
|
2347
|
-
const rawInvoiceAmount =
|
|
2348
|
-
const gstAmount =
|
|
2571
|
+
const rawInvoiceAmount = assertMandatoryNumeric(invoiceAmount, 'invoiceAmount', 'evaluateSection194C');
|
|
2572
|
+
const gstAmount = cleanNumericAmount(gstAmountInr);
|
|
2349
2573
|
|
|
2350
2574
|
// CBDT Circular No. 23/2017: If GST is separately stated, TDS applies to basic value
|
|
2351
2575
|
const effectiveContractAmount = isAdvancePayment
|
|
2352
2576
|
? rawInvoiceAmount
|
|
2353
2577
|
: Math.max(0, rawInvoiceAmount - gstAmount);
|
|
2354
2578
|
|
|
2355
|
-
const prior = Math.max(0,
|
|
2579
|
+
const prior = Math.max(0, cleanNumericAmount(priorFyPayments));
|
|
2356
2580
|
const cumulative = prior + effectiveContractAmount;
|
|
2357
2581
|
|
|
2358
2582
|
const SINGLE_THRESHOLD = 30000;
|
|
@@ -2438,7 +2662,7 @@ export function evaluateSection194C({
|
|
|
2438
2662
|
// ── Temporal Act Bridge ──
|
|
2439
2663
|
const isAct2025 = effectiveDate ? effectiveDate >= '2026-04-01' : false;
|
|
2440
2664
|
const governingSection = isAct2025
|
|
2441
|
-
? "Income-tax Act, 2025 Section 393(1) Table
|
|
2665
|
+
? "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 6(i)"
|
|
2442
2666
|
: "Income-tax Act, 1961 Section 194C";
|
|
2443
2667
|
|
|
2444
2668
|
// Resolve certificate framework with Section 536(2) transitional savings and validity evaluation
|
|
@@ -2580,6 +2804,7 @@ export function evaluateSection194C({
|
|
|
2580
2804
|
standardRatePercent,
|
|
2581
2805
|
effectiveRatePercent,
|
|
2582
2806
|
calculatedTdsInr,
|
|
2807
|
+
taxAmountRoundedSection288B: roundSection288B(calculatedTdsInr),
|
|
2583
2808
|
certifiedTdsInr,
|
|
2584
2809
|
uncoveredTdsInr,
|
|
2585
2810
|
certificateCeiling,
|
|
@@ -2686,14 +2911,14 @@ export function evaluateSection194J({
|
|
|
2686
2911
|
certificatePriorUtilizedInr = 0,
|
|
2687
2912
|
certificateSection = null
|
|
2688
2913
|
} = {}) {
|
|
2689
|
-
const rawInvoiceAmount = Number(invoiceAmount) || 0;
|
|
2690
|
-
const gstAmount = Number(gstAmountInr) || 0;
|
|
2691
|
-
|
|
2692
2914
|
const normalizedCategory = normalize194JCategory(serviceCategory);
|
|
2693
2915
|
if (!normalizedCategory) {
|
|
2694
2916
|
throw new Error(`INVALID_SERVICE_CATEGORY: Unknown serviceCategory "${serviceCategory}". Allowed categories: ${VALID_194J_CATEGORIES.join(', ')}`);
|
|
2695
2917
|
}
|
|
2696
2918
|
|
|
2919
|
+
const rawInvoiceAmount = assertMandatoryNumeric(invoiceAmount, 'invoiceAmount', 'evaluateSection194J');
|
|
2920
|
+
const gstAmount = cleanNumericAmount(gstAmountInr);
|
|
2921
|
+
|
|
2697
2922
|
// CBDT Circular No. 23/2017: If GST is separately stated, TDS applies to basic value
|
|
2698
2923
|
const effectiveServiceAmount = isAdvancePayment
|
|
2699
2924
|
? rawInvoiceAmount
|
|
@@ -2702,7 +2927,7 @@ export function evaluateSection194J({
|
|
|
2702
2927
|
const effectivePriorInput = priorPaymentsInCurrentFyInr !== null && priorPaymentsInCurrentFyInr !== undefined
|
|
2703
2928
|
? priorPaymentsInCurrentFyInr
|
|
2704
2929
|
: priorFyCategoryPayments;
|
|
2705
|
-
const prior = Math.max(0,
|
|
2930
|
+
const prior = Math.max(0, cleanNumericAmount(effectivePriorInput));
|
|
2706
2931
|
const cumulative = prior + effectiveServiceAmount;
|
|
2707
2932
|
|
|
2708
2933
|
const isDirectorFee = normalizedCategory === 'DIRECTOR_REMUNERATION';
|
|
@@ -2748,9 +2973,9 @@ export function evaluateSection194J({
|
|
|
2748
2973
|
|
|
2749
2974
|
// ── Temporal Act Bridge ──
|
|
2750
2975
|
const isAct2025 = Boolean(effectiveDate && String(effectiveDate).slice(0, 10) >= '2026-04-01');
|
|
2751
|
-
const governingSection =
|
|
2752
|
-
? "Income-tax Act, 2025 Section 393(1) Table
|
|
2753
|
-
: "Income-tax Act, 1961 Section 194J";
|
|
2976
|
+
const governingSection = isDirectorFee
|
|
2977
|
+
? (isAct2025 ? "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 6(iii) (Director Remuneration)" : "Income-tax Act, 1961 Section 194J(1)(ba)")
|
|
2978
|
+
: (isAct2025 ? "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 6(iii)" : "Income-tax Act, 1961 Section 194J");
|
|
2754
2979
|
|
|
2755
2980
|
// ── Rate Determination ──
|
|
2756
2981
|
// 2% for FTS, BPO, Royalty; 10% for Professional Services, Non-compete, and Director fees
|
|
@@ -2767,7 +2992,9 @@ export function evaluateSection194J({
|
|
|
2767
2992
|
|
|
2768
2993
|
// ── Threshold Evaluation ──
|
|
2769
2994
|
// Director remuneration has NO threshold under 3rd proviso to Section 194J(1)
|
|
2770
|
-
|
|
2995
|
+
// Director remuneration has NO threshold under 3rd proviso to Section 194J(1) / Section 393(1) Table 1 Sl. No. 6(iii)
|
|
2996
|
+
// 1961 Act threshold: ₹30,000; 2025 Act threshold: ₹50,000 (Section 393(1), Table 1, Sl. No. 6(iii))
|
|
2997
|
+
const CATEGORY_THRESHOLD = isDirectorFee ? 0 : (isAct2025 ? 50000 : 30000);
|
|
2771
2998
|
const isThresholdBreached = isDirectorFee || cumulative > CATEGORY_THRESHOLD;
|
|
2772
2999
|
|
|
2773
3000
|
let taxableBase = 0;
|
|
@@ -2904,6 +3131,7 @@ export function evaluateSection194J({
|
|
|
2904
3131
|
standardRatePercent,
|
|
2905
3132
|
effectiveRatePercent,
|
|
2906
3133
|
calculatedTdsInr,
|
|
3134
|
+
taxAmountRoundedSection288B: roundSection288B(calculatedTdsInr),
|
|
2907
3135
|
certifiedTdsInr,
|
|
2908
3136
|
uncoveredTdsInr,
|
|
2909
3137
|
certificateCeiling,
|
|
@@ -2945,6 +3173,8 @@ export function evaluateSection194J({
|
|
|
2945
3173
|
*/
|
|
2946
3174
|
export function evaluateSection194I({
|
|
2947
3175
|
invoiceAmount,
|
|
3176
|
+
monthlyRent = null,
|
|
3177
|
+
rentPeriodMonths = null,
|
|
2948
3178
|
priorFyRent = 0,
|
|
2949
3179
|
priorFyPayments = null,
|
|
2950
3180
|
priorFyPaymentsAlreadyTaxed = null,
|
|
@@ -2962,16 +3192,16 @@ export function evaluateSection194I({
|
|
|
2962
3192
|
certificatePriorUtilizedInr = 0,
|
|
2963
3193
|
certificateSection = null
|
|
2964
3194
|
} = {}) {
|
|
2965
|
-
const rawInvoiceAmount =
|
|
2966
|
-
const gstAmount =
|
|
3195
|
+
const rawInvoiceAmount = assertMandatoryNumeric(invoiceAmount, 'invoiceAmount', 'evaluateSection194I');
|
|
3196
|
+
const gstAmount = cleanNumericAmount(gstAmountInr);
|
|
2967
3197
|
|
|
2968
3198
|
const effectiveRentAmount = isAdvancePayment
|
|
2969
3199
|
? rawInvoiceAmount
|
|
2970
3200
|
: Math.max(0, rawInvoiceAmount - gstAmount);
|
|
2971
3201
|
|
|
2972
3202
|
const effectivePrior = priorFyPayments !== null && priorFyPayments !== undefined
|
|
2973
|
-
?
|
|
2974
|
-
:
|
|
3203
|
+
? cleanNumericAmount(priorFyPayments)
|
|
3204
|
+
: cleanNumericAmount(priorFyRent);
|
|
2975
3205
|
const prior = Math.max(0, effectivePrior);
|
|
2976
3206
|
const cumulative = prior + effectiveRentAmount;
|
|
2977
3207
|
|
|
@@ -3003,7 +3233,7 @@ export function evaluateSection194I({
|
|
|
3003
3233
|
|
|
3004
3234
|
const isAct2025 = Boolean(effectiveDate && String(effectiveDate).slice(0, 10) >= '2026-04-01');
|
|
3005
3235
|
const governingSection = isAct2025
|
|
3006
|
-
? "Income-tax Act, 2025 Section 393(1) Table
|
|
3236
|
+
? "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 2"
|
|
3007
3237
|
: "Income-tax Act, 1961 Section 194I";
|
|
3008
3238
|
|
|
3009
3239
|
const panAvailable = Boolean(hasPan) && !isPanInoperative;
|
|
@@ -3014,8 +3244,16 @@ export function evaluateSection194I({
|
|
|
3014
3244
|
});
|
|
3015
3245
|
const standardRatePercent = panAvailable ? baseStatutoryRatePercent : Math.max(baseStatutoryRatePercent, panFloorResolution.penaltyFloorPercent);
|
|
3016
3246
|
|
|
3017
|
-
// Threshold: ₹2,40,000 per financial year
|
|
3018
|
-
|
|
3247
|
+
// Threshold: Under 1961 Act: ₹2,40,000 per financial year; Under 2025 Act: ₹50,000 per month or part thereof (Section 393(1), Table 1, Sl. No. 2)
|
|
3248
|
+
let RENT_THRESHOLD;
|
|
3249
|
+
if (isAct2025) {
|
|
3250
|
+
const effectiveRentMonths = (rentPeriodMonths !== null && rentPeriodMonths !== undefined && !isNaN(Number(rentPeriodMonths)) && Number(rentPeriodMonths) > 0)
|
|
3251
|
+
? Number(rentPeriodMonths)
|
|
3252
|
+
: null;
|
|
3253
|
+
RENT_THRESHOLD = effectiveRentMonths ? (50000 * effectiveRentMonths) : 50000;
|
|
3254
|
+
} else {
|
|
3255
|
+
RENT_THRESHOLD = 240000;
|
|
3256
|
+
}
|
|
3019
3257
|
const isThresholdBreached = cumulative > RENT_THRESHOLD;
|
|
3020
3258
|
|
|
3021
3259
|
let taxableBase = 0;
|
|
@@ -3119,16 +3357,16 @@ export function evaluateSection194H({
|
|
|
3119
3357
|
certificatePriorUtilizedInr = 0,
|
|
3120
3358
|
certificateSection = null
|
|
3121
3359
|
} = {}) {
|
|
3122
|
-
const rawInvoiceAmount =
|
|
3123
|
-
const gstAmount =
|
|
3360
|
+
const rawInvoiceAmount = assertMandatoryNumeric(invoiceAmount, 'invoiceAmount', 'evaluateSection194H');
|
|
3361
|
+
const gstAmount = cleanNumericAmount(gstAmountInr);
|
|
3124
3362
|
|
|
3125
3363
|
const effectiveCommissionAmount = isAdvancePayment
|
|
3126
3364
|
? rawInvoiceAmount
|
|
3127
3365
|
: Math.max(0, rawInvoiceAmount - gstAmount);
|
|
3128
3366
|
|
|
3129
3367
|
const effectivePrior = priorFyPayments !== null && priorFyPayments !== undefined
|
|
3130
|
-
?
|
|
3131
|
-
:
|
|
3368
|
+
? cleanNumericAmount(priorFyPayments)
|
|
3369
|
+
: cleanNumericAmount(priorFyCommission);
|
|
3132
3370
|
const prior = Math.max(0, effectivePrior);
|
|
3133
3371
|
const cumulative = prior + effectiveCommissionAmount;
|
|
3134
3372
|
|
|
@@ -3160,7 +3398,7 @@ export function evaluateSection194H({
|
|
|
3160
3398
|
const baseStatutoryRatePercent = isPostOctober2024 ? 2.0 : 5.0;
|
|
3161
3399
|
|
|
3162
3400
|
const governingSection = isAct2025
|
|
3163
|
-
? "Income-tax Act, 2025 Section 393(1) Table
|
|
3401
|
+
? "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 1(ii)"
|
|
3164
3402
|
: "Income-tax Act, 1961 Section 194H";
|
|
3165
3403
|
|
|
3166
3404
|
const panAvailable = Boolean(hasPan) && !isPanInoperative;
|
|
@@ -3171,8 +3409,8 @@ export function evaluateSection194H({
|
|
|
3171
3409
|
});
|
|
3172
3410
|
const standardRatePercent = panAvailable ? baseStatutoryRatePercent : Math.max(baseStatutoryRatePercent, panFloorResolution.penaltyFloorPercent);
|
|
3173
3411
|
|
|
3174
|
-
// Threshold: ₹15,000
|
|
3175
|
-
const COMMISSION_THRESHOLD = 15000;
|
|
3412
|
+
// Threshold: ₹15,000 under 1961 Act; ₹20,000 under 2025 Act Section 393(1), Table 1, Sl. No. 1(ii)
|
|
3413
|
+
const COMMISSION_THRESHOLD = isAct2025 ? 20000 : 15000;
|
|
3176
3414
|
const isThresholdBreached = cumulative > COMMISSION_THRESHOLD;
|
|
3177
3415
|
|
|
3178
3416
|
let taxableBase = 0;
|
|
@@ -3286,12 +3524,12 @@ export function evaluateSection194A({
|
|
|
3286
3524
|
certificatePriorUtilizedInr = 0,
|
|
3287
3525
|
certificateSection = null
|
|
3288
3526
|
} = {}) {
|
|
3289
|
-
const rawInterestAmount =
|
|
3527
|
+
const rawInterestAmount = assertMandatoryNumeric(interestAmount, 'interestAmount', 'evaluateSection194A');
|
|
3290
3528
|
|
|
3291
3529
|
const effectivePriorInput = priorPaymentsInCurrentFyInr !== null && priorPaymentsInCurrentFyInr !== undefined
|
|
3292
3530
|
? priorPaymentsInCurrentFyInr
|
|
3293
3531
|
: priorFyCategoryPayments;
|
|
3294
|
-
const prior = Math.max(0,
|
|
3532
|
+
const prior = Math.max(0, cleanNumericAmount(effectivePriorInput));
|
|
3295
3533
|
const cumulative = prior + rawInterestAmount;
|
|
3296
3534
|
|
|
3297
3535
|
// Temporal Resolution: "Credit or Payment, whichever is earlier"
|
|
@@ -3315,20 +3553,35 @@ export function evaluateSection194A({
|
|
|
3315
3553
|
}
|
|
3316
3554
|
|
|
3317
3555
|
const isAct2025 = Boolean(effectiveDate && String(effectiveDate).slice(0, 10) >= '2026-04-01');
|
|
3318
|
-
const governingSection = isAct2025
|
|
3319
|
-
? "Income-tax Act, 2025 Section 393(1) Table 4, Item (i)"
|
|
3320
|
-
: "Income-tax Act, 1961 Section 194A";
|
|
3321
3556
|
|
|
3322
3557
|
// Payee Category & Threshold Resolution
|
|
3323
3558
|
const isExemptInstitution = payeeCategory === 'EXEMPT_INSTITUTION';
|
|
3324
3559
|
const isFirmToPartner = payeeCategory === 'FIRM_TO_PARTNER';
|
|
3325
3560
|
const isStatutoryExclusion = isExemptInstitution || isFirmToPartner;
|
|
3326
3561
|
|
|
3327
|
-
let INTEREST_THRESHOLD
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3562
|
+
let INTEREST_THRESHOLD;
|
|
3563
|
+
let governingSection;
|
|
3564
|
+
|
|
3565
|
+
if (isAct2025) {
|
|
3566
|
+
if (payeeCategory === 'SENIOR_CITIZEN_DEPOSIT') {
|
|
3567
|
+
INTEREST_THRESHOLD = 100000;
|
|
3568
|
+
governingSection = "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 5(ii)";
|
|
3569
|
+
} else if (payeeCategory === 'BANKING_OR_COOPERATIVE') {
|
|
3570
|
+
INTEREST_THRESHOLD = 50000;
|
|
3571
|
+
governingSection = "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 5(ii)";
|
|
3572
|
+
} else {
|
|
3573
|
+
INTEREST_THRESHOLD = 10000;
|
|
3574
|
+
governingSection = "Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 5(iii)";
|
|
3575
|
+
}
|
|
3576
|
+
} else {
|
|
3577
|
+
if (payeeCategory === 'SENIOR_CITIZEN_DEPOSIT') {
|
|
3578
|
+
INTEREST_THRESHOLD = 50000;
|
|
3579
|
+
} else if (payeeCategory === 'BANKING_OR_COOPERATIVE') {
|
|
3580
|
+
INTEREST_THRESHOLD = 40000;
|
|
3581
|
+
} else {
|
|
3582
|
+
INTEREST_THRESHOLD = 5000;
|
|
3583
|
+
}
|
|
3584
|
+
governingSection = "Income-tax Act, 1961 Section 194A";
|
|
3332
3585
|
}
|
|
3333
3586
|
|
|
3334
3587
|
const isThresholdBreached = !isStatutoryExclusion && (cumulative > INTEREST_THRESHOLD);
|
|
@@ -3480,14 +3733,14 @@ export function evaluateSection194O({
|
|
|
3480
3733
|
certificatePriorUtilizedInr = 0,
|
|
3481
3734
|
certificateSection = null
|
|
3482
3735
|
} = {}) {
|
|
3483
|
-
const rawGrossSale =
|
|
3484
|
-
const deemedDirectPayment = Math.max(0,
|
|
3736
|
+
const rawGrossSale = assertMandatoryNumeric(grossSaleAmount, 'grossSaleAmount', 'evaluateSection194O');
|
|
3737
|
+
const deemedDirectPayment = Math.max(0, cleanNumericAmount(directPurchaserPaymentDeemedInr));
|
|
3485
3738
|
const totalTransactionGmv = rawGrossSale + deemedDirectPayment;
|
|
3486
3739
|
|
|
3487
3740
|
const effectivePriorInput = priorPaymentsInCurrentFyInr !== null && priorPaymentsInCurrentFyInr !== undefined
|
|
3488
3741
|
? priorPaymentsInCurrentFyInr
|
|
3489
3742
|
: priorFyCategoryPayments;
|
|
3490
|
-
const prior = Math.max(0,
|
|
3743
|
+
const prior = Math.max(0, cleanNumericAmount(effectivePriorInput));
|
|
3491
3744
|
const cumulative = prior + totalTransactionGmv;
|
|
3492
3745
|
|
|
3493
3746
|
// Temporal Resolution: "Credit or Payment, whichever is earlier"
|
|
@@ -3630,7 +3883,7 @@ export function evaluateSection194O({
|
|
|
3630
3883
|
* - Dual-Act 2025: Section 393(1) Table 4 Item (q).
|
|
3631
3884
|
*/
|
|
3632
3885
|
export function evaluateSection194IA({
|
|
3633
|
-
contractualConsiderationInr
|
|
3886
|
+
contractualConsiderationInr,
|
|
3634
3887
|
stampDutyValueInr = null,
|
|
3635
3888
|
incidentalChargesInr = 0,
|
|
3636
3889
|
isAgriculturalLand = false,
|
|
@@ -3643,9 +3896,11 @@ export function evaluateSection194IA({
|
|
|
3643
3896
|
creditDate = null,
|
|
3644
3897
|
paymentDate = null
|
|
3645
3898
|
} = {}) {
|
|
3899
|
+
const rawBaseConsideration = Math.max(0, assertMandatoryNumeric(contractualConsiderationInr, 'contractualConsiderationInr', 'evaluateSection194IA'));
|
|
3900
|
+
|
|
3646
3901
|
// Exclusion 1: Agricultural Land
|
|
3647
3902
|
if (isAgriculturalLand) {
|
|
3648
|
-
const grossAmount = roundToPaisa(
|
|
3903
|
+
const grossAmount = roundToPaisa(rawBaseConsideration + Math.max(0, cleanNumericAmount(incidentalChargesInr)));
|
|
3649
3904
|
return {
|
|
3650
3905
|
status: 'SUCCESS',
|
|
3651
3906
|
statutoryDetermination: 'EXEMPT_AGRICULTURAL_LAND',
|
|
@@ -3673,12 +3928,11 @@ export function evaluateSection194IA({
|
|
|
3673
3928
|
};
|
|
3674
3929
|
}
|
|
3675
3930
|
|
|
3676
|
-
const
|
|
3677
|
-
const rawIncidental = Math.max(0, Number(incidentalChargesInr) || 0);
|
|
3931
|
+
const rawIncidental = Math.max(0, cleanNumericAmount(incidentalChargesInr));
|
|
3678
3932
|
const totalContractualConsideration = roundToPaisa(rawBaseConsideration + rawIncidental);
|
|
3679
3933
|
|
|
3680
|
-
const effectiveSdv = (stampDutyValueInr !== null && stampDutyValueInr !== undefined
|
|
3681
|
-
? roundToPaisa(Math.max(0,
|
|
3934
|
+
const effectiveSdv = (stampDutyValueInr !== null && stampDutyValueInr !== undefined)
|
|
3935
|
+
? roundToPaisa(Math.max(0, cleanNumericAmount(stampDutyValueInr)))
|
|
3682
3936
|
: totalContractualConsideration;
|
|
3683
3937
|
|
|
3684
3938
|
// Finance Act 2022 Amendment: Higher of consideration or Stamp Duty Value
|
|
@@ -3710,7 +3964,7 @@ export function evaluateSection194IA({
|
|
|
3710
3964
|
|
|
3711
3965
|
const isAct2025 = Boolean(effectiveDate && String(effectiveDate).slice(0, 10) >= '2026-04-01');
|
|
3712
3966
|
const governingSection = isAct2025
|
|
3713
|
-
? 'Income-tax Act, 2025 Section 393(1) Table
|
|
3967
|
+
? 'Income-tax Act, 2025 Section 393(1), Table 1, Sl. No. 3(i)'
|
|
3714
3968
|
: 'Income-tax Act, 1961 Section 194-IA';
|
|
3715
3969
|
|
|
3716
3970
|
if (!isThresholdMet) {
|
|
@@ -3804,7 +4058,7 @@ export function calculateAdvanceTaxAndInterest({
|
|
|
3804
4058
|
suddenIncomeExceptions = null,
|
|
3805
4059
|
assessmentDate = null,
|
|
3806
4060
|
isAct2025 = null
|
|
3807
|
-
}) {
|
|
4061
|
+
} = {}) {
|
|
3808
4062
|
if (estimatedAssessedTaxInr === undefined || estimatedAssessedTaxInr === null) {
|
|
3809
4063
|
return {
|
|
3810
4064
|
status: 'INDETERMINATE',
|
|
@@ -3817,12 +4071,12 @@ export function calculateAdvanceTaxAndInterest({
|
|
|
3817
4071
|
};
|
|
3818
4072
|
}
|
|
3819
4073
|
|
|
3820
|
-
const estimatedTax =
|
|
3821
|
-
const credits =
|
|
3822
|
-
if (
|
|
4074
|
+
const estimatedTax = cleanNumericAmount(estimatedAssessedTaxInr);
|
|
4075
|
+
const credits = cleanNumericAmount(tdsTcsCreditInr);
|
|
4076
|
+
if (estimatedTax < 0) {
|
|
3823
4077
|
throw new Error('[@mudraforge/statutory] Invalid estimatedAssessedTaxInr: expected non-negative finite number.');
|
|
3824
4078
|
}
|
|
3825
|
-
if (
|
|
4079
|
+
if (credits < 0) {
|
|
3826
4080
|
throw new Error('[@mudraforge/statutory] Invalid tdsTcsCreditInr: expected non-negative finite number.');
|
|
3827
4081
|
}
|
|
3828
4082
|
|
|
@@ -3867,10 +4121,10 @@ export function calculateAdvanceTaxAndInterest({
|
|
|
3867
4121
|
}
|
|
3868
4122
|
|
|
3869
4123
|
// Cumulative installments calculation (15%, 45%, 75%, 100%)
|
|
3870
|
-
const pQ1 = Math.max(0,
|
|
3871
|
-
const pQ2 = Math.max(0,
|
|
3872
|
-
const pQ3 = Math.max(0,
|
|
3873
|
-
const pQ4 = Math.max(0,
|
|
4124
|
+
const pQ1 = Math.max(0, cleanNumericAmount(paidQ1Inr));
|
|
4125
|
+
const pQ2 = Math.max(0, cleanNumericAmount(paidQ2Inr));
|
|
4126
|
+
const pQ3 = Math.max(0, cleanNumericAmount(paidQ3Inr));
|
|
4127
|
+
const pQ4 = Math.max(0, cleanNumericAmount(paidQ4Inr));
|
|
3874
4128
|
|
|
3875
4129
|
const cumPaidQ1 = pQ1;
|
|
3876
4130
|
const cumPaidQ2 = cumPaidQ1 + pQ2;
|
|
@@ -4079,9 +4333,428 @@ export function calculateAdvanceTaxAndInterest({
|
|
|
4079
4333
|
* CGST Rules 2017: Rule 42 (Inputs & Services) & Rule 43 (Capital Goods)
|
|
4080
4334
|
* ═══════════════════════════════════════════════════════════════════════════
|
|
4081
4335
|
*
|
|
4082
|
-
* Implements monthly
|
|
4083
|
-
*
|
|
4084
|
-
*
|
|
4336
|
+
* Implements deterministic apportionment, monthly provisional reversals (Table 4(B)(1)),
|
|
4337
|
+
* annual true-up reconciliations before 30th November (Notification No. 18/2022-CT),
|
|
4338
|
+
* Section 50(1) 18% statutory interest on shortfalls, Table 4(D)(1) reclaim mechanics,
|
|
4339
|
+
* and 60-month capital goods asset amortisation and conversions.
|
|
4340
|
+
*/
|
|
4341
|
+
|
|
4342
|
+
/**
|
|
4343
|
+
* GST RULE 42: INPUTS & INPUT SERVICES COMMON ITC REVERSAL CALCULATOR
|
|
4344
|
+
* CGST Act, 2017: Section 17(1) & 17(2) read with Rule 42
|
|
4345
|
+
*
|
|
4346
|
+
* Segregates total input tax (T) into:
|
|
4347
|
+
* - Specific ineligibles: T1 (non-business), T2 (exempt), T3 (blocked u/s 17(5))
|
|
4348
|
+
* - Credited to Electronic Credit Ledger: C1 = T - (T1 + T2 + T3)
|
|
4349
|
+
* - Specific eligible: T4 (taxable & zero-rated supplies)
|
|
4350
|
+
* - Common Credit: C2 = C1 - T4
|
|
4351
|
+
* - Monthly Ineligible Reversals: D1 = (E / F) * C2, D2 = 5% of C2 (non-business)
|
|
4352
|
+
* - Eligible Common Credit: C3 = C2 - (D1 + D2)
|
|
4353
|
+
* - Net Eligible Inputs Credit: G = T4 + C3 = C1 - (D1 + D2)
|
|
4354
|
+
*/
|
|
4355
|
+
export function calculateRule42InputServiceReversal({
|
|
4356
|
+
taxPeriod = 'CURRENT_PERIOD',
|
|
4357
|
+
totalItcInr,
|
|
4358
|
+
t1NonBusinessInr = 0,
|
|
4359
|
+
t2ExemptInr = 0,
|
|
4360
|
+
t3BlockedInr = 0,
|
|
4361
|
+
t4TaxableInr = 0,
|
|
4362
|
+
exemptTurnoverInr = 0,
|
|
4363
|
+
totalTurnoverInr = 0,
|
|
4364
|
+
hasNonBusinessUse = true,
|
|
4365
|
+
precedingExemptTurnoverInr = 0,
|
|
4366
|
+
precedingTotalTurnoverInr = 0
|
|
4367
|
+
} = {}) {
|
|
4368
|
+
if (totalItcInr === undefined || totalItcInr === null) {
|
|
4369
|
+
throw new Error('[@mudraforge/statutory] calculateRule42InputServiceReversal: totalItcInr is required.');
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
const T = roundToPaisa(Math.max(0, cleanNumericAmount(totalItcInr)));
|
|
4373
|
+
const T1 = roundToPaisa(Math.max(0, cleanNumericAmount(t1NonBusinessInr)));
|
|
4374
|
+
const T2 = roundToPaisa(Math.max(0, cleanNumericAmount(t2ExemptInr)));
|
|
4375
|
+
const T3 = roundToPaisa(Math.max(0, cleanNumericAmount(t3BlockedInr)));
|
|
4376
|
+
const T4 = roundToPaisa(Math.max(0, cleanNumericAmount(t4TaxableInr)));
|
|
4377
|
+
const E = Math.max(0, cleanNumericAmount(exemptTurnoverInr));
|
|
4378
|
+
const F = Math.max(0, cleanNumericAmount(totalTurnoverInr));
|
|
4379
|
+
|
|
4380
|
+
const directIneligible = roundToPaisa(T1 + T2 + T3);
|
|
4381
|
+
if (directIneligible > T) {
|
|
4382
|
+
throw new Error(`[@mudraforge/statutory] Sum of direct ineligible credits (T1 + T2 + T3 = ₹${directIneligible}) cannot exceed total ITC (T = ₹${T}).`);
|
|
4383
|
+
}
|
|
4384
|
+
|
|
4385
|
+
// Step 2: C1 - Amount credited to Electronic Credit Ledger
|
|
4386
|
+
const C1 = roundToPaisa(T - directIneligible);
|
|
4387
|
+
|
|
4388
|
+
// Step 3: T4 check
|
|
4389
|
+
if (T4 > C1) {
|
|
4390
|
+
throw new Error(`[@mudraforge/statutory] Exclusive taxable credit T4 (₹${T4}) cannot exceed available credit C1 (₹${C1}).`);
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
// Step 4: C2 - Common Input Credit
|
|
4394
|
+
const C2 = roundToPaisa(C1 - T4);
|
|
4395
|
+
|
|
4396
|
+
// Step 5: Turnover Apportionment (E / F)
|
|
4397
|
+
let exemptRatio = 0;
|
|
4398
|
+
let turnoverBasis = 'CURRENT_TAX_PERIOD';
|
|
4399
|
+
|
|
4400
|
+
if (C2 > 0) {
|
|
4401
|
+
if (F > 0) {
|
|
4402
|
+
exemptRatio = Math.min(1.0, E / F);
|
|
4403
|
+
} else if (E === 0 && F === 0) {
|
|
4404
|
+
const precE = Math.max(0, cleanNumericAmount(precedingExemptTurnoverInr));
|
|
4405
|
+
const precF = Math.max(0, cleanNumericAmount(precedingTotalTurnoverInr));
|
|
4406
|
+
if (precF > 0) {
|
|
4407
|
+
exemptRatio = Math.min(1.0, precE / precF);
|
|
4408
|
+
turnoverBasis = 'PRECEDING_TAX_PERIOD_PROVISO';
|
|
4409
|
+
} else {
|
|
4410
|
+
exemptRatio = 0;
|
|
4411
|
+
turnoverBasis = 'ZERO_TURNOVER_NO_REVERSAL';
|
|
4412
|
+
}
|
|
4413
|
+
} else {
|
|
4414
|
+
// E > 0 but F <= 0 (defensive full exempt cap)
|
|
4415
|
+
exemptRatio = 1.0;
|
|
4416
|
+
turnoverBasis = 'DEFENSIVE_FULL_EXEMPT_CAP';
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
const D1 = C2 > 0 ? roundToPaisa(C2 * exemptRatio) : 0;
|
|
4421
|
+
const D2 = (C2 > 0 && hasNonBusinessUse) ? roundToPaisa(C2 * 0.05) : 0;
|
|
4422
|
+
const rule42Reversal = roundToPaisa(D1 + D2);
|
|
4423
|
+
const C3 = roundToPaisa(Math.max(0, C2 - rule42Reversal));
|
|
4424
|
+
const netEligibleInputsItc = roundToPaisa(T4 + C3);
|
|
4425
|
+
|
|
4426
|
+
return {
|
|
4427
|
+
status: 'SUCCESS',
|
|
4428
|
+
statutoryDetermination: 'RULE_42_APPORTIONMENT_CALCULATED',
|
|
4429
|
+
governingRules: 'Central Goods and Services Tax Rules, 2017: Rule 42 (Inputs and Input Services)',
|
|
4430
|
+
taxPeriod,
|
|
4431
|
+
totalItcT: T,
|
|
4432
|
+
directIneligibleCredits: {
|
|
4433
|
+
exclusiveNonBusinessT1: T1,
|
|
4434
|
+
exclusiveExemptT2: T2,
|
|
4435
|
+
blockedSection17_5_T3: T3,
|
|
4436
|
+
sumIneligible: directIneligible
|
|
4437
|
+
},
|
|
4438
|
+
creditedToElectronicCreditLedgerC1: C1,
|
|
4439
|
+
exclusiveTaxableT4: T4,
|
|
4440
|
+
commonCreditC2: C2,
|
|
4441
|
+
exemptTurnoverE: E,
|
|
4442
|
+
totalTurnoverF: F,
|
|
4443
|
+
turnoverRatioE_F: Number(exemptRatio.toFixed(6)),
|
|
4444
|
+
turnoverBasis,
|
|
4445
|
+
reversals: {
|
|
4446
|
+
ineligibleExemptCommonD1: D1,
|
|
4447
|
+
nonBusinessCommonD2: D2,
|
|
4448
|
+
totalRule42Reversal: rule42Reversal
|
|
4449
|
+
},
|
|
4450
|
+
eligibleCommonCreditC3: C3,
|
|
4451
|
+
netEligibleInputsItc,
|
|
4452
|
+
gstr3bReporting: {
|
|
4453
|
+
table4A5GrossIngestion: T,
|
|
4454
|
+
table4B1PermanentReversal: roundToPaisa(directIneligible + rule42Reversal),
|
|
4455
|
+
table4B1Rule42OnlyReversal: rule42Reversal,
|
|
4456
|
+
table4CNetItcAvailable: netEligibleInputsItc
|
|
4457
|
+
},
|
|
4458
|
+
isAuthorizedToDisburse: false,
|
|
4459
|
+
statutoryRationale: `Rule 42 credited ₹${C1.toLocaleString('en-IN')} to ledger (after deducting ₹${directIneligible.toLocaleString('en-IN')} direct ineligible). Identified ₹${C2.toLocaleString('en-IN')} common credit, requiring ₹${rule42Reversal.toLocaleString('en-IN')} reversal (D1 exempt: ₹${D1}, D2 non-business: ₹${D2}). Net eligible credit: ₹${netEligibleInputsItc.toLocaleString('en-IN')}. Permanent reversal must be reported in GSTR-3B Table 4(B)(1).`
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
/**
|
|
4464
|
+
* GST RULE 42(2): ANNUAL COMMON CREDIT TRUE-UP & RECONCILIATION ENGINE
|
|
4465
|
+
* CGST Rules, 2017: Rule 42(2) read with Section 16(4) and Section 50(1)
|
|
4466
|
+
* Amended by Finance Act, 2022 & Notification No. 18/2022-Central Tax w.e.f. 01.10.2022
|
|
4467
|
+
*
|
|
4468
|
+
* Annual Cutoff: 30th November following the end of the financial year.
|
|
4469
|
+
* - Shortfall Reversal: Payable with 18% p.a. interest under Section 50(1) from 1st April of succeeding FY.
|
|
4470
|
+
* - Excess Reversal: Reclaimable as credit in Form GSTR-3B Table 4(A)(5) and Table 4(D)(1) before 30th November.
|
|
4471
|
+
*/
|
|
4472
|
+
export function reconcileRule42AnnualTrueUp({
|
|
4473
|
+
financialYear = '2024-25',
|
|
4474
|
+
annualCommonCreditC2Inr,
|
|
4475
|
+
annualExemptTurnoverInr = 0,
|
|
4476
|
+
annualTotalTurnoverInr = 0,
|
|
4477
|
+
cumulativeMonthlyReversalsInr,
|
|
4478
|
+
trueUpExecutionDate = null,
|
|
4479
|
+
hasNonBusinessUse = true,
|
|
4480
|
+
annualInterestRatePercent = 18.0,
|
|
4481
|
+
isRealEstateProject = false
|
|
4482
|
+
} = {}) {
|
|
4483
|
+
if (annualCommonCreditC2Inr === undefined || annualCommonCreditC2Inr === null) {
|
|
4484
|
+
throw new Error('[@mudraforge/statutory] reconcileRule42AnnualTrueUp: annualCommonCreditC2Inr is required.');
|
|
4485
|
+
}
|
|
4486
|
+
if (cumulativeMonthlyReversalsInr === undefined || cumulativeMonthlyReversalsInr === null) {
|
|
4487
|
+
throw new Error('[@mudraforge/statutory] reconcileRule42AnnualTrueUp: cumulativeMonthlyReversalsInr is required.');
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4490
|
+
const C2 = roundToPaisa(Math.max(0, cleanNumericAmount(annualCommonCreditC2Inr)));
|
|
4491
|
+
const priorReversals = roundToPaisa(Math.max(0, cleanNumericAmount(cumulativeMonthlyReversalsInr)));
|
|
4492
|
+
const annualE = Math.max(0, cleanNumericAmount(annualExemptTurnoverInr));
|
|
4493
|
+
const annualF = Math.max(0, cleanNumericAmount(annualTotalTurnoverInr));
|
|
4494
|
+
|
|
4495
|
+
// Derive succeeding FY start date & statutory cutoff (30th November)
|
|
4496
|
+
let endYear = 2025;
|
|
4497
|
+
const fyMatch = String(financialYear).match(/^(\d{4})-(\d{2,4})$/);
|
|
4498
|
+
if (fyMatch) {
|
|
4499
|
+
const y1 = parseInt(fyMatch[1], 10);
|
|
4500
|
+
endYear = y1 + 1;
|
|
4501
|
+
}
|
|
4502
|
+
const succeedingFyStartDate = `${endYear}-04-01`;
|
|
4503
|
+
const statutoryCutoffDate = `${endYear}-11-30`;
|
|
4504
|
+
|
|
4505
|
+
const execDateIso = trueUpExecutionDate ? String(trueUpExecutionDate).slice(0, 10) : statutoryCutoffDate;
|
|
4506
|
+
const isFiledBeforeCutoff = execDateIso <= statutoryCutoffDate;
|
|
4507
|
+
|
|
4508
|
+
const annualRatio = annualF > 0 ? Math.min(1.0, annualE / annualF) : 0;
|
|
4509
|
+
const finalAnnualD1 = roundToPaisa(C2 * annualRatio);
|
|
4510
|
+
const finalAnnualD2 = hasNonBusinessUse ? roundToPaisa(C2 * 0.05) : 0;
|
|
4511
|
+
const finalAnnualReversalTotal = roundToPaisa(finalAnnualD1 + finalAnnualD2);
|
|
4512
|
+
|
|
4513
|
+
const differential = roundToPaisa(finalAnnualReversalTotal - priorReversals);
|
|
4514
|
+
|
|
4515
|
+
let reconciliationOutcome = 'BALANCED_ZERO_VARIANCE';
|
|
4516
|
+
let shortfallAmountInr = 0;
|
|
4517
|
+
let excessReversedInr = 0;
|
|
4518
|
+
let interestPayableInr = 0;
|
|
4519
|
+
let daysElapsed = 0;
|
|
4520
|
+
let statutoryAction = 'Annual true-up precisely matches cumulative monthly reversals. No adjustment required.';
|
|
4521
|
+
|
|
4522
|
+
if (differential > 0) {
|
|
4523
|
+
reconciliationOutcome = 'SHORT_REVERSAL_PAYMENT_DUE';
|
|
4524
|
+
shortfallAmountInr = differential;
|
|
4525
|
+
|
|
4526
|
+
// Interest under Section 50(1) from 1st April of succeeding FY
|
|
4527
|
+
const dStart = new Date(succeedingFyStartDate + 'T00:00:00.000Z').getTime();
|
|
4528
|
+
const dExec = new Date(execDateIso + 'T00:00:00.000Z').getTime();
|
|
4529
|
+
daysElapsed = Math.max(0, Math.floor((dExec - dStart) / (1000 * 60 * 60 * 24)));
|
|
4530
|
+
const rateFrac = (Number(annualInterestRatePercent) || 18.0) / 100;
|
|
4531
|
+
interestPayableInr = roundToPaisa(shortfallAmountInr * rateFrac * (daysElapsed / 365));
|
|
4532
|
+
|
|
4533
|
+
statutoryAction = `Shortfall of ₹${shortfallAmountInr.toLocaleString('en-IN')} must be added to output tax liability / reversed in GSTR-3B Table 4(B)(1) on or before ${statutoryCutoffDate} along with ₹${interestPayableInr.toLocaleString('en-IN')} interest under Section 50(1) (${daysElapsed} days @ ${annualInterestRatePercent}% p.a.).`;
|
|
4534
|
+
} else if (differential < 0) {
|
|
4535
|
+
reconciliationOutcome = 'EXCESS_REVERSAL_RECLAIM_PERMITTED';
|
|
4536
|
+
excessReversedInr = roundToPaisa(Math.abs(differential));
|
|
4537
|
+
statutoryAction = `Excess reversal of ₹${excessReversedInr.toLocaleString('en-IN')} may be reclaimed as eligible ITC in Form GSTR-3B Table 4(A)(5) and reported in Table 4(D)(1) on or before ${statutoryCutoffDate}.`;
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
return {
|
|
4541
|
+
status: 'SUCCESS',
|
|
4542
|
+
statutoryDetermination: 'ANNUAL_TRUE_UP_RECONCILED',
|
|
4543
|
+
governingRules: 'Central Goods and Services Tax Rules, 2017: Rule 42(2) & Finance Act, 2022',
|
|
4544
|
+
financialYear,
|
|
4545
|
+
statutoryCutoffDate,
|
|
4546
|
+
succeedingFyStartDate,
|
|
4547
|
+
trueUpExecutionDate: execDateIso,
|
|
4548
|
+
isFiledBeforeCutoff,
|
|
4549
|
+
annualCommonCreditC2Inr: C2,
|
|
4550
|
+
annualExemptTurnoverInr: annualE,
|
|
4551
|
+
annualTotalTurnoverInr: annualF,
|
|
4552
|
+
annualExemptRatio: Number(annualRatio.toFixed(6)),
|
|
4553
|
+
finalAnnualReversals: {
|
|
4554
|
+
finalAnnualD1ExemptInr: finalAnnualD1,
|
|
4555
|
+
finalAnnualD2NonBusinessInr: finalAnnualD2,
|
|
4556
|
+
finalAnnualRequiredTotalInr: finalAnnualReversalTotal
|
|
4557
|
+
},
|
|
4558
|
+
cumulativeMonthlyReversalsMadeInr: priorReversals,
|
|
4559
|
+
differentialAdjustmentInr: differential,
|
|
4560
|
+
reconciliationOutcome,
|
|
4561
|
+
shortfallAmountInr,
|
|
4562
|
+
excessReversedInr,
|
|
4563
|
+
interestAssessment: {
|
|
4564
|
+
interestRatePercent: annualInterestRatePercent,
|
|
4565
|
+
interestCommencementDate: succeedingFyStartDate,
|
|
4566
|
+
interestEndDate: execDateIso,
|
|
4567
|
+
daysElapsed,
|
|
4568
|
+
interestPayableInr
|
|
4569
|
+
},
|
|
4570
|
+
isRealEstateProject: !!isRealEstateProject,
|
|
4571
|
+
statutoryAction,
|
|
4572
|
+
isAuthorizedToDisburse: false,
|
|
4573
|
+
statutoryRationale: `Annual common credit ₹${C2.toLocaleString('en-IN')} with annual exempt ratio ${annualRatio.toFixed(4)} requires aggregate annual reversal of ₹${finalAnnualReversalTotal.toLocaleString('en-IN')}. Cumulative monthly reversals made: ₹${priorReversals.toLocaleString('en-IN')}. Outcome: ${reconciliationOutcome} (${differential > 0 ? `Shortfall ₹${shortfallAmountInr} + Sec 50(1) Interest ₹${interestPayableInr}` : (differential < 0 ? `Reclaimable ₹${excessReversedInr}` : 'Zero Variance')}).`
|
|
4574
|
+
};
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
/**
|
|
4578
|
+
* GST RULE 43: CAPITAL GOODS COMMON ITC AMORTISATION & REVERSAL CALCULATOR
|
|
4579
|
+
* CGST Act, 2017: Section 17(1) & 17(2) read with Rule 43
|
|
4580
|
+
*
|
|
4581
|
+
* Implements:
|
|
4582
|
+
* - 60-month statutory useful life tracking for each capital asset.
|
|
4583
|
+
* - Monthly credit slice: Tm = Tc / 60.
|
|
4584
|
+
* - Aggregate monthly common credit: Tr = sum(Tm).
|
|
4585
|
+
* - Monthly exempt reversal: Te = (E / F) * Tr (added to output tax liability in Table 4(B)(1)).
|
|
4586
|
+
* - Asset conversion: 5% reduction per quarter under Rule 43(1)(c) for exclusive-to-common transitions.
|
|
4587
|
+
*/
|
|
4588
|
+
export function calculateRule43CapitalGoodsReversal({
|
|
4589
|
+
taxPeriod = 'CURRENT_PERIOD',
|
|
4590
|
+
taxPeriodDate = new Date().toISOString().slice(0, 10),
|
|
4591
|
+
exemptTurnoverInr = 0,
|
|
4592
|
+
totalTurnoverInr = 0,
|
|
4593
|
+
capitalGoodsList = [],
|
|
4594
|
+
precedingExemptTurnoverInr = 0,
|
|
4595
|
+
precedingTotalTurnoverInr = 0
|
|
4596
|
+
} = {}) {
|
|
4597
|
+
if (!Array.isArray(capitalGoodsList)) {
|
|
4598
|
+
throw new Error('[@mudraforge/statutory] calculateRule43CapitalGoodsReversal: capitalGoodsList must be an array.');
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
const E = Math.max(0, cleanNumericAmount(exemptTurnoverInr));
|
|
4602
|
+
const F = Math.max(0, cleanNumericAmount(totalTurnoverInr));
|
|
4603
|
+
|
|
4604
|
+
let exemptRatio = 0;
|
|
4605
|
+
let turnoverBasis = 'CURRENT_TAX_PERIOD';
|
|
4606
|
+
|
|
4607
|
+
if (F > 0) {
|
|
4608
|
+
exemptRatio = Math.min(1.0, E / F);
|
|
4609
|
+
} else if (E === 0 && F === 0) {
|
|
4610
|
+
const precE = Math.max(0, cleanNumericAmount(precedingExemptTurnoverInr));
|
|
4611
|
+
const precF = Math.max(0, cleanNumericAmount(precedingTotalTurnoverInr));
|
|
4612
|
+
if (precF > 0) {
|
|
4613
|
+
exemptRatio = Math.min(1.0, precE / precF);
|
|
4614
|
+
turnoverBasis = 'PRECEDING_TAX_PERIOD_PROVISO';
|
|
4615
|
+
} else {
|
|
4616
|
+
exemptRatio = 0;
|
|
4617
|
+
turnoverBasis = 'ZERO_TURNOVER_NO_REVERSAL';
|
|
4618
|
+
}
|
|
4619
|
+
} else {
|
|
4620
|
+
exemptRatio = 1.0;
|
|
4621
|
+
turnoverBasis = 'DEFENSIVE_FULL_EXEMPT_CAP';
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4624
|
+
const DEEMED_USEFUL_LIFE_MONTHS = 60;
|
|
4625
|
+
const calcDate = new Date(String(taxPeriodDate).slice(0, 10) + 'T00:00:00.000Z');
|
|
4626
|
+
|
|
4627
|
+
let totalCommonTc = 0;
|
|
4628
|
+
let totalMonthlySliceTr = 0;
|
|
4629
|
+
const assetSchedules = [];
|
|
4630
|
+
|
|
4631
|
+
for (const asset of capitalGoodsList) {
|
|
4632
|
+
const assetId = asset.assetId || 'ASSET-UNKNOWN';
|
|
4633
|
+
const description = asset.description || 'Capital Good';
|
|
4634
|
+
const rawItc = roundToPaisa(Math.max(0, cleanNumericAmount(asset.itcAmountInr || asset.taxAmount)));
|
|
4635
|
+
const category = String(asset.category || asset.useCategory || 'COMMON').toUpperCase();
|
|
4636
|
+
const invoiceDateIso = asset.invoiceDate ? String(asset.invoiceDate).slice(0, 10) : taxPeriodDate;
|
|
4637
|
+
|
|
4638
|
+
if (category === 'EXEMPT_ONLY') {
|
|
4639
|
+
assetSchedules.push({
|
|
4640
|
+
assetId,
|
|
4641
|
+
description,
|
|
4642
|
+
category,
|
|
4643
|
+
invoiceDate: invoiceDateIso,
|
|
4644
|
+
originalItcInr: rawItc,
|
|
4645
|
+
effectiveCommonCreditInr: 0,
|
|
4646
|
+
monthlyCreditTmInr: 0,
|
|
4647
|
+
monthlyReversalTeInr: 0,
|
|
4648
|
+
status: 'EXEMPT_EXCLUDED_FROM_LEDGER',
|
|
4649
|
+
statutoryNote: 'Under Rule 43(1)(a), capital goods used exclusively for exempt supplies are not credited to Electronic Credit Ledger.'
|
|
4650
|
+
});
|
|
4651
|
+
continue;
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4654
|
+
if (category === 'TAXABLE_ONLY') {
|
|
4655
|
+
assetSchedules.push({
|
|
4656
|
+
assetId,
|
|
4657
|
+
description,
|
|
4658
|
+
category,
|
|
4659
|
+
invoiceDate: invoiceDateIso,
|
|
4660
|
+
originalItcInr: rawItc,
|
|
4661
|
+
effectiveCommonCreditInr: 0,
|
|
4662
|
+
monthlyCreditTmInr: 0,
|
|
4663
|
+
monthlyReversalTeInr: 0,
|
|
4664
|
+
status: 'TAXABLE_FULL_CREDIT_NO_REVERSAL',
|
|
4665
|
+
statutoryNote: 'Under Rule 43(1)(b), capital goods used exclusively for taxable supplies are credited in full with zero monthly reversal.'
|
|
4666
|
+
});
|
|
4667
|
+
continue;
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4670
|
+
// Category is COMMON (or converted to COMMON)
|
|
4671
|
+
let effectiveItc = rawItc;
|
|
4672
|
+
let quartersElapsed = 0;
|
|
4673
|
+
let reductionPercent = 0;
|
|
4674
|
+
|
|
4675
|
+
if (asset.priorCategory === 'EXEMPT_ONLY' && asset.conversionDate) {
|
|
4676
|
+
const convDateIso = String(asset.conversionDate).slice(0, 10);
|
|
4677
|
+
const dInv = new Date(invoiceDateIso + 'T00:00:00.000Z').getTime();
|
|
4678
|
+
const dConv = new Date(convDateIso + 'T00:00:00.000Z').getTime();
|
|
4679
|
+
const daysConv = Math.max(0, Math.floor((dConv - dInv) / (1000 * 60 * 60 * 24)));
|
|
4680
|
+
quartersElapsed = daysConv > 0 ? Math.ceil(daysConv / 91.25) : 0;
|
|
4681
|
+
reductionPercent = Math.min(100, quartersElapsed * 5);
|
|
4682
|
+
effectiveItc = roundToPaisa(rawItc * (1 - reductionPercent / 100));
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
// Calculate months elapsed from invoice date to calculation date
|
|
4686
|
+
const dInv = new Date(invoiceDateIso + 'T00:00:00.000Z');
|
|
4687
|
+
const monthsElapsed = (calcDate.getFullYear() - dInv.getFullYear()) * 12 + (calcDate.getMonth() - dInv.getMonth());
|
|
4688
|
+
|
|
4689
|
+
let monthlyCreditTm = 0;
|
|
4690
|
+
let monthlyReversalTe = 0;
|
|
4691
|
+
let status = 'ACTIVE_IN_COMMON_POOL';
|
|
4692
|
+
|
|
4693
|
+
if (monthsElapsed >= 0 && monthsElapsed < DEEMED_USEFUL_LIFE_MONTHS) {
|
|
4694
|
+
monthlyCreditTm = roundToPaisa(effectiveItc / DEEMED_USEFUL_LIFE_MONTHS);
|
|
4695
|
+
monthlyReversalTe = roundToPaisa(monthlyCreditTm * exemptRatio);
|
|
4696
|
+
totalCommonTc = roundToPaisa(totalCommonTc + effectiveItc);
|
|
4697
|
+
totalMonthlySliceTr = roundToPaisa(totalMonthlySliceTr + monthlyCreditTm);
|
|
4698
|
+
} else if (monthsElapsed >= DEEMED_USEFUL_LIFE_MONTHS) {
|
|
4699
|
+
status = 'EXPIRED_USEFUL_LIFE_60_MONTHS';
|
|
4700
|
+
} else {
|
|
4701
|
+
status = 'PRE_ACQUISITION_PERIOD';
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
assetSchedules.push({
|
|
4705
|
+
assetId,
|
|
4706
|
+
description,
|
|
4707
|
+
category: 'COMMON',
|
|
4708
|
+
invoiceDate: invoiceDateIso,
|
|
4709
|
+
originalItcInr: rawItc,
|
|
4710
|
+
effectiveCommonCreditInr: effectiveItc,
|
|
4711
|
+
reductionFactorAppliedPercent: reductionPercent,
|
|
4712
|
+
monthsElapsed,
|
|
4713
|
+
remainingUsefulLifeMonths: Math.max(0, DEEMED_USEFUL_LIFE_MONTHS - Math.max(0, monthsElapsed)),
|
|
4714
|
+
monthlyCreditTmInr: monthlyCreditTm,
|
|
4715
|
+
monthlyReversalTeInr: monthlyReversalTe,
|
|
4716
|
+
status,
|
|
4717
|
+
statutoryNote: status === 'ACTIVE_IN_COMMON_POOL'
|
|
4718
|
+
? `Active in common pool (Month ${monthsElapsed + 1}/60). Tm = ₹${monthlyCreditTm}, Te reversal = ₹${monthlyReversalTe}.`
|
|
4719
|
+
: (status === 'EXPIRED_USEFUL_LIFE_60_MONTHS' ? 'Deemed useful life of 60 months has expired. No further reversal required.' : 'Asset acquired after tax period.')
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
const totalPeriodReversalTe = roundToPaisa(totalMonthlySliceTr * exemptRatio);
|
|
4724
|
+
const netEligibleMonthlyCapitalItc = roundToPaisa(Math.max(0, totalMonthlySliceTr - totalPeriodReversalTe));
|
|
4725
|
+
|
|
4726
|
+
return {
|
|
4727
|
+
status: 'SUCCESS',
|
|
4728
|
+
statutoryDetermination: 'RULE_43_CAPITAL_GOODS_APPORTIONED',
|
|
4729
|
+
governingRules: 'Central Goods and Services Tax Rules, 2017: Rule 43 (Capital Goods)',
|
|
4730
|
+
taxPeriod,
|
|
4731
|
+
taxPeriodDate: String(taxPeriodDate).slice(0, 10),
|
|
4732
|
+
exemptTurnoverE: E,
|
|
4733
|
+
totalTurnoverF: F,
|
|
4734
|
+
turnoverRatioE_F: Number(exemptRatio.toFixed(6)),
|
|
4735
|
+
turnoverBasis,
|
|
4736
|
+
deemedUsefulLifeMonths: DEEMED_USEFUL_LIFE_MONTHS,
|
|
4737
|
+
summary: {
|
|
4738
|
+
totalAssetsAssessed: capitalGoodsList.length,
|
|
4739
|
+
activeCommonAssetsCount: assetSchedules.filter(a => a.status === 'ACTIVE_IN_COMMON_POOL').length,
|
|
4740
|
+
totalCommonCreditTcInr: totalCommonTc,
|
|
4741
|
+
monthlyCommonCreditSliceTrInr: totalMonthlySliceTr,
|
|
4742
|
+
monthlyReversalTeInr: totalPeriodReversalTe,
|
|
4743
|
+
netEligibleMonthlyCapitalItcInr: netEligibleMonthlyCapitalItc
|
|
4744
|
+
},
|
|
4745
|
+
gstr3bReporting: {
|
|
4746
|
+
reportingTable: 'Table 4(B)(1) (Permanent Reversals)',
|
|
4747
|
+
monthlyReversalAmountInr: totalPeriodReversalTe
|
|
4748
|
+
},
|
|
4749
|
+
assetSchedules,
|
|
4750
|
+
isAuthorizedToDisburse: false,
|
|
4751
|
+
statutoryRationale: `Assessed ${capitalGoodsList.length} capital goods. Common pool ITC (Tc): ₹${totalCommonTc.toLocaleString('en-IN')}, monthly slice (Tr): ₹${totalMonthlySliceTr.toLocaleString('en-IN')}. Monthly exempt reversal (Te = Tr * E/F): ₹${totalPeriodReversalTe.toLocaleString('en-IN')}. Reversal must be added to output tax liability / reported in Table 4(B)(1).`
|
|
4752
|
+
};
|
|
4753
|
+
}
|
|
4754
|
+
|
|
4755
|
+
/**
|
|
4756
|
+
* UNIFIED ORCHESTRATOR: GST RULE 42 & 43 COMBINED APPORTIONMENT
|
|
4757
|
+
* Backward-compatible facade coordinating inputs/services, capital goods, and annual true-ups.
|
|
4085
4758
|
*/
|
|
4086
4759
|
export function calculateGstItcApportionmentRule42_43({
|
|
4087
4760
|
taxPeriod = null,
|
|
@@ -4094,16 +4767,19 @@ export function calculateGstItcApportionmentRule42_43({
|
|
|
4094
4767
|
exclusiveTaxableT4 = 0,
|
|
4095
4768
|
exemptTurnoverE = 0,
|
|
4096
4769
|
totalTurnoverF = 0,
|
|
4770
|
+
hasNonBusinessUse = true,
|
|
4097
4771
|
// Rule 43 Inputs (Capital Goods):
|
|
4098
4772
|
capitalGoodsCommonCreditA = 0,
|
|
4099
4773
|
capitalGoodsPriorQuartersExempt = 0,
|
|
4774
|
+
capitalGoodsList = null,
|
|
4100
4775
|
// Annual True-Up Reconciliation parameters:
|
|
4101
4776
|
isAnnualTrueUp = false,
|
|
4102
4777
|
annualExemptTurnover = null,
|
|
4103
4778
|
annualTotalTurnover = null,
|
|
4104
4779
|
sumOfMonthlyReversalsD1 = 0,
|
|
4105
|
-
isRealEstateProject = false
|
|
4106
|
-
|
|
4780
|
+
isRealEstateProject = false,
|
|
4781
|
+
trueUpExecutionDate = null
|
|
4782
|
+
} = {}) {
|
|
4107
4783
|
if (totalInputTaxCreditT === undefined || totalInputTaxCreditT === null) {
|
|
4108
4784
|
return {
|
|
4109
4785
|
status: 'INDETERMINATE',
|
|
@@ -4116,121 +4792,101 @@ export function calculateGstItcApportionmentRule42_43({
|
|
|
4116
4792
|
};
|
|
4117
4793
|
}
|
|
4118
4794
|
|
|
4119
|
-
|
|
4120
|
-
const
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
// Step 1: Ineligible credits under Rule 42(1)(a)-(c)
|
|
4132
|
-
const ineligibleDirect = roundToPaisa(T1 + T2 + T3);
|
|
4133
|
-
if (ineligibleDirect > T) {
|
|
4134
|
-
throw new Error(`[@mudraforge/statutory] Sum of direct ineligible credits (T1 + T2 + T3 = ₹${ineligibleDirect}) cannot exceed total ITC (T = ₹${T}).`);
|
|
4135
|
-
}
|
|
4136
|
-
|
|
4137
|
-
// Step 2: C1 - Amount credited to Electronic Credit Ledger
|
|
4138
|
-
const C1 = roundToPaisa(T - ineligibleDirect);
|
|
4139
|
-
|
|
4140
|
-
// Step 3: T4 - Exclusive taxable supplies
|
|
4141
|
-
if (T4 > C1) {
|
|
4142
|
-
throw new Error(`[@mudraforge/statutory] Exclusive taxable credit T4 (₹${T4}) cannot exceed available credit C1 (₹${C1}).`);
|
|
4143
|
-
}
|
|
4144
|
-
|
|
4145
|
-
// Step 4: C2 - Common Input Credit
|
|
4146
|
-
const C2 = roundToPaisa(C1 - T4);
|
|
4147
|
-
|
|
4148
|
-
// Step 5: D1 - Ineligible common credit attributable to exempt supplies
|
|
4149
|
-
// Formula: D1 = (E / F) * C2
|
|
4150
|
-
let exemptRatio = 0;
|
|
4151
|
-
let D1 = 0;
|
|
4152
|
-
let turnoverBasis = 'TAX_PERIOD_TURNOVER';
|
|
4153
|
-
|
|
4154
|
-
if (C2 > 0) {
|
|
4155
|
-
if (F > 0) {
|
|
4156
|
-
exemptRatio = E / F;
|
|
4157
|
-
if (exemptRatio > 1) exemptRatio = 1; // Defensive cap: exempt cannot exceed total turnover
|
|
4158
|
-
D1 = roundToPaisa(C2 * exemptRatio);
|
|
4159
|
-
} else if (E === 0 && F === 0) {
|
|
4160
|
-
// No turnover in tax period; Rule 42 proviso: use last tax period with turnover
|
|
4161
|
-
turnoverBasis = 'ZERO_TURNOVER_DEFERRED_TO_PRECEDING_PERIOD';
|
|
4162
|
-
exemptRatio = 0;
|
|
4163
|
-
D1 = 0;
|
|
4164
|
-
} else {
|
|
4165
|
-
// E > 0 but F <= 0 (data anomaly)
|
|
4166
|
-
exemptRatio = 1.0;
|
|
4167
|
-
D1 = C2;
|
|
4168
|
-
}
|
|
4169
|
-
}
|
|
4170
|
-
|
|
4171
|
-
// Step 6: D2 - Ineligible common credit attributable to non-business use (5% of C2)
|
|
4172
|
-
const D2 = C2 > 0 ? roundToPaisa(C2 * 0.05) : 0;
|
|
4173
|
-
|
|
4174
|
-
// Step 7: C3 - Eligible common credit
|
|
4175
|
-
// Formula: C3 = C2 - (D1 + D2)
|
|
4176
|
-
const C3 = roundToPaisa(Math.max(0, C2 - (D1 + D2)));
|
|
4177
|
-
|
|
4178
|
-
// Total Rule 42 reversal added to output tax liability:
|
|
4179
|
-
const rule42Reversal = roundToPaisa(D1 + D2);
|
|
4180
|
-
|
|
4181
|
-
// Total net eligible input credit for the period (exclusive taxable T4 + eligible common C3):
|
|
4182
|
-
const netEligibleInputsItc = roundToPaisa(T4 + C3);
|
|
4183
|
-
|
|
4184
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
4185
|
-
// RULE 43: CAPITAL GOODS APPORTIONMENT
|
|
4186
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
4187
|
-
const rawA = Math.max(0, Number(capitalGoodsCommonCreditA || 0));
|
|
4188
|
-
const priorQ = Math.max(0, Number(capitalGoodsPriorQuartersExempt || 0));
|
|
4189
|
-
|
|
4190
|
-
// If capital goods were used exclusively for exempt/non-business and converted to common,
|
|
4191
|
-
// reduce credit by 5% per quarter of prior usage
|
|
4192
|
-
const reductionFactor = Math.min(1.0, priorQ * 0.05);
|
|
4193
|
-
const effectiveA = roundToPaisa(rawA * (1 - reductionFactor));
|
|
4795
|
+
// Step 1: Rule 42 Inputs & Input Services Evaluation
|
|
4796
|
+
const rule42Result = calculateRule42InputServiceReversal({
|
|
4797
|
+
taxPeriod: taxPeriod || 'CURRENT_MONTH',
|
|
4798
|
+
totalItcInr: totalInputTaxCreditT,
|
|
4799
|
+
t1NonBusinessInr: exclusiveNonBusinessT1,
|
|
4800
|
+
t2ExemptInr: exclusiveExemptT2,
|
|
4801
|
+
t3BlockedInr: blockedSection17_5_T3,
|
|
4802
|
+
t4TaxableInr: exclusiveTaxableT4,
|
|
4803
|
+
exemptTurnoverInr: exemptTurnoverE,
|
|
4804
|
+
totalTurnoverInr: totalTurnoverF,
|
|
4805
|
+
hasNonBusinessUse
|
|
4806
|
+
});
|
|
4194
4807
|
|
|
4195
|
-
//
|
|
4808
|
+
// Step 2: Rule 43 Capital Goods Evaluation
|
|
4809
|
+
let capitalGoodsBreakdown = null;
|
|
4810
|
+
let capitalGoodsReversalTe = 0;
|
|
4811
|
+
let monthlyCapitalGoodsCreditTm = 0;
|
|
4196
4812
|
const DEEMED_USEFUL_LIFE_MONTHS = 60;
|
|
4197
|
-
const monthlyCapitalGoodsCreditTm = rawA > 0 ? roundToPaisa(effectiveA / DEEMED_USEFUL_LIFE_MONTHS) : 0;
|
|
4198
4813
|
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4814
|
+
if (Array.isArray(capitalGoodsList) && capitalGoodsList.length > 0) {
|
|
4815
|
+
const rule43Result = calculateRule43CapitalGoodsReversal({
|
|
4816
|
+
taxPeriod: taxPeriod || 'CURRENT_MONTH',
|
|
4817
|
+
exemptTurnoverInr: exemptTurnoverE,
|
|
4818
|
+
totalTurnoverInr: totalTurnoverF,
|
|
4819
|
+
capitalGoodsList
|
|
4820
|
+
});
|
|
4821
|
+
capitalGoodsReversalTe = rule43Result.summary.monthlyReversalTeInr;
|
|
4822
|
+
monthlyCapitalGoodsCreditTm = rule43Result.summary.monthlyCommonCreditSliceTrInr;
|
|
4823
|
+
capitalGoodsBreakdown = {
|
|
4824
|
+
capitalGoodsCommonCreditA: rule43Result.summary.totalCommonCreditTcInr,
|
|
4825
|
+
deemedUsefulLifeMonths: DEEMED_USEFUL_LIFE_MONTHS,
|
|
4826
|
+
priorQuartersExempt: 0,
|
|
4827
|
+
reductionFactorAppliedPercent: 0,
|
|
4828
|
+
effectiveCommonCredit: rule43Result.summary.totalCommonCreditTcInr,
|
|
4829
|
+
monthlyCreditTm: monthlyCapitalGoodsCreditTm,
|
|
4830
|
+
monthlyReversalTe: capitalGoodsReversalTe,
|
|
4831
|
+
eligibleCapitalGoodsCreditMonthly: rule43Result.summary.netEligibleMonthlyCapitalItcInr,
|
|
4832
|
+
assetSchedules: rule43Result.assetSchedules
|
|
4833
|
+
};
|
|
4834
|
+
} else {
|
|
4835
|
+
const rawA = Math.max(0, Number(capitalGoodsCommonCreditA || 0));
|
|
4836
|
+
const priorQ = Math.max(0, Number(capitalGoodsPriorQuartersExempt || 0));
|
|
4837
|
+
const reductionFactor = Math.min(1.0, priorQ * 0.05);
|
|
4838
|
+
const effectiveA = roundToPaisa(rawA * (1 - reductionFactor));
|
|
4839
|
+
monthlyCapitalGoodsCreditTm = rawA > 0 ? roundToPaisa(effectiveA / DEEMED_USEFUL_LIFE_MONTHS) : 0;
|
|
4840
|
+
capitalGoodsReversalTe = monthlyCapitalGoodsCreditTm > 0 && Number(totalTurnoverF) > 0
|
|
4841
|
+
? roundToPaisa(monthlyCapitalGoodsCreditTm * rule42Result.turnoverRatioE_F)
|
|
4842
|
+
: 0;
|
|
4843
|
+
|
|
4844
|
+
capitalGoodsBreakdown = {
|
|
4845
|
+
capitalGoodsCommonCreditA: rawA,
|
|
4846
|
+
deemedUsefulLifeMonths: DEEMED_USEFUL_LIFE_MONTHS,
|
|
4847
|
+
priorQuartersExempt: priorQ,
|
|
4848
|
+
reductionFactorAppliedPercent: reductionFactor * 100,
|
|
4849
|
+
effectiveCommonCredit: effectiveA,
|
|
4850
|
+
monthlyCreditTm: monthlyCapitalGoodsCreditTm,
|
|
4851
|
+
monthlyReversalTe: capitalGoodsReversalTe,
|
|
4852
|
+
eligibleCapitalGoodsCreditMonthly: Math.max(0, monthlyCapitalGoodsCreditTm - capitalGoodsReversalTe)
|
|
4853
|
+
};
|
|
4203
4854
|
}
|
|
4204
4855
|
|
|
4205
|
-
const totalPeriodReversal = roundToPaisa(
|
|
4206
|
-
const totalNetEligibleItc = roundToPaisa(netEligibleInputsItc + Math.max(0, monthlyCapitalGoodsCreditTm - capitalGoodsReversalTe));
|
|
4856
|
+
const totalPeriodReversal = roundToPaisa(rule42Result.reversals.totalRule42Reversal + capitalGoodsReversalTe);
|
|
4857
|
+
const totalNetEligibleItc = roundToPaisa(rule42Result.netEligibleInputsItc + Math.max(0, monthlyCapitalGoodsCreditTm - capitalGoodsReversalTe));
|
|
4207
4858
|
|
|
4208
|
-
//
|
|
4209
|
-
// ANNUAL TRUE-UP RECONCILIATION (RULE 42(2) & 43(2))
|
|
4210
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
4859
|
+
// Step 3: Annual True-Up Reconciliation (Rule 42(2) & 43(2))
|
|
4211
4860
|
let annualTrueUpDetails = null;
|
|
4212
4861
|
if (isAnnualTrueUp && annualTotalTurnover !== null && annualExemptTurnover !== null) {
|
|
4213
|
-
const
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4862
|
+
const trueUpResult = reconcileRule42AnnualTrueUp({
|
|
4863
|
+
financialYear,
|
|
4864
|
+
annualCommonCreditC2Inr: rule42Result.commonCreditC2,
|
|
4865
|
+
annualExemptTurnoverInr: annualExemptTurnover,
|
|
4866
|
+
annualTotalTurnoverInr: annualTotalTurnover,
|
|
4867
|
+
cumulativeMonthlyReversalsInr: sumOfMonthlyReversalsD1,
|
|
4868
|
+
trueUpExecutionDate,
|
|
4869
|
+
hasNonBusinessUse,
|
|
4870
|
+
isRealEstateProject
|
|
4871
|
+
});
|
|
4221
4872
|
|
|
4222
4873
|
annualTrueUpDetails = {
|
|
4223
4874
|
isAnnualTrueUp: true,
|
|
4224
4875
|
financialYear,
|
|
4225
|
-
annualExemptTurnover:
|
|
4226
|
-
annualTotalTurnover:
|
|
4227
|
-
annualExemptRatio:
|
|
4228
|
-
finalAnnualD1Inr:
|
|
4229
|
-
sumOfMonthlyD1ReversalsClaimedInr:
|
|
4230
|
-
differentialAdjustmentInr:
|
|
4231
|
-
adjustmentAction:
|
|
4232
|
-
? 'ADD_TO_OUTPUT_LIABILITY_WITH_SECTION_50_INTEREST'
|
|
4233
|
-
: (
|
|
4876
|
+
annualExemptTurnover: trueUpResult.annualExemptTurnoverInr,
|
|
4877
|
+
annualTotalTurnover: trueUpResult.annualTotalTurnoverInr,
|
|
4878
|
+
annualExemptRatio: trueUpResult.annualExemptRatio,
|
|
4879
|
+
finalAnnualD1Inr: trueUpResult.finalAnnualReversals.finalAnnualD1ExemptInr,
|
|
4880
|
+
sumOfMonthlyD1ReversalsClaimedInr: trueUpResult.cumulativeMonthlyReversalsMadeInr,
|
|
4881
|
+
differentialAdjustmentInr: trueUpResult.differentialAdjustmentInr,
|
|
4882
|
+
adjustmentAction: trueUpResult.reconciliationOutcome === 'SHORT_REVERSAL_PAYMENT_DUE'
|
|
4883
|
+
? 'ADD_TO_OUTPUT_LIABILITY_WITH_SECTION_50_INTEREST'
|
|
4884
|
+
: (trueUpResult.reconciliationOutcome === 'EXCESS_REVERSAL_RECLAIM_PERMITTED' ? 'RECLAIM_AS_ELIGIBLE_ITC' : 'ZERO_DIFFERENTIAL_MATCHED'),
|
|
4885
|
+
reconciliationOutcome: trueUpResult.reconciliationOutcome,
|
|
4886
|
+
shortfallAmountInr: trueUpResult.shortfallAmountInr,
|
|
4887
|
+
excessReversedInr: trueUpResult.excessReversedInr,
|
|
4888
|
+
interestAssessment: trueUpResult.interestAssessment,
|
|
4889
|
+
statutoryAction: trueUpResult.statutoryAction,
|
|
4234
4890
|
statutoryFilingDeadlineNotice: isRealEstateProject
|
|
4235
4891
|
? 'Real estate project true-up governed by Annexure I/II of Notification 16/2019-CT.'
|
|
4236
4892
|
: 'Annual adjustment due on or before filing return for month of September following FY end (or 30th November cutoff).'
|
|
@@ -4244,48 +4900,39 @@ export function calculateGstItcApportionmentRule42_43({
|
|
|
4244
4900
|
taxPeriod: taxPeriod || 'CURRENT_MONTH',
|
|
4245
4901
|
financialYear,
|
|
4246
4902
|
rule42InputsBreakdown: {
|
|
4247
|
-
totalItcT:
|
|
4903
|
+
totalItcT: rule42Result.totalItcT,
|
|
4248
4904
|
directIneligible: {
|
|
4249
|
-
exclusiveNonBusinessT1:
|
|
4250
|
-
exclusiveExemptT2:
|
|
4251
|
-
blockedSection17_5_T3:
|
|
4252
|
-
sumIneligible:
|
|
4905
|
+
exclusiveNonBusinessT1: rule42Result.directIneligibleCredits.exclusiveNonBusinessT1,
|
|
4906
|
+
exclusiveExemptT2: rule42Result.directIneligibleCredits.exclusiveExemptT2,
|
|
4907
|
+
blockedSection17_5_T3: rule42Result.directIneligibleCredits.blockedSection17_5_T3,
|
|
4908
|
+
sumIneligible: rule42Result.directIneligibleCredits.sumIneligible
|
|
4253
4909
|
},
|
|
4254
|
-
creditedToElectronicLedgerC1:
|
|
4255
|
-
exclusiveTaxableT4:
|
|
4256
|
-
commonCreditC2:
|
|
4257
|
-
exemptTurnoverE:
|
|
4258
|
-
totalTurnoverF:
|
|
4259
|
-
exemptRatio:
|
|
4260
|
-
turnoverBasis,
|
|
4910
|
+
creditedToElectronicLedgerC1: rule42Result.creditedToElectronicCreditLedgerC1,
|
|
4911
|
+
exclusiveTaxableT4: rule42Result.exclusiveTaxableT4,
|
|
4912
|
+
commonCreditC2: rule42Result.commonCreditC2,
|
|
4913
|
+
exemptTurnoverE: rule42Result.exemptTurnoverE,
|
|
4914
|
+
totalTurnoverF: rule42Result.totalTurnoverF,
|
|
4915
|
+
exemptRatio: rule42Result.turnoverRatioE_F,
|
|
4916
|
+
turnoverBasis: rule42Result.turnoverBasis,
|
|
4261
4917
|
reversals: {
|
|
4262
|
-
ineligibleExemptCommonD1:
|
|
4263
|
-
nonBusinessCommonD2:
|
|
4264
|
-
totalRule42Reversal:
|
|
4918
|
+
ineligibleExemptCommonD1: rule42Result.reversals.ineligibleExemptCommonD1,
|
|
4919
|
+
nonBusinessCommonD2: rule42Result.reversals.nonBusinessCommonD2,
|
|
4920
|
+
totalRule42Reversal: rule42Result.reversals.totalRule42Reversal
|
|
4265
4921
|
},
|
|
4266
|
-
eligibleCommonCreditC3:
|
|
4267
|
-
netEligibleInputsItc
|
|
4268
|
-
},
|
|
4269
|
-
rule43CapitalGoodsBreakdown: {
|
|
4270
|
-
capitalGoodsCommonCreditA: rawA,
|
|
4271
|
-
deemedUsefulLifeMonths: DEEMED_USEFUL_LIFE_MONTHS,
|
|
4272
|
-
priorQuartersExempt: priorQ,
|
|
4273
|
-
reductionFactorAppliedPercent: reductionFactor * 100,
|
|
4274
|
-
effectiveCommonCredit: effectiveA,
|
|
4275
|
-
monthlyCreditTm: monthlyCapitalGoodsCreditTm,
|
|
4276
|
-
monthlyReversalTe: capitalGoodsReversalTe,
|
|
4277
|
-
eligibleCapitalGoodsCreditMonthly: Math.max(0, monthlyCapitalGoodsCreditTm - capitalGoodsReversalTe)
|
|
4922
|
+
eligibleCommonCreditC3: rule42Result.eligibleCommonCreditC3,
|
|
4923
|
+
netEligibleInputsItc: rule42Result.netEligibleInputsItc
|
|
4278
4924
|
},
|
|
4925
|
+
rule43CapitalGoodsBreakdown: capitalGoodsBreakdown,
|
|
4279
4926
|
summary: {
|
|
4280
|
-
totalGrossItcT:
|
|
4281
|
-
totalDirectIneligible:
|
|
4282
|
-
totalCommonCreditApportioned:
|
|
4927
|
+
totalGrossItcT: rule42Result.totalItcT,
|
|
4928
|
+
totalDirectIneligible: rule42Result.directIneligibleCredits.sumIneligible,
|
|
4929
|
+
totalCommonCreditApportioned: rule42Result.commonCreditC2,
|
|
4283
4930
|
totalMonthlyReversalAddedToOutputLiability: totalPeriodReversal,
|
|
4284
4931
|
netEligibleItcTotal: totalNetEligibleItc
|
|
4285
4932
|
},
|
|
4286
4933
|
annualTrueUp: annualTrueUpDetails,
|
|
4287
4934
|
isAuthorizedToDisburse: false,
|
|
4288
|
-
statutoryRationale: `Rule 42 credited ₹${
|
|
4935
|
+
statutoryRationale: `Rule 42 credited ₹${rule42Result.creditedToElectronicCreditLedgerC1.toLocaleString('en-IN')} to ledger, identified ₹${rule42Result.commonCreditC2.toLocaleString('en-IN')} common credit, and calculated ₹${rule42Result.reversals.totalRule42Reversal.toLocaleString('en-IN')} reversal (D1: ₹${rule42Result.reversals.ineligibleExemptCommonD1}, D2: ₹${rule42Result.reversals.nonBusinessCommonD2}). Rule 43 capital goods reversal: ₹${capitalGoodsReversalTe.toLocaleString('en-IN')}. Net eligible ITC: ₹${totalNetEligibleItc.toLocaleString('en-IN')}. Reversal must be added to GSTR-3B Table 4(B).`
|
|
4289
4936
|
};
|
|
4290
4937
|
}
|
|
4291
4938
|
|
|
@@ -4311,9 +4958,9 @@ export function calculateItcReversalRule37({
|
|
|
4311
4958
|
isScheduleISupply = false, // Deemed supplies without consideration under Schedule I are exempt
|
|
4312
4959
|
annualInterestRatePercent = 18.0
|
|
4313
4960
|
} = {}) {
|
|
4314
|
-
const totalInvoice =
|
|
4315
|
-
const totalItc =
|
|
4316
|
-
const totalPaid = Math.max(0,
|
|
4961
|
+
const totalInvoice = cleanNumericAmount(invoiceAmount);
|
|
4962
|
+
const totalItc = cleanNumericAmount(taxAmount);
|
|
4963
|
+
const totalPaid = Math.max(0, cleanNumericAmount(paidAmount));
|
|
4317
4964
|
const unpaidAmount = roundToPaisa(Math.max(0, totalInvoice - totalPaid));
|
|
4318
4965
|
|
|
4319
4966
|
if (!invoiceDate || !/^\d{4}-\d{2}-\d{2}$/.test(String(invoiceDate).slice(0, 10))) {
|
|
@@ -4485,7 +5132,7 @@ export function calculateRule37ASupplierNonFilingReversal({
|
|
|
4485
5132
|
calculationDate = new Date().toISOString().slice(0, 10),
|
|
4486
5133
|
annualInterestRatePercent = 18.0
|
|
4487
5134
|
} = {}) {
|
|
4488
|
-
const rawItc =
|
|
5135
|
+
const rawItc = cleanNumericAmount(itcAmount);
|
|
4489
5136
|
const calcDateIso = String(calculationDate).slice(0, 10);
|
|
4490
5137
|
|
|
4491
5138
|
// Compute November 30 cutoff date for the given financial year
|
|
@@ -4582,7 +5229,7 @@ export function generateMsme1AndScheduleThreeAging({
|
|
|
4582
5229
|
asOfDate = new Date().toISOString().slice(0, 10),
|
|
4583
5230
|
halfYearPeriod = null, // 'APRIL_TO_SEPTEMBER' | 'OCTOBER_TO_MARCH'
|
|
4584
5231
|
payables = []
|
|
4585
|
-
}) {
|
|
5232
|
+
} = {}) {
|
|
4586
5233
|
if (!Array.isArray(payables)) {
|
|
4587
5234
|
throw new Error('[@mudraforge/statutory] Invalid payables: expected an array of payable records.');
|
|
4588
5235
|
}
|
|
@@ -4606,7 +5253,7 @@ export function generateMsme1AndScheduleThreeAging({
|
|
|
4606
5253
|
const assessedPayables = [];
|
|
4607
5254
|
|
|
4608
5255
|
for (const item of payables) {
|
|
4609
|
-
const amount = Math.max(0,
|
|
5256
|
+
const amount = Math.max(0, cleanNumericAmount(item.amount || item.invoiceAmount));
|
|
4610
5257
|
if (amount === 0) continue;
|
|
4611
5258
|
|
|
4612
5259
|
const isUnbilled = item.isUnbilled === true;
|
|
@@ -4779,14 +5426,14 @@ export function calculateCorporateTaxMatVsConcessional({
|
|
|
4779
5426
|
bookProfitAdjustments = null,
|
|
4780
5427
|
adjustedBookProfitInr = null,
|
|
4781
5428
|
optedSection115BAA = null
|
|
4782
|
-
}) {
|
|
5429
|
+
} = {}) {
|
|
4783
5430
|
const fy = financialYear || '2025-26';
|
|
4784
5431
|
const ay = assessmentYear || (fy === '2025-26' ? '2026-27' : '2027-28');
|
|
4785
|
-
const turnover =
|
|
4786
|
-
const npbt =
|
|
4787
|
-
const additions =
|
|
4788
|
-
const deductions =
|
|
4789
|
-
const priorMatCredit =
|
|
5432
|
+
const turnover = cleanNumericAmount(turnoverPrecedingFyInr);
|
|
5433
|
+
const npbt = cleanNumericAmount(netProfitBeforeTaxInr);
|
|
5434
|
+
const additions = cleanNumericAmount(normalTaxAdditionsInr);
|
|
5435
|
+
const deductions = cleanNumericAmount(normalTaxDeductionsInr);
|
|
5436
|
+
const priorMatCredit = cleanNumericAmount(accumulatedMatCreditInr);
|
|
4790
5437
|
|
|
4791
5438
|
// A. Normal Corporate Tax Regime
|
|
4792
5439
|
const taxableIncomeNormal = Math.max(0, roundToPaisa(npbt + additions - deductions));
|
|
@@ -4983,8 +5630,8 @@ export function evaluatePlaceOfSupplyAndLut({
|
|
|
4983
5630
|
isIntermediaryService = false,
|
|
4984
5631
|
hasValidLut = false,
|
|
4985
5632
|
lutArn = null
|
|
4986
|
-
}) {
|
|
4987
|
-
const amount =
|
|
5633
|
+
} = {}) {
|
|
5634
|
+
const amount = cleanNumericAmount(invoiceAmountInr);
|
|
4988
5635
|
const rate = Number(gstRate) || 0.18;
|
|
4989
5636
|
const isTransExport = transactionType === 'EXPORT_SERVICES' || transactionType === 'EXPORT_GOODS' || (recipientCountryCode && recipientCountryCode.toUpperCase() !== 'IN');
|
|
4990
5637
|
|
|
@@ -5165,9 +5812,9 @@ export function calculateTransferPricingAlp({
|
|
|
5165
5812
|
isWholesaleTrading = false,
|
|
5166
5813
|
comparablesData = [],
|
|
5167
5814
|
transferPricingMethod = 'TNMM'
|
|
5168
|
-
}) {
|
|
5169
|
-
const amount =
|
|
5170
|
-
const testedMargin =
|
|
5815
|
+
} = {}) {
|
|
5816
|
+
const amount = cleanNumericAmount(internationalTransactionAmountInr);
|
|
5817
|
+
const testedMargin = cleanNumericAmount(testedPartyOperatingMarginPercent);
|
|
5171
5818
|
const tolerancePercent = isWholesaleTrading ? 3.0 : 1.0;
|
|
5172
5819
|
|
|
5173
5820
|
if (!isAssociatedEnterprise) {
|
|
@@ -5298,9 +5945,9 @@ export function evaluateSection79LossContinuity({
|
|
|
5298
5945
|
shareholdingInLossYear = [],
|
|
5299
5946
|
shareholdingInSetOffYear = [],
|
|
5300
5947
|
votingPowerHeldByOriginalShareholdersPercent = null
|
|
5301
|
-
}) {
|
|
5302
|
-
const bfLoss = Math.max(0,
|
|
5303
|
-
const unabsDep = Math.max(0,
|
|
5948
|
+
} = {}) {
|
|
5949
|
+
const bfLoss = Math.max(0, cleanNumericAmount(accumulatedBroughtForwardLossInr));
|
|
5950
|
+
const unabsDep = Math.max(0, cleanNumericAmount(unabsorbedDepreciationInr));
|
|
5304
5951
|
|
|
5305
5952
|
let continuityPercent = votingPowerHeldByOriginalShareholdersPercent !== null
|
|
5306
5953
|
? Number(votingPowerHeldByOriginalShareholdersPercent)
|
|
@@ -5580,7 +6227,7 @@ export function revalueForeignExchangeIndAs109({
|
|
|
5580
6227
|
asOfDate = null,
|
|
5581
6228
|
reportingCurrency = 'INR',
|
|
5582
6229
|
items = []
|
|
5583
|
-
}) {
|
|
6230
|
+
} = {}) {
|
|
5584
6231
|
const dAsOf = asOfDate ? new Date(asOfDate) : new Date();
|
|
5585
6232
|
const asOfIso = !isNaN(dAsOf.getTime()) ? dAsOf.toISOString().split('T')[0] : new Date().toISOString().split('T')[0];
|
|
5586
6233
|
|
|
@@ -5596,9 +6243,9 @@ export function revalueForeignExchangeIndAs109({
|
|
|
5596
6243
|
const desc = item.description || item.itemDescription || 'FX Item';
|
|
5597
6244
|
const itemType = (item.itemType || 'MONETARY_ASSET').toUpperCase();
|
|
5598
6245
|
const ccy = item.foreignCurrency || item.currency || 'USD';
|
|
5599
|
-
const fcyAmt =
|
|
5600
|
-
const bookRate =
|
|
5601
|
-
const closeRate =
|
|
6246
|
+
const fcyAmt = cleanNumericAmount(item.foreignAmount || item.amount);
|
|
6247
|
+
const bookRate = cleanNumericAmount(item.bookingRate || item.historicalRate) || 1;
|
|
6248
|
+
const closeRate = cleanNumericAmount(item.closingRate || item.spotRate) || bookRate;
|
|
5602
6249
|
const isHedge = item.isDesignatedCashFlowHedge === true;
|
|
5603
6250
|
|
|
5604
6251
|
if (itemType === 'NON_MONETARY') {
|
|
@@ -5700,7 +6347,7 @@ export function revalueForeignExchangeIndAs109({
|
|
|
5700
6347
|
* - Always returns isAuthorizedToDisburse: false.
|
|
5701
6348
|
*/
|
|
5702
6349
|
export function evaluateSection194R({
|
|
5703
|
-
currentBenefitValueInr
|
|
6350
|
+
currentBenefitValueInr,
|
|
5704
6351
|
cumulativePriorBenefitsInr = 0,
|
|
5705
6352
|
payerEntityType = 'COMPANY',
|
|
5706
6353
|
payerPrecedingFyTurnoverInr = null,
|
|
@@ -5713,8 +6360,8 @@ export function evaluateSection194R({
|
|
|
5713
6360
|
isSpecifiedPerson206AB = false,
|
|
5714
6361
|
effectiveDate = null
|
|
5715
6362
|
} = {}) {
|
|
5716
|
-
const benefitVal = roundToPaisa(Math.max(0,
|
|
5717
|
-
const priorVal = roundToPaisa(Math.max(0,
|
|
6363
|
+
const benefitVal = roundToPaisa(Math.max(0, assertMandatoryNumeric(currentBenefitValueInr, 'currentBenefitValueInr', 'evaluateSection194R')));
|
|
6364
|
+
const priorVal = roundToPaisa(Math.max(0, cleanNumericAmount(cumulativePriorBenefitsInr)));
|
|
5718
6365
|
const totalCumulativeVal = roundToPaisa(priorVal + benefitVal);
|
|
5719
6366
|
const dateStr = effectiveDate ? String(effectiveDate).slice(0, 10) : new Date().toISOString().slice(0, 10);
|
|
5720
6367
|
|
|
@@ -5739,8 +6386,8 @@ export function evaluateSection194R({
|
|
|
5739
6386
|
// Exclusion 2: Individual / HUF Payer Turnover Floor
|
|
5740
6387
|
const normalizedEntityType = String(payerEntityType || 'COMPANY').toUpperCase();
|
|
5741
6388
|
if (normalizedEntityType === 'INDIVIDUAL' || normalizedEntityType === 'HUF' || normalizedEntityType === 'INDIVIDUAL_HUF') {
|
|
5742
|
-
const turnover =
|
|
5743
|
-
const profReceipts =
|
|
6389
|
+
const turnover = cleanNumericAmount(payerPrecedingFyTurnoverInr);
|
|
6390
|
+
const profReceipts = cleanNumericAmount(payerPrecedingFyProfessionReceiptsInr);
|
|
5744
6391
|
const exceedsBusinessFloor = turnover > 10000000; // > 1 Crore
|
|
5745
6392
|
const exceedsProfessionFloor = profReceipts > 5000000; // > 50 Lakhs
|
|
5746
6393
|
|
|
@@ -5866,7 +6513,7 @@ export function evaluateSection194R({
|
|
|
5866
6513
|
* - Always returns isAuthorizedToDisburse: false.
|
|
5867
6514
|
*/
|
|
5868
6515
|
export function evaluateSection194IB({
|
|
5869
|
-
monthlyRentInr
|
|
6516
|
+
monthlyRentInr,
|
|
5870
6517
|
tenancyMonthsInYear = 1,
|
|
5871
6518
|
lastMonthRentPayableInr = null,
|
|
5872
6519
|
isPayerTaxAuditCovered = false,
|
|
@@ -5874,11 +6521,11 @@ export function evaluateSection194IB({
|
|
|
5874
6521
|
isLandlordPanInoperative = false,
|
|
5875
6522
|
effectiveDate = null
|
|
5876
6523
|
} = {}) {
|
|
5877
|
-
const monthRent = roundToPaisa(Math.max(0,
|
|
6524
|
+
const monthRent = roundToPaisa(Math.max(0, assertMandatoryNumeric(monthlyRentInr, 'monthlyRentInr', 'evaluateSection194IB')));
|
|
5878
6525
|
const months = Math.max(1, Math.min(12, Number(tenancyMonthsInYear) || 1));
|
|
5879
6526
|
const totalRentForPeriod = roundToPaisa(monthRent * months);
|
|
5880
|
-
const lastMonthRent = lastMonthRentPayableInr !== null && lastMonthRentPayableInr !== undefined
|
|
5881
|
-
? roundToPaisa(Math.max(0,
|
|
6527
|
+
const lastMonthRent = lastMonthRentPayableInr !== null && lastMonthRentPayableInr !== undefined
|
|
6528
|
+
? roundToPaisa(Math.max(0, cleanNumericAmount(lastMonthRentPayableInr)))
|
|
5882
6529
|
: monthRent;
|
|
5883
6530
|
const dateStr = effectiveDate ? String(effectiveDate).slice(0, 10) : new Date().toISOString().slice(0, 10);
|
|
5884
6531
|
|
|
@@ -5990,7 +6637,7 @@ export function evaluateSection194IB({
|
|
|
5990
6637
|
* - Always returns isAuthorizedToDisburse: false.
|
|
5991
6638
|
*/
|
|
5992
6639
|
export function evaluateSection194M({
|
|
5993
|
-
currentPaymentAmountInr
|
|
6640
|
+
currentPaymentAmountInr,
|
|
5994
6641
|
cumulativePriorPaymentsInr = 0,
|
|
5995
6642
|
isPayerCoveredBy194C_H_J = false,
|
|
5996
6643
|
hasPan = true,
|
|
@@ -5998,8 +6645,8 @@ export function evaluateSection194M({
|
|
|
5998
6645
|
isSpecifiedPerson206AB = false,
|
|
5999
6646
|
effectiveDate = null
|
|
6000
6647
|
} = {}) {
|
|
6001
|
-
const currentAmt = roundToPaisa(Math.max(0,
|
|
6002
|
-
const priorAmt = roundToPaisa(Math.max(0,
|
|
6648
|
+
const currentAmt = roundToPaisa(Math.max(0, assertMandatoryNumeric(currentPaymentAmountInr, 'currentPaymentAmountInr', 'evaluateSection194M')));
|
|
6649
|
+
const priorAmt = roundToPaisa(Math.max(0, cleanNumericAmount(cumulativePriorPaymentsInr)));
|
|
6003
6650
|
const totalCumulative = roundToPaisa(priorAmt + currentAmt);
|
|
6004
6651
|
const dateStr = effectiveDate ? String(effectiveDate).slice(0, 10) : new Date().toISOString().slice(0, 10);
|
|
6005
6652
|
|
|
@@ -6085,6 +6732,1294 @@ export function evaluateSection194M({
|
|
|
6085
6732
|
};
|
|
6086
6733
|
}
|
|
6087
6734
|
|
|
6735
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6736
|
+
// CORPORATE FINANCE, ACCOUNTING STANDARDS & REPORTING ENGINES
|
|
6737
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6738
|
+
|
|
6739
|
+
/**
|
|
6740
|
+
* Ind AS 103 / IFRS 3 / US GAAP ASC 805: Business Combinations & Purchase Price Allocation (PPA) Calculator.
|
|
6741
|
+
*
|
|
6742
|
+
* Implements deterministic PPA accounting under the acquisition method:
|
|
6743
|
+
* 1. Computes total consideration transferred:
|
|
6744
|
+
* - Upfront cash consideration
|
|
6745
|
+
* - Fair value of equity shares issued (sharesCount * sharePrice)
|
|
6746
|
+
* - Fair value of contingent consideration / earnouts (probability-weighted discounted payout)
|
|
6747
|
+
* - Present value of deferred cash consideration (discounted at discountRate / WACC)
|
|
6748
|
+
* - Fair value of pre-existing non-controlling equity interest (for step acquisitions)
|
|
6749
|
+
* 2. Identifies and aggregates Net Identifiable Assets acquired:
|
|
6750
|
+
* - Fair value of tangible assets (PPE, working capital, inventory)
|
|
6751
|
+
* - Fair value of separately identifiable intangible assets (customer relationships, software, brand, patents, order backlog)
|
|
6752
|
+
* - Less: Fair value of liabilities assumed (current liabilities, borrowings, contingent liabilities)
|
|
6753
|
+
* - Less: Net deferred tax liability (DTL) arising on fair value adjustments:
|
|
6754
|
+
* (fairValueIntangiblesStepUp - taxBaseIntangibles) * taxRate
|
|
6755
|
+
* 3. Incorporates Non-Controlling Interest (NCI):
|
|
6756
|
+
* - 'PROPORTIONATE_SHARE': nciPercentage * netIdentifiableAssets
|
|
6757
|
+
* - 'FAIR_VALUE': nciFairValue
|
|
6758
|
+
* 4. Determines Goodwill or Gain on Bargain Purchase (Capital Reserve):
|
|
6759
|
+
* - Acquisition Basis = Consideration Transferred + NCI
|
|
6760
|
+
* - If Acquisition Basis > Net Identifiable Assets:
|
|
6761
|
+
* Goodwill = Acquisition Basis - Net Identifiable Assets (capitalized as indefinite-life intangible, tested under Ind AS 36)
|
|
6762
|
+
* - If Net Identifiable Assets > Acquisition Basis:
|
|
6763
|
+
* Bargain Purchase Gain = Net Identifiable Assets - Acquisition Basis (requires reassessment; recognized in OCI and accumulated in Capital Reserve)
|
|
6764
|
+
* 5. Expensing of Transaction Costs:
|
|
6765
|
+
* - Legal, investment banking, due diligence, valuation fees are expensed immediately in P&L (not capitalized into Goodwill)
|
|
6766
|
+
* 6. Generates balanced double-entry vouchers to exact paisa.
|
|
6767
|
+
* 7. Enforces statutory invariant: isAuthorizedToDisburse: false.
|
|
6768
|
+
*/
|
|
6769
|
+
export function calculateBusinessCombinationsIndAs103(params = {}) {
|
|
6770
|
+
const acquisitionId = params.acquisitionId || 'ACQ-01';
|
|
6771
|
+
const acquirerName = params.acquirerName || 'Acquiring Enterprise';
|
|
6772
|
+
const acquireeName = params.acquireeName || 'Acquired Business Entity';
|
|
6773
|
+
const acquisitionDate = params.acquisitionDate || new Date().toISOString().split('T')[0];
|
|
6774
|
+
|
|
6775
|
+
// 1. Consideration Transferred
|
|
6776
|
+
const rawCons = params.consideration || {};
|
|
6777
|
+
const cashConsideration = roundToPaisa(cleanNumericAmount(rawCons.upfrontCashInr, { defaultValue: 0 }));
|
|
6778
|
+
|
|
6779
|
+
let equitySharesFairValue = 0;
|
|
6780
|
+
if (rawCons.equitySharesIssued && typeof rawCons.equitySharesIssued === 'object') {
|
|
6781
|
+
const count = cleanNumericAmount(rawCons.equitySharesIssued.count, { defaultValue: 0 });
|
|
6782
|
+
const price = cleanNumericAmount(rawCons.equitySharesIssued.sharePriceInr, { defaultValue: 0 });
|
|
6783
|
+
equitySharesFairValue = roundToPaisa(count * price);
|
|
6784
|
+
} else {
|
|
6785
|
+
equitySharesFairValue = roundToPaisa(cleanNumericAmount(rawCons.equitySharesFairValueInr, { defaultValue: 0 }));
|
|
6786
|
+
}
|
|
6787
|
+
|
|
6788
|
+
const contingentConsideration = roundToPaisa(cleanNumericAmount(rawCons.contingentConsiderationFairValueInr, { defaultValue: 0 }));
|
|
6789
|
+
|
|
6790
|
+
let deferredConsiderationPv = 0;
|
|
6791
|
+
if (rawCons.deferredCashConsideration && typeof rawCons.deferredCashConsideration === 'object') {
|
|
6792
|
+
const amt = cleanNumericAmount(rawCons.deferredCashConsideration.amountInr, { defaultValue: 0 });
|
|
6793
|
+
const r = cleanNumericAmount(rawCons.deferredCashConsideration.discountRate, { defaultValue: 0.10 });
|
|
6794
|
+
const yrs = cleanNumericAmount(rawCons.deferredCashConsideration.payableInYears, { defaultValue: 1 });
|
|
6795
|
+
deferredConsiderationPv = roundToPaisa(amt / Math.pow(1 + r, yrs));
|
|
6796
|
+
} else {
|
|
6797
|
+
deferredConsiderationPv = roundToPaisa(cleanNumericAmount(rawCons.deferredConsiderationPresentValueInr, { defaultValue: 0 }));
|
|
6798
|
+
}
|
|
6799
|
+
|
|
6800
|
+
const preExistingInterest = roundToPaisa(cleanNumericAmount(rawCons.preExistingInterestFairValueInr, { defaultValue: 0 }));
|
|
6801
|
+
|
|
6802
|
+
const totalConsiderationTransferred = roundToPaisa(
|
|
6803
|
+
cashConsideration + equitySharesFairValue + contingentConsideration + deferredConsiderationPv + preExistingInterest
|
|
6804
|
+
);
|
|
6805
|
+
|
|
6806
|
+
// 2. Identifiable Assets Acquired
|
|
6807
|
+
const rawAssets = params.identifiableAssets || {};
|
|
6808
|
+
const tangibleAssets = roundToPaisa(cleanNumericAmount(rawAssets.tangibleAssetsFairValueInr, { defaultValue: 0 }));
|
|
6809
|
+
const identifiableIntangibles = roundToPaisa(cleanNumericAmount(rawAssets.identifiableIntangiblesFairValueInr, { defaultValue: 0 }));
|
|
6810
|
+
const otherAssets = roundToPaisa(cleanNumericAmount(rawAssets.otherAssetsFairValueInr, { defaultValue: 0 }));
|
|
6811
|
+
const totalGrossAssets = roundToPaisa(tangibleAssets + identifiableIntangibles + otherAssets);
|
|
6812
|
+
|
|
6813
|
+
// 3. Liabilities Assumed
|
|
6814
|
+
const rawLiab = params.liabilitiesAssumed || {};
|
|
6815
|
+
const borrowings = roundToPaisa(cleanNumericAmount(rawLiab.borrowingsFairValueInr, { defaultValue: 0 }));
|
|
6816
|
+
const workingCapitalLiab = roundToPaisa(cleanNumericAmount(rawLiab.workingCapitalLiabilitiesInr, { defaultValue: 0 }));
|
|
6817
|
+
const contingentLiab = roundToPaisa(cleanNumericAmount(rawLiab.contingentLiabilitiesRecognizedInr, { defaultValue: 0 }));
|
|
6818
|
+
|
|
6819
|
+
// Deferred Tax on Intangibles Step-Up
|
|
6820
|
+
const taxRate = cleanNumericAmount(params.taxRate, { defaultValue: 0.25168 });
|
|
6821
|
+
const intangiblesTaxBase = cleanNumericAmount(params.intangiblesTaxBaseInr, { defaultValue: 0 });
|
|
6822
|
+
const intangiblesStepUp = Math.max(0, identifiableIntangibles - intangiblesTaxBase);
|
|
6823
|
+
const deferredTaxLiability = roundToPaisa(intangiblesStepUp * taxRate);
|
|
6824
|
+
|
|
6825
|
+
const totalLiabilitiesAssumed = roundToPaisa(borrowings + workingCapitalLiab + contingentLiab + deferredTaxLiability);
|
|
6826
|
+
const netIdentifiableAssets = roundToPaisa(totalGrossAssets - totalLiabilitiesAssumed);
|
|
6827
|
+
|
|
6828
|
+
if (totalConsiderationTransferred <= 0 && netIdentifiableAssets <= 0) {
|
|
6829
|
+
return {
|
|
6830
|
+
status: 'INSUFFICIENT_DATA',
|
|
6831
|
+
standard: 'Ind AS 103 / IFRS 3 / US GAAP ASC 805',
|
|
6832
|
+
acquisitionId,
|
|
6833
|
+
isAuthorizedToDisburse: false,
|
|
6834
|
+
statutoryRationale: 'Consideration transferred or identifiable net assets must be greater than zero.'
|
|
6835
|
+
};
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
// 4. Non-Controlling Interest (NCI)
|
|
6839
|
+
const rawNci = params.nonControllingInterest || {};
|
|
6840
|
+
const nciMethod = String(rawNci.method || 'PROPORTIONATE_SHARE').toUpperCase();
|
|
6841
|
+
const nciPct = cleanNumericAmount(rawNci.percentage, { defaultValue: 0 });
|
|
6842
|
+
let nciAmount = 0;
|
|
6843
|
+
if (nciMethod === 'FAIR_VALUE' && rawNci.fairValueInr) {
|
|
6844
|
+
nciAmount = roundToPaisa(cleanNumericAmount(rawNci.fairValueInr, { defaultValue: 0 }));
|
|
6845
|
+
} else {
|
|
6846
|
+
nciAmount = roundToPaisa(Math.max(0, netIdentifiableAssets * (nciPct / 100)));
|
|
6847
|
+
}
|
|
6848
|
+
|
|
6849
|
+
// 5. Acquisition Basis & Goodwill / Capital Reserve
|
|
6850
|
+
const acquisitionBasis = roundToPaisa(totalConsiderationTransferred + nciAmount);
|
|
6851
|
+
const excess = roundToPaisa(acquisitionBasis - netIdentifiableAssets);
|
|
6852
|
+
|
|
6853
|
+
let goodwillInr = 0;
|
|
6854
|
+
let bargainPurchaseGainInr = 0;
|
|
6855
|
+
let disposition = '';
|
|
6856
|
+
let statutoryRationale = '';
|
|
6857
|
+
|
|
6858
|
+
if (excess > 0) {
|
|
6859
|
+
goodwillInr = excess;
|
|
6860
|
+
disposition = 'GOODWILL_RECOGNIZED';
|
|
6861
|
+
statutoryRationale = `Ind AS 103 PPA complete: Consideration of INR ${totalConsiderationTransferred.toLocaleString('en-IN')} plus NCI of INR ${nciAmount.toLocaleString('en-IN')} exceeds Net Identifiable Assets of INR ${netIdentifiableAssets.toLocaleString('en-IN')}. Goodwill of INR ${goodwillInr.toLocaleString('en-IN')} capitalized on balance sheet. Mandatory annual impairment test under Ind AS 36 (amortization prohibited).`;
|
|
6862
|
+
} else if (excess < 0) {
|
|
6863
|
+
bargainPurchaseGainInr = Math.abs(excess);
|
|
6864
|
+
disposition = 'BARGAIN_PURCHASE_CAPITAL_RESERVE';
|
|
6865
|
+
statutoryRationale = `Ind AS 103 PPA complete: Net Identifiable Assets of INR ${netIdentifiableAssets.toLocaleString('en-IN')} exceed Acquisition Basis of INR ${acquisitionBasis.toLocaleString('en-IN')}. Bargain purchase gain of INR ${bargainPurchaseGainInr.toLocaleString('en-IN')} identified. After mandatory reassessment per Ind AS 103 paragraph 36, recognized in OCI and accumulated in Equity as Capital Reserve.`;
|
|
6866
|
+
} else {
|
|
6867
|
+
disposition = 'PAR_ACQUISITION';
|
|
6868
|
+
statutoryRationale = `Ind AS 103 PPA complete: Acquisition Basis exactly equals Net Identifiable Assets of INR ${netIdentifiableAssets.toLocaleString('en-IN')}. Zero goodwill or bargain purchase gain recognized.`;
|
|
6869
|
+
}
|
|
6870
|
+
|
|
6871
|
+
// 6. Acquisition-Related Costs (Expensed immediately in P&L)
|
|
6872
|
+
const transactionCosts = roundToPaisa(cleanNumericAmount(params.acquisitionRelatedCostsInr, { defaultValue: 0 }));
|
|
6873
|
+
|
|
6874
|
+
// 7. General Ledger Journal Entries
|
|
6875
|
+
const journalEntries = [];
|
|
6876
|
+
if (tangibleAssets > 0) {
|
|
6877
|
+
journalEntries.push({ account: 'Property, Plant & Equipment (Fair Value)', type: 'PPE', debitInr: tangibleAssets, creditInr: 0, reference: acquisitionId });
|
|
6878
|
+
}
|
|
6879
|
+
if (identifiableIntangibles > 0) {
|
|
6880
|
+
journalEntries.push({ account: 'Identifiable Intangible Assets (Fair Value)', type: 'INTANGIBLE', debitInr: identifiableIntangibles, creditInr: 0, reference: acquisitionId });
|
|
6881
|
+
}
|
|
6882
|
+
if (otherAssets > 0) {
|
|
6883
|
+
journalEntries.push({ account: 'Other Net Current Assets', type: 'CURRENT_ASSET', debitInr: otherAssets, creditInr: 0, reference: acquisitionId });
|
|
6884
|
+
}
|
|
6885
|
+
if (goodwillInr > 0) {
|
|
6886
|
+
journalEntries.push({ account: 'Goodwill on Consolidation', type: 'INTANGIBLE_GOODWILL', debitInr: goodwillInr, creditInr: 0, reference: acquisitionId });
|
|
6887
|
+
}
|
|
6888
|
+
if (borrowings > 0) {
|
|
6889
|
+
journalEntries.push({ account: 'Borrowings Assumed (Fair Value)', type: 'NON_CURRENT_LIABILITY', debitInr: 0, creditInr: borrowings, reference: acquisitionId });
|
|
6890
|
+
}
|
|
6891
|
+
if (workingCapitalLiab > 0) {
|
|
6892
|
+
journalEntries.push({ account: 'Working Capital Liabilities Assumed', type: 'CURRENT_LIABILITY', debitInr: 0, creditInr: workingCapitalLiab, reference: acquisitionId });
|
|
6893
|
+
}
|
|
6894
|
+
if (contingentLiab > 0) {
|
|
6895
|
+
journalEntries.push({ account: 'Contingent Liabilities Recognized', type: 'PROVISION', debitInr: 0, creditInr: contingentLiab, reference: acquisitionId });
|
|
6896
|
+
}
|
|
6897
|
+
if (deferredTaxLiability > 0) {
|
|
6898
|
+
journalEntries.push({ account: 'Deferred Tax Liability (Ind AS 12 / Step-Up)', type: 'DTL', debitInr: 0, creditInr: deferredTaxLiability, reference: acquisitionId });
|
|
6899
|
+
}
|
|
6900
|
+
if (nciAmount > 0) {
|
|
6901
|
+
journalEntries.push({ account: 'Non-Controlling Interest (Equity)', type: 'EQUITY_NCI', debitInr: 0, creditInr: nciAmount, reference: acquisitionId });
|
|
6902
|
+
}
|
|
6903
|
+
if (bargainPurchaseGainInr > 0) {
|
|
6904
|
+
journalEntries.push({ account: 'Capital Reserve (Bargain Purchase Gain in OCI)', type: 'EQUITY_CAPITAL_RESERVE', debitInr: 0, creditInr: bargainPurchaseGainInr, reference: acquisitionId });
|
|
6905
|
+
}
|
|
6906
|
+
if (cashConsideration > 0) {
|
|
6907
|
+
journalEntries.push({ account: 'Cash / Bank Rail Settlement', type: 'CURRENT_ASSET', debitInr: 0, creditInr: cashConsideration, reference: acquisitionId });
|
|
6908
|
+
}
|
|
6909
|
+
if (equitySharesFairValue > 0) {
|
|
6910
|
+
journalEntries.push({ account: 'Equity Share Capital & Securities Premium', type: 'EQUITY', debitInr: 0, creditInr: equitySharesFairValue, reference: acquisitionId });
|
|
6911
|
+
}
|
|
6912
|
+
if (contingentConsideration > 0) {
|
|
6913
|
+
journalEntries.push({ account: 'Contingent Consideration Liability', type: 'FINANCIAL_LIABILITY', debitInr: 0, creditInr: contingentConsideration, reference: acquisitionId });
|
|
6914
|
+
}
|
|
6915
|
+
if (deferredConsiderationPv > 0) {
|
|
6916
|
+
journalEntries.push({ account: 'Deferred Consideration Payable (PV)', type: 'NON_CURRENT_LIABILITY', debitInr: 0, creditInr: deferredConsiderationPv, reference: acquisitionId });
|
|
6917
|
+
}
|
|
6918
|
+
if (preExistingInterest > 0) {
|
|
6919
|
+
journalEntries.push({ account: 'Investment in Acquiree (Pre-existing Interest De-recognized)', type: 'INVESTMENT', debitInr: 0, creditInr: preExistingInterest, reference: acquisitionId });
|
|
6920
|
+
}
|
|
6921
|
+
|
|
6922
|
+
if (transactionCosts > 0) {
|
|
6923
|
+
journalEntries.push({ account: 'Acquisition Transaction Costs (P&L Expense)', type: 'OPERATING_EXPENSE', debitInr: transactionCosts, creditInr: 0, reference: acquisitionId });
|
|
6924
|
+
journalEntries.push({ account: 'Accounts Payable / Bank (Advisory Fees)', type: 'CURRENT_LIABILITY', debitInr: 0, creditInr: transactionCosts, reference: acquisitionId });
|
|
6925
|
+
}
|
|
6926
|
+
|
|
6927
|
+
const totalDebits = roundToPaisa(journalEntries.reduce((acc, e) => acc + e.debitInr, 0));
|
|
6928
|
+
const totalCredits = roundToPaisa(journalEntries.reduce((acc, e) => acc + e.creditInr, 0));
|
|
6929
|
+
const isDoubleEntryBalanced = Math.abs(totalDebits - totalCredits) < 0.001;
|
|
6930
|
+
|
|
6931
|
+
return {
|
|
6932
|
+
status: 'SUCCESS',
|
|
6933
|
+
standard: 'Ind AS 103 / IFRS 3 / US GAAP ASC 805',
|
|
6934
|
+
acquisitionId,
|
|
6935
|
+
acquirerName,
|
|
6936
|
+
acquireeName,
|
|
6937
|
+
acquisitionDate,
|
|
6938
|
+
considerationSummary: {
|
|
6939
|
+
cashConsiderationInr: cashConsideration,
|
|
6940
|
+
equitySharesFairValueInr: equitySharesFairValue,
|
|
6941
|
+
contingentConsiderationFairValueInr: contingentConsideration,
|
|
6942
|
+
deferredConsiderationPresentValueInr: deferredConsiderationPv,
|
|
6943
|
+
preExistingInterestFairValueInr: preExistingInterest,
|
|
6944
|
+
totalConsiderationTransferredInr: totalConsiderationTransferred
|
|
6945
|
+
},
|
|
6946
|
+
netIdentifiableAssetsSummary: {
|
|
6947
|
+
tangibleAssetsFairValueInr: tangibleAssets,
|
|
6948
|
+
identifiableIntangiblesFairValueInr: identifiableIntangibles,
|
|
6949
|
+
otherAssetsFairValueInr: otherAssets,
|
|
6950
|
+
totalGrossAssetsFairValueInr: totalGrossAssets,
|
|
6951
|
+
borrowingsFairValueInr: borrowings,
|
|
6952
|
+
workingCapitalLiabilitiesInr: workingCapitalLiab,
|
|
6953
|
+
contingentLiabilitiesRecognizedInr: contingentLiab,
|
|
6954
|
+
deferredTaxLiabilityInr: deferredTaxLiability,
|
|
6955
|
+
totalLiabilitiesAssumedInr: totalLiabilitiesAssumed,
|
|
6956
|
+
netIdentifiableAssetsInr: netIdentifiableAssets
|
|
6957
|
+
},
|
|
6958
|
+
nonControllingInterestSummary: {
|
|
6959
|
+
measurementMethod: nciMethod,
|
|
6960
|
+
nciPercentage: nciPct,
|
|
6961
|
+
nciAmountInr: nciAmount
|
|
6962
|
+
},
|
|
6963
|
+
purchasePriceAllocationOutcome: {
|
|
6964
|
+
acquisitionBasisInr: acquisitionBasis,
|
|
6965
|
+
netIdentifiableAssetsInr: netIdentifiableAssets,
|
|
6966
|
+
disposition,
|
|
6967
|
+
goodwillRecognizedInr: goodwillInr,
|
|
6968
|
+
gainOnBargainPurchaseCapitalReserveInr: bargainPurchaseGainInr,
|
|
6969
|
+
transactionCostsExpensedInr: transactionCosts
|
|
6970
|
+
},
|
|
6971
|
+
journalEntries,
|
|
6972
|
+
isDoubleEntryBalanced,
|
|
6973
|
+
isAuthorizedToDisburse: false,
|
|
6974
|
+
statutoryRationale
|
|
6975
|
+
};
|
|
6976
|
+
}
|
|
6977
|
+
|
|
6978
|
+
/**
|
|
6979
|
+
* Ind AS 115 / IFRS 15 / US GAAP ASC 606: 5-Step Revenue Recognition & Relative SSP Allocation Calculator.
|
|
6980
|
+
*/
|
|
6981
|
+
export function calculateRevenueAllocationIndAs115(params = {}) {
|
|
6982
|
+
const rawPrice = cleanNumericAmount(params.transactionPrice, { defaultValue: 0 });
|
|
6983
|
+
const transactionPrice = roundToPaisa(rawPrice);
|
|
6984
|
+
const obligations = Array.isArray(params.performanceObligations) ? params.performanceObligations : [];
|
|
6985
|
+
|
|
6986
|
+
if (transactionPrice <= 0 || obligations.length === 0) {
|
|
6987
|
+
return {
|
|
6988
|
+
status: 'INSUFFICIENT_DATA',
|
|
6989
|
+
standard: 'Ind AS 115 / IFRS 15 / US GAAP ASC 606',
|
|
6990
|
+
transactionPriceInr: transactionPrice,
|
|
6991
|
+
performanceObligationsCount: obligations.length,
|
|
6992
|
+
isAuthorizedToDisburse: false,
|
|
6993
|
+
statutoryRationale: 'Transaction price and at least one distinct performance obligation must be specified.'
|
|
6994
|
+
};
|
|
6995
|
+
}
|
|
6996
|
+
|
|
6997
|
+
let totalSsp = 0;
|
|
6998
|
+
const prepped = obligations.map((ob, idx) => {
|
|
6999
|
+
const ssp = roundToPaisa(cleanNumericAmount(ob.standaloneSellingPrice, { defaultValue: 0 }));
|
|
7000
|
+
totalSsp += ssp;
|
|
7001
|
+
return {
|
|
7002
|
+
id: ob.id || `PO-${idx + 1}`,
|
|
7003
|
+
description: ob.description || `Performance Obligation ${idx + 1}`,
|
|
7004
|
+
standaloneSellingPrice: ssp,
|
|
7005
|
+
satisfactionPattern: ob.satisfactionPattern === 'POINT_IN_TIME' ? 'POINT_IN_TIME' : 'OVER_TIME',
|
|
7006
|
+
percentageCompletion: Math.min(100, Math.max(0, cleanNumericAmount(ob.percentageCompletion, { defaultValue: 0 }))),
|
|
7007
|
+
termMonths: Math.max(0, cleanNumericAmount(ob.termMonths, { defaultValue: 0 })),
|
|
7008
|
+
monthsElapsed: Math.max(0, cleanNumericAmount(ob.monthsElapsed, { defaultValue: 0 })),
|
|
7009
|
+
isPointInTimeSatisfied: Boolean(ob.isPointInTimeSatisfied),
|
|
7010
|
+
amountBilledToDate: roundToPaisa(cleanNumericAmount(ob.amountBilledToDate, { defaultValue: 0 }))
|
|
7011
|
+
};
|
|
7012
|
+
});
|
|
7013
|
+
|
|
7014
|
+
totalSsp = roundToPaisa(totalSsp);
|
|
7015
|
+
const sspDenominator = totalSsp > 0 ? totalSsp : transactionPrice;
|
|
7016
|
+
const bundleDiscount = roundToPaisa(Math.max(0, totalSsp - transactionPrice));
|
|
7017
|
+
const bundleDiscountPercentage = totalSsp > 0 ? Number(((bundleDiscount / totalSsp) * 100).toFixed(2)) : 0;
|
|
7018
|
+
|
|
7019
|
+
let allocatedSum = 0;
|
|
7020
|
+
let totalRevenueRecognized = 0;
|
|
7021
|
+
let totalBilled = 0;
|
|
7022
|
+
|
|
7023
|
+
const allocatedObligations = prepped.map((ob, idx) => {
|
|
7024
|
+
let allocatedAmount = 0;
|
|
7025
|
+
if (idx === prepped.length - 1) {
|
|
7026
|
+
allocatedAmount = roundToPaisa(transactionPrice - allocatedSum);
|
|
7027
|
+
} else {
|
|
7028
|
+
const ratio = ob.standaloneSellingPrice / sspDenominator;
|
|
7029
|
+
allocatedAmount = roundToPaisa(transactionPrice * ratio);
|
|
7030
|
+
allocatedSum = roundToPaisa(allocatedSum + allocatedAmount);
|
|
7031
|
+
}
|
|
7032
|
+
|
|
7033
|
+
let revenueRecognized = 0;
|
|
7034
|
+
let satisfactionProgressPercent = 0;
|
|
7035
|
+
|
|
7036
|
+
if (ob.satisfactionPattern === 'POINT_IN_TIME') {
|
|
7037
|
+
if (ob.isPointInTimeSatisfied) {
|
|
7038
|
+
revenueRecognized = allocatedAmount;
|
|
7039
|
+
satisfactionProgressPercent = 100;
|
|
7040
|
+
}
|
|
7041
|
+
} else {
|
|
7042
|
+
if (ob.percentageCompletion > 0) {
|
|
7043
|
+
satisfactionProgressPercent = ob.percentageCompletion;
|
|
7044
|
+
revenueRecognized = roundToPaisa(allocatedAmount * (ob.percentageCompletion / 100));
|
|
7045
|
+
} else if (ob.termMonths > 0) {
|
|
7046
|
+
const fraction = Math.min(1, ob.monthsElapsed / ob.termMonths);
|
|
7047
|
+
satisfactionProgressPercent = Number((fraction * 100).toFixed(2));
|
|
7048
|
+
revenueRecognized = roundToPaisa(allocatedAmount * fraction);
|
|
7049
|
+
}
|
|
7050
|
+
}
|
|
7051
|
+
|
|
7052
|
+
const unearnedDeferredRevenue = roundToPaisa(Math.max(0, allocatedAmount - revenueRecognized));
|
|
7053
|
+
totalRevenueRecognized = roundToPaisa(totalRevenueRecognized + revenueRecognized);
|
|
7054
|
+
totalBilled = roundToPaisa(totalBilled + ob.amountBilledToDate);
|
|
7055
|
+
|
|
7056
|
+
return {
|
|
7057
|
+
id: ob.id,
|
|
7058
|
+
description: ob.description,
|
|
7059
|
+
standaloneSellingPriceInr: ob.standaloneSellingPrice,
|
|
7060
|
+
sspWeightPercentage: Number(((ob.standaloneSellingPrice / sspDenominator) * 100).toFixed(2)),
|
|
7061
|
+
allocatedTransactionPriceInr: allocatedAmount,
|
|
7062
|
+
satisfactionPattern: ob.satisfactionPattern,
|
|
7063
|
+
satisfactionProgressPercent,
|
|
7064
|
+
cumulativeRevenueRecognizedInr: revenueRecognized,
|
|
7065
|
+
unearnedDeferredRevenueInr: unearnedDeferredRevenue,
|
|
7066
|
+
amountBilledToDateInr: ob.amountBilledToDate
|
|
7067
|
+
};
|
|
7068
|
+
});
|
|
7069
|
+
|
|
7070
|
+
const netContractPosition = roundToPaisa(totalRevenueRecognized - totalBilled);
|
|
7071
|
+
const contractAssetUnbilled = netContractPosition > 0 ? netContractPosition : 0;
|
|
7072
|
+
const contractLiabilityDeferred = netContractPosition < 0 ? Math.abs(netContractPosition) : 0;
|
|
7073
|
+
|
|
7074
|
+
return {
|
|
7075
|
+
status: 'SUCCESS',
|
|
7076
|
+
standard: 'Ind AS 115 / IFRS 15 / US GAAP ASC 606',
|
|
7077
|
+
contractId: params.contractId || 'CONTRACT-UNSPECIFIED',
|
|
7078
|
+
totalContractTransactionPriceInr: transactionPrice,
|
|
7079
|
+
totalStandaloneSellingPriceInr: totalSsp,
|
|
7080
|
+
bundleDiscountInr: bundleDiscount,
|
|
7081
|
+
bundleDiscountPercentage,
|
|
7082
|
+
allocatedObligations,
|
|
7083
|
+
totalCumulativeRevenueRecognizedInr: totalRevenueRecognized,
|
|
7084
|
+
totalBilledToDateInr: totalBilled,
|
|
7085
|
+
contractBalanceSummary: {
|
|
7086
|
+
netContractAssetUnbilledReceivableInr: contractAssetUnbilled,
|
|
7087
|
+
netContractLiabilityDeferredRevenueInr: contractLiabilityDeferred,
|
|
7088
|
+
disposition: contractAssetUnbilled > 0
|
|
7089
|
+
? 'CONTRACT_ASSET_UNBILLED_RECEIVABLE'
|
|
7090
|
+
: (contractLiabilityDeferred > 0 ? 'CONTRACT_LIABILITY_DEFERRED_REVENUE' : 'CONTRACT_BALANCED')
|
|
7091
|
+
},
|
|
7092
|
+
isAuthorizedToDisburse: false,
|
|
7093
|
+
statutoryRationale: `Ind AS 115 5-Step Evaluation complete: Relative SSP allocation on total contract price INR ${transactionPrice.toLocaleString('en-IN')} across ${allocatedObligations.length} obligations. Cumulative recognized revenue: INR ${totalRevenueRecognized.toLocaleString('en-IN')} vs billed INR ${totalBilled.toLocaleString('en-IN')}. Net contract balance: ${contractAssetUnbilled > 0 ? `Contract Asset INR ${contractAssetUnbilled.toLocaleString('en-IN')}` : `Contract Liability INR ${contractLiabilityDeferred.toLocaleString('en-IN')}`}.`
|
|
7094
|
+
};
|
|
7095
|
+
}
|
|
7096
|
+
|
|
7097
|
+
/**
|
|
7098
|
+
* Ind AS 116 / IFRS 16 / US GAAP ASC 842: Right-of-Use (ROU) Asset & Lease Liability Amortization Schedule.
|
|
7099
|
+
*/
|
|
7100
|
+
export function calculateLeaseAmortizationIndAs116(params = {}) {
|
|
7101
|
+
const monthlyPayment = roundToPaisa(cleanNumericAmount(params.monthlyPaymentInr, { defaultValue: 0 }));
|
|
7102
|
+
const termMonths = Math.max(0, Math.floor(cleanNumericAmount(params.leaseTermMonths, { defaultValue: 0 })));
|
|
7103
|
+
const annualRate = cleanNumericAmount(params.annualDiscountRate, { defaultValue: 0.085 });
|
|
7104
|
+
const isAdvance = String(params.paymentTiming || 'ARREARS').toUpperCase().trim() === 'ADVANCE';
|
|
7105
|
+
const directCosts = roundToPaisa(cleanNumericAmount(params.initialDirectCostsInr, { defaultValue: 0 }));
|
|
7106
|
+
const incentives = roundToPaisa(cleanNumericAmount(params.leaseIncentivesReceivedInr, { defaultValue: 0 }));
|
|
7107
|
+
|
|
7108
|
+
if (params.electionShortTermExemption && termMonths <= 12) {
|
|
7109
|
+
return {
|
|
7110
|
+
status: 'EXEMPT_SHORT_TERM',
|
|
7111
|
+
standard: 'Ind AS 116 / IFRS 16 / US GAAP ASC 842',
|
|
7112
|
+
isCapitalizedOnBalanceSheet: false,
|
|
7113
|
+
statutoryStatus: 'SHORT_TERM_LEASE_OFF_BALANCE_SHEET',
|
|
7114
|
+
leaseTermMonths: termMonths,
|
|
7115
|
+
monthlyPaymentInr: monthlyPayment,
|
|
7116
|
+
monthlyStraightLinePnlExpenseInr: monthlyPayment,
|
|
7117
|
+
totalUndiscountedCashPaymentsInr: roundToPaisa(monthlyPayment * termMonths),
|
|
7118
|
+
initialPresentValueLeaseLiabilityInr: 0,
|
|
7119
|
+
initialRightOfUseAssetInr: 0,
|
|
7120
|
+
isAuthorizedToDisburse: false,
|
|
7121
|
+
statutoryRationale: `Short-term lease exemption elected under Ind AS 116. Lease term of ${termMonths} months is <= 12 months without purchase option. Lease payments recognized as rent expense on straight-line basis in P&L. Off-balance sheet.`
|
|
7122
|
+
};
|
|
7123
|
+
}
|
|
7124
|
+
|
|
7125
|
+
if (params.electionLowValueExemption) {
|
|
7126
|
+
return {
|
|
7127
|
+
status: 'EXEMPT_LOW_VALUE',
|
|
7128
|
+
standard: 'Ind AS 116 / IFRS 16 / US GAAP ASC 842',
|
|
7129
|
+
isCapitalizedOnBalanceSheet: false,
|
|
7130
|
+
statutoryStatus: 'LOW_VALUE_LEASE_OFF_BALANCE_SHEET',
|
|
7131
|
+
leaseTermMonths: termMonths,
|
|
7132
|
+
monthlyPaymentInr: monthlyPayment,
|
|
7133
|
+
monthlyStraightLinePnlExpenseInr: monthlyPayment,
|
|
7134
|
+
totalUndiscountedCashPaymentsInr: roundToPaisa(monthlyPayment * termMonths),
|
|
7135
|
+
initialPresentValueLeaseLiabilityInr: 0,
|
|
7136
|
+
initialRightOfUseAssetInr: 0,
|
|
7137
|
+
isAuthorizedToDisburse: false,
|
|
7138
|
+
statutoryRationale: 'Low-value underlying asset exemption elected under Ind AS 116. Lease payments recognized in P&L on straight-line basis. Off-balance sheet.'
|
|
7139
|
+
};
|
|
7140
|
+
}
|
|
7141
|
+
|
|
7142
|
+
if (monthlyPayment <= 0 || termMonths <= 0 || annualRate <= 0) {
|
|
7143
|
+
return {
|
|
7144
|
+
status: 'INSUFFICIENT_DATA',
|
|
7145
|
+
standard: 'Ind AS 116 / IFRS 16 / US GAAP ASC 842',
|
|
7146
|
+
isCapitalizedOnBalanceSheet: false,
|
|
7147
|
+
isAuthorizedToDisburse: false,
|
|
7148
|
+
statutoryRationale: 'Monthly payment, non-zero lease term, and positive incremental borrowing rate (IBR) are mandatory.'
|
|
7149
|
+
};
|
|
7150
|
+
}
|
|
7151
|
+
|
|
7152
|
+
const monthlyRate = annualRate / 12;
|
|
7153
|
+
let pvMultiplier = (1 - Math.pow(1 + monthlyRate, -termMonths)) / monthlyRate;
|
|
7154
|
+
if (isAdvance) {
|
|
7155
|
+
pvMultiplier = pvMultiplier * (1 + monthlyRate);
|
|
7156
|
+
}
|
|
7157
|
+
|
|
7158
|
+
const initialLiability = roundToPaisa(monthlyPayment * pvMultiplier);
|
|
7159
|
+
const initialRouAsset = roundToPaisa(initialLiability + directCosts - incentives);
|
|
7160
|
+
const monthlyDepreciation = roundToPaisa(initialRouAsset / termMonths);
|
|
7161
|
+
|
|
7162
|
+
let runningLiability = initialLiability;
|
|
7163
|
+
let year1InterestTotal = 0;
|
|
7164
|
+
let year1DepreciationTotal = 0;
|
|
7165
|
+
let year1CashTotal = 0;
|
|
7166
|
+
const monthlySchedules = [];
|
|
7167
|
+
|
|
7168
|
+
const previewMonths = Math.min(12, termMonths);
|
|
7169
|
+
for (let m = 1; m <= previewMonths; m++) {
|
|
7170
|
+
let interestExpense = 0;
|
|
7171
|
+
let principalReduction = 0;
|
|
7172
|
+
|
|
7173
|
+
if (isAdvance && m === 1) {
|
|
7174
|
+
interestExpense = 0;
|
|
7175
|
+
principalReduction = monthlyPayment;
|
|
7176
|
+
runningLiability = roundToPaisa(runningLiability - principalReduction);
|
|
7177
|
+
} else {
|
|
7178
|
+
interestExpense = roundToPaisa(runningLiability * monthlyRate);
|
|
7179
|
+
principalReduction = roundToPaisa(monthlyPayment - interestExpense);
|
|
7180
|
+
runningLiability = roundToPaisa(Math.max(0, runningLiability - principalReduction));
|
|
7181
|
+
}
|
|
7182
|
+
|
|
7183
|
+
year1InterestTotal = roundToPaisa(year1InterestTotal + interestExpense);
|
|
7184
|
+
year1DepreciationTotal = roundToPaisa(year1DepreciationTotal + monthlyDepreciation);
|
|
7185
|
+
year1CashTotal = roundToPaisa(year1CashTotal + monthlyPayment);
|
|
7186
|
+
|
|
7187
|
+
monthlySchedules.push({
|
|
7188
|
+
month: m,
|
|
7189
|
+
openingLiabilityInr: isAdvance ? runningLiability : roundToPaisa(runningLiability + principalReduction),
|
|
7190
|
+
paymentInr: monthlyPayment,
|
|
7191
|
+
interestExpenseInr: interestExpense,
|
|
7192
|
+
principalReductionInr: principalReduction,
|
|
7193
|
+
closingLiabilityInr: runningLiability,
|
|
7194
|
+
monthlyRouDepreciationInr: monthlyDepreciation
|
|
7195
|
+
});
|
|
7196
|
+
}
|
|
7197
|
+
|
|
7198
|
+
const totalUndiscountedCash = roundToPaisa(monthlyPayment * termMonths);
|
|
7199
|
+
const totalLifetimeInterest = roundToPaisa(totalUndiscountedCash - initialLiability);
|
|
7200
|
+
|
|
7201
|
+
return {
|
|
7202
|
+
status: 'SUCCESS',
|
|
7203
|
+
standard: 'Ind AS 116 / IFRS 16 / US GAAP ASC 842',
|
|
7204
|
+
isCapitalizedOnBalanceSheet: true,
|
|
7205
|
+
leaseTermMonths: termMonths,
|
|
7206
|
+
paymentTiming: isAdvance ? 'ADVANCE' : 'ARREARS',
|
|
7207
|
+
annualDiscountRate: annualRate,
|
|
7208
|
+
monthlyDiscountRate: Number(monthlyRate.toFixed(6)),
|
|
7209
|
+
monthlyContractualPaymentInr: monthlyPayment,
|
|
7210
|
+
totalUndiscountedCashPaymentsInr: totalUndiscountedCash,
|
|
7211
|
+
initialPresentValueLeaseLiabilityInr: initialLiability,
|
|
7212
|
+
initialRightOfUseAssetInr: initialRouAsset,
|
|
7213
|
+
monthlyStraightLineDepreciationInr: monthlyDepreciation,
|
|
7214
|
+
totalLifetimeInterestExpenseInr: totalLifetimeInterest,
|
|
7215
|
+
year1Summary: {
|
|
7216
|
+
year1CashPaymentsInr: year1CashTotal,
|
|
7217
|
+
year1InterestExpenseInr: year1InterestTotal,
|
|
7218
|
+
year1DepreciationExpenseInr: year1DepreciationTotal,
|
|
7219
|
+
year1TotalPnlExpenseInr: roundToPaisa(year1InterestTotal + year1DepreciationTotal),
|
|
7220
|
+
year1FrontLoadedVarianceInr: roundToPaisa((year1InterestTotal + year1DepreciationTotal) - year1CashTotal)
|
|
7221
|
+
},
|
|
7222
|
+
first12MonthsAmortizationPreview: monthlySchedules,
|
|
7223
|
+
isAuthorizedToDisburse: false,
|
|
7224
|
+
statutoryRationale: `Ind AS 116 on-balance-sheet capitalization active: Present value of future lease payments discounted at ${(annualRate * 100).toFixed(2)}% IBR yields initial Lease Liability of INR ${initialLiability.toLocaleString('en-IN')} and initial ROU Asset of INR ${initialRouAsset.toLocaleString('en-IN')}. Monthly straight-line depreciation: INR ${monthlyDepreciation.toLocaleString('en-IN')}. Year 1 P&L charge: INR ${(year1InterestTotal + year1DepreciationTotal).toLocaleString('en-IN')} (Interest: INR ${year1InterestTotal.toLocaleString('en-IN')}, Depreciation: INR ${year1DepreciationTotal.toLocaleString('en-IN')}).`
|
|
7225
|
+
};
|
|
7226
|
+
}
|
|
7227
|
+
|
|
7228
|
+
/**
|
|
7229
|
+
* Ind AS 109 / IFRS 9 / US GAAP ASC 326: Expected Credit Loss (ECL) Impairment Calculator.
|
|
7230
|
+
*/
|
|
7231
|
+
export function calculateExpectedCreditLossIndAs109(params = {}) {
|
|
7232
|
+
const grossTotal = roundToPaisa(cleanNumericAmount(params.grossTradeReceivablesInr, { defaultValue: 0 }));
|
|
7233
|
+
const rawBuckets = Array.isArray(params.agingBuckets) ? params.agingBuckets : [];
|
|
7234
|
+
const macroFactor = cleanNumericAmount(params.macroeconomicAdjustmentFactor, { defaultValue: 1.0 });
|
|
7235
|
+
const lgdPercent = cleanNumericAmount(params.lossGivenDefaultPercent, { defaultValue: 100.0 });
|
|
7236
|
+
const asOfDate = params.reportingDate || new Date().toISOString().split('T')[0];
|
|
7237
|
+
|
|
7238
|
+
if (grossTotal <= 0 && rawBuckets.length === 0) {
|
|
7239
|
+
return {
|
|
7240
|
+
status: 'INSUFFICIENT_DATA',
|
|
7241
|
+
standard: 'Ind AS 109 / IFRS 9 / US GAAP ASC 326',
|
|
7242
|
+
isAuthorizedToDisburse: false,
|
|
7243
|
+
statutoryRationale: 'Gross trade receivables or non-empty aging buckets are mandatory.'
|
|
7244
|
+
};
|
|
7245
|
+
}
|
|
7246
|
+
|
|
7247
|
+
let totalComputedGross = 0;
|
|
7248
|
+
let totalLossAllowance = 0;
|
|
7249
|
+
let stage1Allowance = 0;
|
|
7250
|
+
let stage2Allowance = 0;
|
|
7251
|
+
let stage3Allowance = 0;
|
|
7252
|
+
let stage1Gross = 0;
|
|
7253
|
+
let stage2Gross = 0;
|
|
7254
|
+
let stage3Gross = 0;
|
|
7255
|
+
|
|
7256
|
+
const evaluatedBuckets = rawBuckets.map((b, idx) => {
|
|
7257
|
+
const bucketGross = roundToPaisa(cleanNumericAmount(b.grossAmountInr, { defaultValue: 0 }));
|
|
7258
|
+
const histRate = cleanNumericAmount(b.historicalDefaultRatePercent, { defaultValue: 0 });
|
|
7259
|
+
const stage = Number(b.stage || (idx === 0 ? 1 : (idx === rawBuckets.length - 1 ? 3 : 2)));
|
|
7260
|
+
|
|
7261
|
+
const effectiveLossRatePercent = Number(Math.min(100, Math.max(0, histRate * macroFactor * (lgdPercent / 100))).toFixed(4));
|
|
7262
|
+
const allowance = roundToPaisa(bucketGross * (effectiveLossRatePercent / 100));
|
|
7263
|
+
const netCarryingAmount = roundToPaisa(bucketGross - allowance);
|
|
7264
|
+
|
|
7265
|
+
totalComputedGross = roundToPaisa(totalComputedGross + bucketGross);
|
|
7266
|
+
totalLossAllowance = roundToPaisa(totalLossAllowance + allowance);
|
|
7267
|
+
|
|
7268
|
+
if (stage === 1) {
|
|
7269
|
+
stage1Gross = roundToPaisa(stage1Gross + bucketGross);
|
|
7270
|
+
stage1Allowance = roundToPaisa(stage1Allowance + allowance);
|
|
7271
|
+
} else if (stage === 2) {
|
|
7272
|
+
stage2Gross = roundToPaisa(stage2Gross + bucketGross);
|
|
7273
|
+
stage2Allowance = roundToPaisa(stage2Allowance + allowance);
|
|
7274
|
+
} else {
|
|
7275
|
+
stage3Gross = roundToPaisa(stage3Gross + bucketGross);
|
|
7276
|
+
stage3Allowance = roundToPaisa(stage3Allowance + allowance);
|
|
7277
|
+
}
|
|
7278
|
+
|
|
7279
|
+
return {
|
|
7280
|
+
bucketLabel: b.bucketLabel || `Bracket-${idx + 1}`,
|
|
7281
|
+
grossAmountInr: bucketGross,
|
|
7282
|
+
stage,
|
|
7283
|
+
historicalDefaultRatePercent: histRate,
|
|
7284
|
+
effectiveLossRatePercent,
|
|
7285
|
+
lossAllowanceInr: allowance,
|
|
7286
|
+
netCarryingAmountInr: netCarryingAmount
|
|
7287
|
+
};
|
|
7288
|
+
});
|
|
7289
|
+
|
|
7290
|
+
const reconciledGross = grossTotal > 0 ? grossTotal : totalComputedGross;
|
|
7291
|
+
const netReceivables = roundToPaisa(reconciledGross - totalLossAllowance);
|
|
7292
|
+
const overallEffectiveRate = reconciledGross > 0 ? Number(((totalLossAllowance / reconciledGross) * 100).toFixed(2)) : 0;
|
|
7293
|
+
|
|
7294
|
+
return {
|
|
7295
|
+
status: 'SUCCESS',
|
|
7296
|
+
standard: 'Ind AS 109 / IFRS 9 / US GAAP ASC 326',
|
|
7297
|
+
asOfDate,
|
|
7298
|
+
grossTradeReceivablesInr: reconciledGross,
|
|
7299
|
+
totalExpectedCreditLossAllowanceInr: totalLossAllowance,
|
|
7300
|
+
netTradeReceivablesCarryingAmountInr: netReceivables,
|
|
7301
|
+
overallEffectiveLossRatePercent: overallEffectiveRate,
|
|
7302
|
+
macroeconomicAdjustmentFactor: macroFactor,
|
|
7303
|
+
stagingSummary: {
|
|
7304
|
+
stage1: { grossInr: stage1Gross, allowanceInr: stage1Allowance, label: '12-Month ECL (Performing)' },
|
|
7305
|
+
stage2: { grossInr: stage2Gross, allowanceInr: stage2Allowance, label: 'Lifetime ECL (SICR / Underperforming)' },
|
|
7306
|
+
stage3: { grossInr: stage3Gross, allowanceInr: stage3Allowance, label: 'Lifetime ECL (Credit Impaired / Default)' }
|
|
7307
|
+
},
|
|
7308
|
+
agingBuckets: evaluatedBuckets,
|
|
7309
|
+
taxTreatment: {
|
|
7310
|
+
isTaxDeductibleInCurrentYear: false,
|
|
7311
|
+
statutoryAddBackSection: 'Section 36(1)(vii) read with Section 40A(9) Income-tax Act, 1961',
|
|
7312
|
+
taxConsequence: `ECL impairment provision of INR ${totalLossAllowance.toLocaleString('en-IN')} is not deductible for Indian corporate tax purposes until actual bad debt write-off. Recognizes a Deferred Tax Asset (DTA) under Ind AS 12.`
|
|
7313
|
+
},
|
|
7314
|
+
isAuthorizedToDisburse: false,
|
|
7315
|
+
statutoryRationale: `Ind AS 109 Simplified Approach: Expected Credit Loss provision of INR ${totalLossAllowance.toLocaleString('en-IN')} computed on gross receivables of INR ${reconciledGross.toLocaleString('en-IN')} (overall rate: ${overallEffectiveRate}%). Net receivables on balance sheet: INR ${netReceivables.toLocaleString('en-IN')}. Accounting impairment does not authorize fund disbursement.`
|
|
7316
|
+
};
|
|
7317
|
+
}
|
|
7318
|
+
|
|
7319
|
+
/**
|
|
7320
|
+
* Ind AS 102 / IFRS 2 / US GAAP ASC 718: ESOP Graded Vesting Amortization Calculator.
|
|
7321
|
+
*/
|
|
7322
|
+
export function calculateEsopAmortizationIndAs102(params = {}) {
|
|
7323
|
+
const totalOptions = Math.max(0, Math.floor(cleanNumericAmount(params.totalOptionsGranted, { defaultValue: 0 })));
|
|
7324
|
+
const fairValuePerOption = roundToPaisa(cleanNumericAmount(params.grantDateFairValuePerOptionInr, { defaultValue: 0 }));
|
|
7325
|
+
const defaultTranches = [
|
|
7326
|
+
{ trancheNumber: 1, vestingYears: 1, allocationPercentage: 25, expectedForfeitureRate: 0 },
|
|
7327
|
+
{ trancheNumber: 2, vestingYears: 2, allocationPercentage: 25, expectedForfeitureRate: 0 },
|
|
7328
|
+
{ trancheNumber: 3, vestingYears: 3, allocationPercentage: 25, expectedForfeitureRate: 0 },
|
|
7329
|
+
{ trancheNumber: 4, vestingYears: 4, allocationPercentage: 25, expectedForfeitureRate: 0 }
|
|
7330
|
+
];
|
|
7331
|
+
const tranches = Array.isArray(params.vestingTranches) && params.vestingTranches.length > 0 ? params.vestingTranches : defaultTranches;
|
|
7332
|
+
|
|
7333
|
+
if (totalOptions <= 0 || fairValuePerOption <= 0) {
|
|
7334
|
+
return {
|
|
7335
|
+
status: 'INSUFFICIENT_DATA',
|
|
7336
|
+
standard: 'Ind AS 102 / IFRS 2 / US GAAP ASC 718',
|
|
7337
|
+
isAuthorizedToDisburse: false,
|
|
7338
|
+
statutoryRationale: 'Total options granted and positive grant-date fair value per option are mandatory.'
|
|
7339
|
+
};
|
|
7340
|
+
}
|
|
7341
|
+
|
|
7342
|
+
const totalGrantDateFairValue = roundToPaisa(totalOptions * fairValuePerOption);
|
|
7343
|
+
let maxVestingYear = 1;
|
|
7344
|
+
let totalAllocatedOptions = 0;
|
|
7345
|
+
let totalAdjustedFairValue = 0;
|
|
7346
|
+
|
|
7347
|
+
const processedTranches = tranches.map((t, idx) => {
|
|
7348
|
+
const trancheNum = t.trancheNumber || idx + 1;
|
|
7349
|
+
const years = Math.max(0.5, cleanNumericAmount(t.vestingYears, { defaultValue: idx + 1 }));
|
|
7350
|
+
if (years > maxVestingYear) maxVestingYear = Math.ceil(years);
|
|
7351
|
+
|
|
7352
|
+
const allocPct = cleanNumericAmount(t.allocationPercentage, { defaultValue: 100 / tranches.length });
|
|
7353
|
+
const forfeitRate = cleanNumericAmount(t.expectedForfeitureRate, { defaultValue: 0 });
|
|
7354
|
+
|
|
7355
|
+
const nominalOptions = Math.floor(totalOptions * (allocPct / 100));
|
|
7356
|
+
const effectiveOptions = Math.floor(nominalOptions * (1 - forfeitRate / 100));
|
|
7357
|
+
const trancheFairValue = roundToPaisa(effectiveOptions * fairValuePerOption);
|
|
7358
|
+
const annualAmortization = roundToPaisa(trancheFairValue / years);
|
|
7359
|
+
|
|
7360
|
+
totalAllocatedOptions += effectiveOptions;
|
|
7361
|
+
totalAdjustedFairValue = roundToPaisa(totalAdjustedFairValue + trancheFairValue);
|
|
7362
|
+
|
|
7363
|
+
return {
|
|
7364
|
+
trancheNumber: trancheNum,
|
|
7365
|
+
vestingYears: years,
|
|
7366
|
+
allocationPercentage: allocPct,
|
|
7367
|
+
expectedForfeitureRatePercent: forfeitRate,
|
|
7368
|
+
effectiveOptionsGranted: effectiveOptions,
|
|
7369
|
+
trancheGrantDateFairValueInr: trancheFairValue,
|
|
7370
|
+
annualAmortizationExpenseInr: annualAmortization
|
|
7371
|
+
};
|
|
7372
|
+
});
|
|
7373
|
+
|
|
7374
|
+
const yearlyExpenseProfile = [];
|
|
7375
|
+
let cumulativeExpense = 0;
|
|
7376
|
+
for (let yr = 1; yr <= maxVestingYear; yr++) {
|
|
7377
|
+
let yearExpense = 0;
|
|
7378
|
+
for (const pt of processedTranches) {
|
|
7379
|
+
if (yr <= Math.ceil(pt.vestingYears)) {
|
|
7380
|
+
yearExpense = roundToPaisa(yearExpense + pt.annualAmortizationExpenseInr);
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7383
|
+
cumulativeExpense = roundToPaisa(cumulativeExpense + yearExpense);
|
|
7384
|
+
yearlyExpenseProfile.push({
|
|
7385
|
+
year: yr,
|
|
7386
|
+
pnlCompensationExpenseInr: yearExpense,
|
|
7387
|
+
cumulativeExpenseRecognizedInr: cumulativeExpense,
|
|
7388
|
+
closingShareBasedPaymentReserveInr: cumulativeExpense
|
|
7389
|
+
});
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
const year1Expense = yearlyExpenseProfile.length > 0 ? yearlyExpenseProfile[0].pnlCompensationExpenseInr : 0;
|
|
7393
|
+
const flatStraightLineAverage = roundToPaisa(totalAdjustedFairValue / maxVestingYear);
|
|
7394
|
+
const isFrontLoaded = year1Expense > flatStraightLineAverage;
|
|
7395
|
+
|
|
7396
|
+
return {
|
|
7397
|
+
status: 'SUCCESS',
|
|
7398
|
+
standard: 'Ind AS 102 / IFRS 2 / US GAAP ASC 718',
|
|
7399
|
+
schemeName: params.schemeName || 'SCHEME-DEFAULT-ESOP',
|
|
7400
|
+
totalOptionsGranted: totalOptions,
|
|
7401
|
+
effectiveVestingOptions: totalAllocatedOptions,
|
|
7402
|
+
grantDateFairValuePerOptionInr: fairValuePerOption,
|
|
7403
|
+
totalNominalGrantDateFairValueInr: totalGrantDateFairValue,
|
|
7404
|
+
totalNetFairValueToExpenseInr: totalAdjustedFairValue,
|
|
7405
|
+
maxVestingYears: maxVestingYear,
|
|
7406
|
+
isGradedVestingFrontLoaded: isFrontLoaded,
|
|
7407
|
+
year1ExpenseComparison: {
|
|
7408
|
+
year1GradedVestingExpenseInr: year1Expense,
|
|
7409
|
+
hypotheticalFlatAverageInr: flatStraightLineAverage,
|
|
7410
|
+
frontLoadingAccelerationInr: roundToPaisa(year1Expense - flatStraightLineAverage)
|
|
7411
|
+
},
|
|
7412
|
+
vestingTranches: processedTranches,
|
|
7413
|
+
yearlyExpenseProfile,
|
|
7414
|
+
taxTreatment: {
|
|
7415
|
+
statutoryDeductionSection: 'Section 37(1) Income-tax Act, 1961 (Biocon Special Bench Principle)',
|
|
7416
|
+
perquisiteTaxSection: 'Section 17(2)(vi) read with Rule 3(8) Income-tax Rules, 1962',
|
|
7417
|
+
deductibilitySummary: `Company is eligible to claim ESOP discount as an allowable business deduction under Section 37(1) spread over the vesting period. Upon exercise, employee is subject to salary TDS under Section 192 on perquisite value (FMV on exercise date minus exercise price).`
|
|
7418
|
+
},
|
|
7419
|
+
isAuthorizedToDisburse: false,
|
|
7420
|
+
statutoryRationale: `Ind AS 102 Graded Vesting: Total ESOP fair value INR ${totalAdjustedFairValue.toLocaleString('en-IN')} across ${processedTranches.length} tranches. Mandatory separate-tranche accounting produces Year 1 front-loaded expense of INR ${year1Expense.toLocaleString('en-IN')} (vs flat average INR ${flatStraightLineAverage.toLocaleString('en-IN')}). Share-based compensation does not authorize cash disbursement.`
|
|
7421
|
+
};
|
|
7422
|
+
}
|
|
7423
|
+
|
|
7424
|
+
/**
|
|
7425
|
+
* Ind AS 12 / IAS 12 / US GAAP ASC 740: Income Taxes & Deferred Tax (DTA / DTL) Calculator.
|
|
7426
|
+
*/
|
|
7427
|
+
export function calculateDeferredTaxIndAs12(params = {}) {
|
|
7428
|
+
const bookProfit = roundToPaisa(cleanNumericAmount(params.accountingProfitBeforeTaxInr, { defaultValue: 0 }));
|
|
7429
|
+
const taxRatePercent = cleanNumericAmount(params.enactedTaxRatePercent, { defaultValue: 25.168 });
|
|
7430
|
+
const taxRate = taxRatePercent / 100;
|
|
7431
|
+
const diffs = params.temporaryDifferences || {};
|
|
7432
|
+
const permanentDiffs = roundToPaisa(cleanNumericAmount(params.permanentDifferencesInr, { defaultValue: 0 }));
|
|
7433
|
+
const openingNetDta = roundToPaisa(cleanNumericAmount(params.openingNetDtaInr, { defaultValue: 0 }));
|
|
7434
|
+
const fy = params.financialYear || '2025-26';
|
|
7435
|
+
|
|
7436
|
+
if (taxRate <= 0) {
|
|
7437
|
+
return {
|
|
7438
|
+
status: 'INSUFFICIENT_DATA',
|
|
7439
|
+
standard: 'Ind AS 12 / IAS 12 / US GAAP ASC 740',
|
|
7440
|
+
isAuthorizedToDisburse: false,
|
|
7441
|
+
statutoryRationale: 'Positive enacted tax rate is mandatory.'
|
|
7442
|
+
};
|
|
7443
|
+
}
|
|
7444
|
+
|
|
7445
|
+
const msme43bH = roundToPaisa(cleanNumericAmount(diffs.msmeDisallowanceSec43bHInr, { defaultValue: 0 }));
|
|
7446
|
+
const statutory43B = roundToPaisa(cleanNumericAmount(diffs.statutoryDuesUnpaidSec43BInr, { defaultValue: 0 }));
|
|
7447
|
+
const ecl109 = roundToPaisa(cleanNumericAmount(diffs.eclLossAllowanceIndAs109Inr, { defaultValue: 0 }));
|
|
7448
|
+
const lease116 = roundToPaisa(cleanNumericAmount(diffs.leaseLiabilityNetIndAs116Inr, { defaultValue: 0 }));
|
|
7449
|
+
const esop102 = roundToPaisa(cleanNumericAmount(diffs.esopReserveUnvestedIndAs102Inr, { defaultValue: 0 }));
|
|
7450
|
+
const otherDeductible = roundToPaisa(cleanNumericAmount(diffs.otherDeductibleDifferencesInr, { defaultValue: 0 }));
|
|
7451
|
+
|
|
7452
|
+
const totalDeductibleDifferences = roundToPaisa(msme43bH + statutory43B + ecl109 + lease116 + esop102 + otherDeductible);
|
|
7453
|
+
const grossDta = roundToPaisa(totalDeductibleDifferences * taxRate);
|
|
7454
|
+
|
|
7455
|
+
const accelDep32 = roundToPaisa(cleanNumericAmount(diffs.acceleratedTaxDepreciationSec32Inr, { defaultValue: 0 }));
|
|
7456
|
+
const otherTaxable = roundToPaisa(cleanNumericAmount(diffs.otherTaxableDifferencesInr, { defaultValue: 0 }));
|
|
7457
|
+
|
|
7458
|
+
const totalTaxableDifferences = roundToPaisa(accelDep32 + otherTaxable);
|
|
7459
|
+
const grossDtl = roundToPaisa(totalTaxableDifferences * taxRate);
|
|
7460
|
+
|
|
7461
|
+
const netDeferredTaxBalance = roundToPaisa(grossDta - grossDtl);
|
|
7462
|
+
const closingNetDta = netDeferredTaxBalance > 0 ? netDeferredTaxBalance : 0;
|
|
7463
|
+
const closingNetDtl = netDeferredTaxBalance < 0 ? Math.abs(netDeferredTaxBalance) : 0;
|
|
7464
|
+
|
|
7465
|
+
const taxableProfit = roundToPaisa(Math.max(0, bookProfit + totalDeductibleDifferences - totalTaxableDifferences + permanentDiffs));
|
|
7466
|
+
const currentTaxExpense = roundToPaisa(taxableProfit * taxRate);
|
|
7467
|
+
|
|
7468
|
+
const deferredTaxPnlCharge = roundToPaisa(openingNetDta - netDeferredTaxBalance);
|
|
7469
|
+
const totalTaxExpense = roundToPaisa(currentTaxExpense + deferredTaxPnlCharge);
|
|
7470
|
+
|
|
7471
|
+
const theoreticalTaxOnBookProfit = roundToPaisa(bookProfit * taxRate);
|
|
7472
|
+
const taxEffectPermanent = roundToPaisa(permanentDiffs * taxRate);
|
|
7473
|
+
const effectiveTaxRatePercent = bookProfit > 0 ? Number(((totalTaxExpense / bookProfit) * 100).toFixed(2)) : 0;
|
|
7474
|
+
|
|
7475
|
+
return {
|
|
7476
|
+
status: 'SUCCESS',
|
|
7477
|
+
standard: 'Ind AS 12 / IAS 12 / US GAAP ASC 740',
|
|
7478
|
+
financialYear: fy,
|
|
7479
|
+
accountingProfitBeforeTaxInr: bookProfit,
|
|
7480
|
+
enactedCorporateTaxRatePercent: taxRatePercent,
|
|
7481
|
+
taxableProfitForIncomeTaxInr: taxableProfit,
|
|
7482
|
+
currentTaxLiabilityInr: currentTaxExpense,
|
|
7483
|
+
temporaryDifferencesSummary: {
|
|
7484
|
+
totalDeductibleDifferencesInr: totalDeductibleDifferences,
|
|
7485
|
+
totalTaxableDifferencesInr: totalTaxableDifferences,
|
|
7486
|
+
netTemporaryDifferenceInr: roundToPaisa(totalDeductibleDifferences - totalTaxableDifferences),
|
|
7487
|
+
breakdown: {
|
|
7488
|
+
msmeDisallowanceSec43bHInr: msme43bH,
|
|
7489
|
+
statutoryDuesSec43BInr: statutory43B,
|
|
7490
|
+
eclLossAllowanceIndAs109Inr: ecl109,
|
|
7491
|
+
leaseNetIndAs116Inr: lease116,
|
|
7492
|
+
esopReserveIndAs102Inr: esop102,
|
|
7493
|
+
acceleratedTaxDepreciationSec32Inr: accelDep32
|
|
7494
|
+
}
|
|
7495
|
+
},
|
|
7496
|
+
balanceSheetPosition: {
|
|
7497
|
+
grossDeferredTaxAssetInr: grossDta,
|
|
7498
|
+
grossDeferredTaxLiabilityInr: grossDtl,
|
|
7499
|
+
netDeferredTaxAssetBalanceSheetInr: closingNetDta,
|
|
7500
|
+
netDeferredTaxLiabilityBalanceSheetInr: closingNetDtl,
|
|
7501
|
+
disposition: closingNetDta > 0 ? 'NET_DEFERRED_TAX_ASSET' : (closingNetDtl > 0 ? 'NET_DEFERRED_TAX_LIABILITY' : 'BALANCED')
|
|
7502
|
+
},
|
|
7503
|
+
statementOfProfitAndLossTaxExpense: {
|
|
7504
|
+
currentTaxExpenseInr: currentTaxExpense,
|
|
7505
|
+
deferredTaxExpenseOrCreditInr: deferredTaxPnlCharge,
|
|
7506
|
+
totalTaxExpenseInr: totalTaxExpense
|
|
7507
|
+
},
|
|
7508
|
+
effectiveTaxRateReconciliation: {
|
|
7509
|
+
theoreticalTaxExpenseAtStatutoryRateInr: theoreticalTaxOnBookProfit,
|
|
7510
|
+
taxEffectOfPermanentDifferencesInr: taxEffectPermanent,
|
|
7511
|
+
reconciledTotalTaxExpenseInr: totalTaxExpense,
|
|
7512
|
+
effectiveTaxRatePercent,
|
|
7513
|
+
statutoryTaxRatePercent: taxRatePercent,
|
|
7514
|
+
rateVariancePercent: Number((effectiveTaxRatePercent - taxRatePercent).toFixed(2))
|
|
7515
|
+
},
|
|
7516
|
+
isAuthorizedToDisburse: false,
|
|
7517
|
+
statutoryRationale: `Ind AS 12 Balance Sheet Method: Current Tax of INR ${currentTaxExpense.toLocaleString('en-IN')} (Taxable Profit: INR ${taxableProfit.toLocaleString('en-IN')}). Closing Net ${closingNetDta > 0 ? `DTA of INR ${closingNetDta.toLocaleString('en-IN')}` : `DTL of INR ${closingNetDtl.toLocaleString('en-IN')}`}. Total P&L Tax Expense: INR ${totalTaxExpense.toLocaleString('en-IN')} (ETR: ${effectiveTaxRatePercent}% vs statutory ${taxRatePercent}%). Tax calculations do not authorize payment disbursement.`
|
|
7518
|
+
};
|
|
7519
|
+
}
|
|
7520
|
+
|
|
7521
|
+
/**
|
|
7522
|
+
* Ind AS 37 / IAS 37 / US GAAP ASC 450: Provisions, Contingent Liabilities & Contingent Assets Evaluator.
|
|
7523
|
+
*/
|
|
7524
|
+
export function evaluateProvisionsAndContingenciesIndAs37(params = {}) {
|
|
7525
|
+
const rawItems = Array.isArray(params.exposureItems) ? params.exposureItems : [];
|
|
7526
|
+
const asOfDate = params.reportingDate || new Date().toISOString().split('T')[0];
|
|
7527
|
+
|
|
7528
|
+
if (rawItems.length === 0) {
|
|
7529
|
+
return {
|
|
7530
|
+
status: 'INSUFFICIENT_DATA',
|
|
7531
|
+
standard: 'Ind AS 37 / IAS 37 / US GAAP ASC 450',
|
|
7532
|
+
isAuthorizedToDisburse: false,
|
|
7533
|
+
statutoryRationale: 'At least one exposure item must be provided for evaluation.'
|
|
7534
|
+
};
|
|
7535
|
+
}
|
|
7536
|
+
|
|
7537
|
+
let totalProvisionsAccrued = 0;
|
|
7538
|
+
let totalContingentLiabilities = 0;
|
|
7539
|
+
let totalRemoteExposures = 0;
|
|
7540
|
+
let totalReimbursements = 0;
|
|
7541
|
+
|
|
7542
|
+
const classifiedItems = rawItems.map((item, idx) => {
|
|
7543
|
+
const claimId = item.claimId || `EXP-${idx + 1}`;
|
|
7544
|
+
const desc = item.description || `Exposure item ${idx + 1}`;
|
|
7545
|
+
const claimedAmt = roundToPaisa(cleanNumericAmount(item.claimedAmountInr, { defaultValue: 0 }));
|
|
7546
|
+
const estimateAmt = roundToPaisa(cleanNumericAmount(item.bestEstimateAmountInr, { defaultValue: claimedAmt }));
|
|
7547
|
+
const prob = cleanNumericAmount(item.outflowProbabilityPercent, { defaultValue: 0 });
|
|
7548
|
+
const measurable = item.isReliablyMeasurable !== false;
|
|
7549
|
+
const reimb = roundToPaisa(cleanNumericAmount(item.expectedReimbursementInr, { defaultValue: 0 }));
|
|
7550
|
+
const disputeType = item.disputeType || 'STATUTORY_TAX_DISPUTE';
|
|
7551
|
+
|
|
7552
|
+
let classification = '';
|
|
7553
|
+
let accountingTreatment = '';
|
|
7554
|
+
let provisionAmount = 0;
|
|
7555
|
+
let contingentAmount = 0;
|
|
7556
|
+
let remoteAmount = 0;
|
|
7557
|
+
|
|
7558
|
+
if (prob > 50 && measurable) {
|
|
7559
|
+
classification = 'PROVISION_RECOGNIZED_ON_BALANCE_SHEET';
|
|
7560
|
+
provisionAmount = Math.max(0, roundToPaisa(estimateAmt - reimb));
|
|
7561
|
+
accountingTreatment = 'Recognize accrued liability on balance sheet and charge expense to profit or loss.';
|
|
7562
|
+
totalProvisionsAccrued = roundToPaisa(totalProvisionsAccrued + provisionAmount);
|
|
7563
|
+
totalReimbursements = roundToPaisa(totalReimbursements + reimb);
|
|
7564
|
+
} else if (prob >= 5 || (prob > 50 && !measurable)) {
|
|
7565
|
+
classification = 'CONTINGENT_LIABILITY_DISCLOSED_IN_NOTES';
|
|
7566
|
+
contingentAmount = estimateAmt;
|
|
7567
|
+
accountingTreatment = 'Do not recognize liability on balance sheet. Disclose in Schedule III Notes to Accounts.';
|
|
7568
|
+
totalContingentLiabilities = roundToPaisa(totalContingentLiabilities + contingentAmount);
|
|
7569
|
+
} else {
|
|
7570
|
+
classification = 'REMOTE_NO_DISCLOSURE_REQUIRED';
|
|
7571
|
+
remoteAmount = estimateAmt;
|
|
7572
|
+
accountingTreatment = 'Outflow probability is remote (< 5%). Neither recognition nor disclosure required.';
|
|
7573
|
+
totalRemoteExposures = roundToPaisa(totalRemoteExposures + remoteAmount);
|
|
7574
|
+
}
|
|
7575
|
+
|
|
7576
|
+
return {
|
|
7577
|
+
claimId,
|
|
7578
|
+
description: desc,
|
|
7579
|
+
disputeType,
|
|
7580
|
+
claimedAmountInr: claimedAmt,
|
|
7581
|
+
bestEstimateAmountInr: estimateAmt,
|
|
7582
|
+
outflowProbabilityPercent: prob,
|
|
7583
|
+
isReliablyMeasurable: measurable,
|
|
7584
|
+
classification,
|
|
7585
|
+
accountingTreatment,
|
|
7586
|
+
provisionRecognizedInr: provisionAmount,
|
|
7587
|
+
contingentLiabilityDisclosedInr: contingentAmount,
|
|
7588
|
+
remoteAmountInr: remoteAmount,
|
|
7589
|
+
expectedReimbursementInr: reimb
|
|
7590
|
+
};
|
|
7591
|
+
});
|
|
7592
|
+
|
|
7593
|
+
return {
|
|
7594
|
+
status: 'SUCCESS',
|
|
7595
|
+
standard: 'Ind AS 37 / IAS 37 / US GAAP ASC 450',
|
|
7596
|
+
asOfDate,
|
|
7597
|
+
totalItemsEvaluated: classifiedItems.length,
|
|
7598
|
+
summary: {
|
|
7599
|
+
totalProvisionsAccruedOnBalanceSheetInr: totalProvisionsAccrued,
|
|
7600
|
+
totalContingentLiabilitiesDisclosedInNotesInr: totalContingentLiabilities,
|
|
7601
|
+
totalRemoteExposuresNoDisclosureInr: totalRemoteExposures,
|
|
7602
|
+
totalExpectedReimbursementsInr: totalReimbursements
|
|
7603
|
+
},
|
|
7604
|
+
classifiedExposures: classifiedItems,
|
|
7605
|
+
scheduleThreeDisclosureNote: `Contingent Liabilities and Commitments (to the extent not provided for): The company has contingent liabilities of INR ${totalContingentLiabilities.toLocaleString('en-IN')} in respect of disputed statutory demands and claims under appeal where adverse outflow is possible (5%-50%) but not probable. Balance sheet provisions of INR ${totalProvisionsAccrued.toLocaleString('en-IN')} have been recognized where outflow is probable (> 50%).`,
|
|
7606
|
+
isAuthorizedToDisburse: false,
|
|
7607
|
+
statutoryRationale: `Ind AS 37 Evaluation complete: ${classifiedItems.length} exposures evaluated. Balance Sheet Provisions accrued: INR ${totalProvisionsAccrued.toLocaleString('en-IN')}; Footnote Contingent Liabilities: INR ${totalContingentLiabilities.toLocaleString('en-IN')}; Remote exclusions: INR ${totalRemoteExposures.toLocaleString('en-IN')}. Evaluation of legal claims does not authorize payment disbursement.`
|
|
7608
|
+
};
|
|
7609
|
+
}
|
|
7610
|
+
|
|
7611
|
+
/**
|
|
7612
|
+
* Ind AS 21 / IAS 21 / US GAAP ASC 830: Foreign Currency Monetary Revaluation & FCTR Translation Calculator.
|
|
7613
|
+
*/
|
|
7614
|
+
export function calculateForeignCurrencyIndAs21(params = {}) {
|
|
7615
|
+
const asOfDate = params.asOfDate || new Date().toISOString().split('T')[0];
|
|
7616
|
+
const functionalCurrency = params.functionalCurrency || 'INR';
|
|
7617
|
+
const rawItems = Array.isArray(params.items) ? params.items : [];
|
|
7618
|
+
const foreignOps = Array.isArray(params.foreignOperations) ? params.foreignOperations : [];
|
|
7619
|
+
|
|
7620
|
+
if (rawItems.length === 0 && foreignOps.length === 0) {
|
|
7621
|
+
return {
|
|
7622
|
+
status: 'INSUFFICIENT_DATA',
|
|
7623
|
+
standard: 'Ind AS 21 / IAS 21 / US GAAP ASC 830',
|
|
7624
|
+
asOfDate,
|
|
7625
|
+
itemsCount: 0,
|
|
7626
|
+
isAuthorizedToDisburse: false,
|
|
7627
|
+
statutoryRationale: 'At least one foreign currency item or foreign operation package must be provided for evaluation.'
|
|
7628
|
+
};
|
|
7629
|
+
}
|
|
7630
|
+
|
|
7631
|
+
let totalOriginalBookValueInr = 0;
|
|
7632
|
+
let totalClosingBookValueInr = 0;
|
|
7633
|
+
let totalUnrealizedGainInr = 0;
|
|
7634
|
+
let totalUnrealizedLossInr = 0;
|
|
7635
|
+
const journalEntries = [];
|
|
7636
|
+
|
|
7637
|
+
const revaluedItems = rawItems.map((item, idx) => {
|
|
7638
|
+
const id = item.id || `FX-ITEM-${idx + 1}`;
|
|
7639
|
+
const description = item.description || `Foreign Currency Exposure ${idx + 1}`;
|
|
7640
|
+
const itemType = String(item.itemType || 'MONETARY_ASSET').toUpperCase().trim();
|
|
7641
|
+
const foreignCurrency = item.foreignCurrency || 'USD';
|
|
7642
|
+
const foreignAmount = cleanNumericAmount(item.foreignAmount, { defaultValue: 0 });
|
|
7643
|
+
const bookingRate = cleanNumericAmount(item.bookingRate, { defaultValue: 1 });
|
|
7644
|
+
const closingRate = cleanNumericAmount(item.closingRate, { defaultValue: bookingRate });
|
|
7645
|
+
|
|
7646
|
+
const originalBookValueInr = roundToPaisa(foreignAmount * bookingRate);
|
|
7647
|
+
totalOriginalBookValueInr = roundToPaisa(totalOriginalBookValueInr + originalBookValueInr);
|
|
7648
|
+
|
|
7649
|
+
let closingBookValueInr = originalBookValueInr;
|
|
7650
|
+
let unrealizedGainLossInr = 0;
|
|
7651
|
+
let treatment = '';
|
|
7652
|
+
let glImpact = 'NONE';
|
|
7653
|
+
|
|
7654
|
+
if (itemType === 'NON_MONETARY_HISTORICAL') {
|
|
7655
|
+
closingBookValueInr = originalBookValueInr;
|
|
7656
|
+
unrealizedGainLossInr = 0;
|
|
7657
|
+
treatment = 'Carried at historical exchange rate under Ind AS 21.23(b). No closing revaluation recognized.';
|
|
7658
|
+
} else if (itemType === 'MONETARY_ASSET') {
|
|
7659
|
+
closingBookValueInr = roundToPaisa(foreignAmount * closingRate);
|
|
7660
|
+
unrealizedGainLossInr = roundToPaisa(closingBookValueInr - originalBookValueInr);
|
|
7661
|
+
|
|
7662
|
+
if (unrealizedGainLossInr > 0) {
|
|
7663
|
+
treatment = 'Unrealized foreign exchange gain credited to Statement of Profit & Loss.';
|
|
7664
|
+
glImpact = 'CREDIT_PL_GAIN';
|
|
7665
|
+
totalUnrealizedGainInr = roundToPaisa(totalUnrealizedGainInr + unrealizedGainLossInr);
|
|
7666
|
+
journalEntries.push({
|
|
7667
|
+
account: `Foreign Trade Receivables (${foreignCurrency})`,
|
|
7668
|
+
type: 'CURRENT_ASSET',
|
|
7669
|
+
debitInr: unrealizedGainLossInr,
|
|
7670
|
+
creditInr: 0,
|
|
7671
|
+
reference: id
|
|
7672
|
+
});
|
|
7673
|
+
journalEntries.push({
|
|
7674
|
+
account: 'Foreign Exchange Fluctuation Gain (P&L)',
|
|
7675
|
+
type: 'OTHER_INCOME',
|
|
7676
|
+
debitInr: 0,
|
|
7677
|
+
creditInr: unrealizedGainLossInr,
|
|
7678
|
+
reference: id
|
|
7679
|
+
});
|
|
7680
|
+
} else if (unrealizedGainLossInr < 0) {
|
|
7681
|
+
const lossAmt = Math.abs(unrealizedGainLossInr);
|
|
7682
|
+
treatment = 'Unrealized foreign exchange loss debited to Statement of Profit & Loss.';
|
|
7683
|
+
glImpact = 'DEBIT_PL_LOSS';
|
|
7684
|
+
totalUnrealizedLossInr = roundToPaisa(totalUnrealizedLossInr + lossAmt);
|
|
7685
|
+
journalEntries.push({
|
|
7686
|
+
account: 'Foreign Exchange Fluctuation Loss (P&L)',
|
|
7687
|
+
type: 'FINANCE_EXPENSE',
|
|
7688
|
+
debitInr: lossAmt,
|
|
7689
|
+
creditInr: 0,
|
|
7690
|
+
reference: id
|
|
7691
|
+
});
|
|
7692
|
+
journalEntries.push({
|
|
7693
|
+
account: `Foreign Trade Receivables (${foreignCurrency})`,
|
|
7694
|
+
type: 'CURRENT_ASSET',
|
|
7695
|
+
debitInr: 0,
|
|
7696
|
+
creditInr: lossAmt,
|
|
7697
|
+
reference: id
|
|
7698
|
+
});
|
|
7699
|
+
} else {
|
|
7700
|
+
treatment = 'No foreign exchange variance detected at reporting date.';
|
|
7701
|
+
}
|
|
7702
|
+
} else if (itemType === 'MONETARY_LIABILITY') {
|
|
7703
|
+
closingBookValueInr = roundToPaisa(foreignAmount * closingRate);
|
|
7704
|
+
unrealizedGainLossInr = roundToPaisa(originalBookValueInr - closingBookValueInr);
|
|
7705
|
+
|
|
7706
|
+
if (unrealizedGainLossInr > 0) {
|
|
7707
|
+
treatment = 'Unrealized foreign exchange gain (liability reduction) credited to P&L.';
|
|
7708
|
+
glImpact = 'CREDIT_PL_GAIN';
|
|
7709
|
+
totalUnrealizedGainInr = roundToPaisa(totalUnrealizedGainInr + unrealizedGainLossInr);
|
|
7710
|
+
journalEntries.push({
|
|
7711
|
+
account: `Foreign Trade Payables (${foreignCurrency})`,
|
|
7712
|
+
type: 'CURRENT_LIABILITY',
|
|
7713
|
+
debitInr: unrealizedGainLossInr,
|
|
7714
|
+
creditInr: 0,
|
|
7715
|
+
reference: id
|
|
7716
|
+
});
|
|
7717
|
+
journalEntries.push({
|
|
7718
|
+
account: 'Foreign Exchange Fluctuation Gain (P&L)',
|
|
7719
|
+
type: 'OTHER_INCOME',
|
|
7720
|
+
debitInr: 0,
|
|
7721
|
+
creditInr: unrealizedGainLossInr,
|
|
7722
|
+
reference: id
|
|
7723
|
+
});
|
|
7724
|
+
} else if (unrealizedGainLossInr < 0) {
|
|
7725
|
+
const lossAmt = Math.abs(unrealizedGainLossInr);
|
|
7726
|
+
treatment = 'Unrealized foreign exchange loss (liability expansion) debited to P&L.';
|
|
7727
|
+
glImpact = 'DEBIT_PL_LOSS';
|
|
7728
|
+
totalUnrealizedLossInr = roundToPaisa(totalUnrealizedLossInr + lossAmt);
|
|
7729
|
+
journalEntries.push({
|
|
7730
|
+
account: 'Foreign Exchange Fluctuation Loss (P&L)',
|
|
7731
|
+
type: 'FINANCE_EXPENSE',
|
|
7732
|
+
debitInr: lossAmt,
|
|
7733
|
+
creditInr: 0,
|
|
7734
|
+
reference: id
|
|
7735
|
+
});
|
|
7736
|
+
journalEntries.push({
|
|
7737
|
+
account: `Foreign Trade Payables (${foreignCurrency})`,
|
|
7738
|
+
type: 'CURRENT_LIABILITY',
|
|
7739
|
+
debitInr: 0,
|
|
7740
|
+
creditInr: lossAmt,
|
|
7741
|
+
reference: id
|
|
7742
|
+
});
|
|
7743
|
+
} else {
|
|
7744
|
+
treatment = 'No foreign exchange variance detected at reporting date.';
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
|
|
7748
|
+
totalClosingBookValueInr = roundToPaisa(totalClosingBookValueInr + closingBookValueInr);
|
|
7749
|
+
|
|
7750
|
+
return {
|
|
7751
|
+
id,
|
|
7752
|
+
description,
|
|
7753
|
+
itemType,
|
|
7754
|
+
foreignCurrency,
|
|
7755
|
+
foreignAmount,
|
|
7756
|
+
bookingRate,
|
|
7757
|
+
closingRate,
|
|
7758
|
+
originalBookValueInr,
|
|
7759
|
+
closingBookValueInr,
|
|
7760
|
+
unrealizedGainLossInr,
|
|
7761
|
+
treatment,
|
|
7762
|
+
glImpact
|
|
7763
|
+
};
|
|
7764
|
+
});
|
|
7765
|
+
|
|
7766
|
+
const netUnrealizedPnLInr = roundToPaisa(totalUnrealizedGainInr - totalUnrealizedLossInr);
|
|
7767
|
+
|
|
7768
|
+
let totalFctrReserveInr = 0;
|
|
7769
|
+
const translatedOperations = foreignOps.map((op, idx) => {
|
|
7770
|
+
const entityName = op.entityName || `Foreign Subsidiary ${idx + 1}`;
|
|
7771
|
+
const closingRate = cleanNumericAmount(op.closingRate, { defaultValue: 1 });
|
|
7772
|
+
const averageRate = cleanNumericAmount(op.averageRate, { defaultValue: closingRate });
|
|
7773
|
+
const assetsForeign = cleanNumericAmount(op.totalAssetsForeign, { defaultValue: 0 });
|
|
7774
|
+
const liabilitiesForeign = cleanNumericAmount(op.totalLiabilitiesForeign, { defaultValue: 0 });
|
|
7775
|
+
const netIncomeForeign = cleanNumericAmount(op.netIncomeForeign, { defaultValue: 0 });
|
|
7776
|
+
const shareCapitalInr = cleanNumericAmount(op.shareCapitalInr, { defaultValue: 0 });
|
|
7777
|
+
const openingRetainedEarningsInr = cleanNumericAmount(op.openingRetainedEarningsInr, { defaultValue: 0 });
|
|
7778
|
+
|
|
7779
|
+
const assetsInr = roundToPaisa(assetsForeign * closingRate);
|
|
7780
|
+
const liabilitiesInr = roundToPaisa(liabilitiesForeign * closingRate);
|
|
7781
|
+
const netAssetsClosingInr = roundToPaisa(assetsInr - liabilitiesInr);
|
|
7782
|
+
const netIncomeAverageInr = roundToPaisa(netIncomeForeign * averageRate);
|
|
7783
|
+
const totalEquityPreFctrInr = roundToPaisa(shareCapitalInr + openingRetainedEarningsInr + netIncomeAverageInr);
|
|
7784
|
+
|
|
7785
|
+
const fctrInr = roundToPaisa(netAssetsClosingInr - totalEquityPreFctrInr);
|
|
7786
|
+
totalFctrReserveInr = roundToPaisa(totalFctrReserveInr + fctrInr);
|
|
7787
|
+
|
|
7788
|
+
return {
|
|
7789
|
+
entityName,
|
|
7790
|
+
closingRate,
|
|
7791
|
+
averageRate,
|
|
7792
|
+
totalAssetsInr: assetsInr,
|
|
7793
|
+
totalLiabilitiesInr: liabilitiesInr,
|
|
7794
|
+
netAssetsClosingInr,
|
|
7795
|
+
netIncomeInr: netIncomeAverageInr,
|
|
7796
|
+
fctrBalanceInr: fctrInr,
|
|
7797
|
+
treatment: 'Translation difference routed to Foreign Currency Translation Reserve (FCTR) in Other Comprehensive Income (OCI) under Ind AS 21.39.'
|
|
7798
|
+
};
|
|
7799
|
+
});
|
|
7800
|
+
|
|
7801
|
+
const totalDebits = roundToPaisa(journalEntries.reduce((acc, e) => acc + e.debitInr, 0));
|
|
7802
|
+
const totalCredits = roundToPaisa(journalEntries.reduce((acc, e) => acc + e.creditInr, 0));
|
|
7803
|
+
const isDoubleEntryBalanced = Math.abs(totalDebits - totalCredits) < 0.001;
|
|
7804
|
+
|
|
7805
|
+
return {
|
|
7806
|
+
status: 'SUCCESS',
|
|
7807
|
+
standard: 'Ind AS 21 / IAS 21 / US GAAP ASC 830',
|
|
7808
|
+
asOfDate,
|
|
7809
|
+
functionalCurrency,
|
|
7810
|
+
totalItemsEvaluated: revaluedItems.length,
|
|
7811
|
+
foreignOperationsCount: translatedOperations.length,
|
|
7812
|
+
summary: {
|
|
7813
|
+
totalOriginalBookValueInr,
|
|
7814
|
+
totalClosingBookValueInr,
|
|
7815
|
+
totalUnrealizedGainInr,
|
|
7816
|
+
totalUnrealizedLossInr,
|
|
7817
|
+
netUnrealizedPnLInr,
|
|
7818
|
+
totalFctrReserveInr,
|
|
7819
|
+
isDoubleEntryBalanced,
|
|
7820
|
+
totalDebitsInr: totalDebits,
|
|
7821
|
+
totalCreditsInr: totalCredits
|
|
7822
|
+
},
|
|
7823
|
+
revaluedItems,
|
|
7824
|
+
translatedForeignOperations: translatedOperations,
|
|
7825
|
+
journalEntries,
|
|
7826
|
+
isAuthorizedToDisburse: false,
|
|
7827
|
+
statutoryRationale: `Ind AS 21 evaluation completed: ${revaluedItems.length} monetary/non-monetary exposures revalued. Net unrealized P&L variance: INR ${netUnrealizedPnLInr.toLocaleString('en-IN')}; Total FCTR in OCI: INR ${totalFctrReserveInr.toLocaleString('en-IN')}. General Ledger vouchers balanced at INR ${totalDebits.toLocaleString('en-IN')}. Foreign currency revaluation does not authorize payment disbursement.`
|
|
7828
|
+
};
|
|
7829
|
+
}
|
|
7830
|
+
|
|
7831
|
+
/**
|
|
7832
|
+
* Ind AS 36 / IAS 36 / US GAAP ASC 360 & 350: Impairment of Assets & Cash-Generating Units (CGU).
|
|
7833
|
+
*/
|
|
7834
|
+
export function calculateImpairmentIndAs36(params = {}) {
|
|
7835
|
+
const asOfDate = params.asOfDate || new Date().toISOString().split('T')[0];
|
|
7836
|
+
const cguId = params.cguId || 'CGU-01';
|
|
7837
|
+
const cguName = params.cguName || 'Corporate Cash-Generating Unit';
|
|
7838
|
+
|
|
7839
|
+
const rawAmounts = params.carryingAmounts || {};
|
|
7840
|
+
const goodwill = roundToPaisa(cleanNumericAmount(rawAmounts.goodwill, { defaultValue: 0 }));
|
|
7841
|
+
const ppe = roundToPaisa(cleanNumericAmount(rawAmounts.propertyPlantEquipment, { defaultValue: 0 }));
|
|
7842
|
+
const intangibles = roundToPaisa(cleanNumericAmount(rawAmounts.intangibleAssets, { defaultValue: 0 }));
|
|
7843
|
+
const otherNonCurrent = roundToPaisa(cleanNumericAmount(rawAmounts.otherNonCurrentAssets, { defaultValue: 0 }));
|
|
7844
|
+
|
|
7845
|
+
const totalCarryingAmount = roundToPaisa(goodwill + ppe + intangibles + otherNonCurrent);
|
|
7846
|
+
|
|
7847
|
+
if (totalCarryingAmount <= 0) {
|
|
7848
|
+
return {
|
|
7849
|
+
status: 'INSUFFICIENT_DATA',
|
|
7850
|
+
standard: 'Ind AS 36 / IAS 36 / US GAAP ASC 360',
|
|
7851
|
+
cguId,
|
|
7852
|
+
totalCarryingAmountInr: 0,
|
|
7853
|
+
isAuthorizedToDisburse: false,
|
|
7854
|
+
statutoryRationale: 'Carrying amounts must be greater than zero for impairment evaluation.'
|
|
7855
|
+
};
|
|
7856
|
+
}
|
|
7857
|
+
|
|
7858
|
+
const rad = params.recoverableAmountDetermination || {};
|
|
7859
|
+
const fvlcod = roundToPaisa(cleanNumericAmount(rad.fairValueLessCostsOfDisposal, { defaultValue: 0 }));
|
|
7860
|
+
|
|
7861
|
+
let valueInUse = 0;
|
|
7862
|
+
let discountRate = 0;
|
|
7863
|
+
const discountedCashFlows = [];
|
|
7864
|
+
|
|
7865
|
+
if (rad.valueInUse && typeof rad.valueInUse === 'object') {
|
|
7866
|
+
discountRate = cleanNumericAmount(rad.valueInUse.preTaxDiscountRatePercent, { defaultValue: 10 }) / 100;
|
|
7867
|
+
const cashFlows = Array.isArray(rad.valueInUse.projectedCashFlows) ? rad.valueInUse.projectedCashFlows : [];
|
|
7868
|
+
|
|
7869
|
+
let pvSum = 0;
|
|
7870
|
+
cashFlows.forEach((cfRaw, idx) => {
|
|
7871
|
+
const year = idx + 1;
|
|
7872
|
+
const cf = cleanNumericAmount(cfRaw, { defaultValue: 0 });
|
|
7873
|
+
const discountFactor = 1 / Math.pow(1 + discountRate, year);
|
|
7874
|
+
const presentValue = roundToPaisa(cf * discountFactor);
|
|
7875
|
+
pvSum = roundToPaisa(pvSum + presentValue);
|
|
7876
|
+
discountedCashFlows.push({ year, cashFlow: cf, discountFactor: Number(discountFactor.toFixed(4)), presentValue });
|
|
7877
|
+
});
|
|
7878
|
+
|
|
7879
|
+
let terminalPv = 0;
|
|
7880
|
+
if (rad.valueInUse.terminalValue) {
|
|
7881
|
+
const tv = cleanNumericAmount(rad.valueInUse.terminalValue, { defaultValue: 0 });
|
|
7882
|
+
const finalYear = cashFlows.length || 1;
|
|
7883
|
+
const tvDiscountFactor = 1 / Math.pow(1 + discountRate, finalYear);
|
|
7884
|
+
terminalPv = roundToPaisa(tv * tvDiscountFactor);
|
|
7885
|
+
} else if (rad.valueInUse.terminalGrowthRatePercent && cashFlows.length > 0) {
|
|
7886
|
+
const g = cleanNumericAmount(rad.valueInUse.terminalGrowthRatePercent, { defaultValue: 2 }) / 100;
|
|
7887
|
+
const finalCf = cleanNumericAmount(cashFlows[cashFlows.length - 1], { defaultValue: 0 });
|
|
7888
|
+
if (discountRate > g) {
|
|
7889
|
+
const tv = (finalCf * (1 + g)) / (discountRate - g);
|
|
7890
|
+
const finalYear = cashFlows.length;
|
|
7891
|
+
const tvDiscountFactor = 1 / Math.pow(1 + discountRate, finalYear);
|
|
7892
|
+
terminalPv = roundToPaisa(tv * tvDiscountFactor);
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7895
|
+
|
|
7896
|
+
valueInUse = roundToPaisa(pvSum + terminalPv);
|
|
7897
|
+
}
|
|
7898
|
+
|
|
7899
|
+
const recoverableAmount = Math.max(fvlcod, valueInUse);
|
|
7900
|
+
const recoverableMethod = fvlcod >= valueInUse ? 'FAIR_VALUE_LESS_COSTS_OF_DISPOSAL' : 'VALUE_IN_USE';
|
|
7901
|
+
|
|
7902
|
+
let impairmentRequired = false;
|
|
7903
|
+
let totalImpairmentLoss = 0;
|
|
7904
|
+
let postImpairmentCarryingAmount = totalCarryingAmount;
|
|
7905
|
+
let goodwillImpairment = 0;
|
|
7906
|
+
let ppeImpairment = 0;
|
|
7907
|
+
let intangiblesImpairment = 0;
|
|
7908
|
+
let otherImpairment = 0;
|
|
7909
|
+
const journalEntries = [];
|
|
7910
|
+
|
|
7911
|
+
if (recoverableAmount < totalCarryingAmount) {
|
|
7912
|
+
impairmentRequired = true;
|
|
7913
|
+
totalImpairmentLoss = roundToPaisa(totalCarryingAmount - recoverableAmount);
|
|
7914
|
+
postImpairmentCarryingAmount = recoverableAmount;
|
|
7915
|
+
|
|
7916
|
+
let remainingLoss = totalImpairmentLoss;
|
|
7917
|
+
if (goodwill > 0) {
|
|
7918
|
+
goodwillImpairment = Math.min(goodwill, remainingLoss);
|
|
7919
|
+
remainingLoss = roundToPaisa(remainingLoss - goodwillImpairment);
|
|
7920
|
+
journalEntries.push({
|
|
7921
|
+
account: 'Accumulated Impairment - Goodwill',
|
|
7922
|
+
type: 'INTANGIBLE_ASSET_CONTRA',
|
|
7923
|
+
debitInr: 0,
|
|
7924
|
+
creditInr: goodwillImpairment,
|
|
7925
|
+
rule: 'Ind AS 36.104(a) Goodwill Priority Write-Down'
|
|
7926
|
+
});
|
|
7927
|
+
}
|
|
7928
|
+
|
|
7929
|
+
const floors = params.assetFloors || {};
|
|
7930
|
+
const ppeFloor = cleanNumericAmount(floors.propertyPlantEquipment, { defaultValue: 0 });
|
|
7931
|
+
const intFloor = cleanNumericAmount(floors.intangibleAssets, { defaultValue: 0 });
|
|
7932
|
+
const otherFloor = cleanNumericAmount(floors.otherNonCurrentAssets, { defaultValue: 0 });
|
|
7933
|
+
|
|
7934
|
+
const ppeMaxWriteDown = Math.max(0, ppe - ppeFloor);
|
|
7935
|
+
const intMaxWriteDown = Math.max(0, intangibles - intFloor);
|
|
7936
|
+
const otherMaxWriteDown = Math.max(0, otherNonCurrent - otherFloor);
|
|
7937
|
+
const totalEligibleBase = ppeMaxWriteDown + intMaxWriteDown + otherMaxWriteDown;
|
|
7938
|
+
|
|
7939
|
+
if (remainingLoss > 0 && totalEligibleBase > 0) {
|
|
7940
|
+
const ppeRatio = ppeMaxWriteDown / totalEligibleBase;
|
|
7941
|
+
const intRatio = intMaxWriteDown / totalEligibleBase;
|
|
7942
|
+
|
|
7943
|
+
ppeImpairment = roundToPaisa(Math.min(ppeMaxWriteDown, remainingLoss * ppeRatio));
|
|
7944
|
+
intangiblesImpairment = roundToPaisa(Math.min(intMaxWriteDown, remainingLoss * intRatio));
|
|
7945
|
+
otherImpairment = roundToPaisa(remainingLoss - ppeImpairment - intangiblesImpairment);
|
|
7946
|
+
|
|
7947
|
+
if (ppeImpairment > 0) {
|
|
7948
|
+
journalEntries.push({
|
|
7949
|
+
account: 'Accumulated Impairment - Property, Plant & Equipment',
|
|
7950
|
+
type: 'PPE_CONTRA',
|
|
7951
|
+
debitInr: 0,
|
|
7952
|
+
creditInr: ppeImpairment,
|
|
7953
|
+
rule: 'Ind AS 36.104(b) Pro-rata PPE Allocation'
|
|
7954
|
+
});
|
|
7955
|
+
}
|
|
7956
|
+
if (intangiblesImpairment > 0) {
|
|
7957
|
+
journalEntries.push({
|
|
7958
|
+
account: 'Accumulated Impairment - Intangible Assets',
|
|
7959
|
+
type: 'INTANGIBLE_CONTRA',
|
|
7960
|
+
debitInr: 0,
|
|
7961
|
+
creditInr: intangiblesImpairment,
|
|
7962
|
+
rule: 'Ind AS 36.104(b) Pro-rata Intangibles Allocation'
|
|
7963
|
+
});
|
|
7964
|
+
}
|
|
7965
|
+
if (otherImpairment > 0) {
|
|
7966
|
+
journalEntries.push({
|
|
7967
|
+
account: 'Accumulated Impairment - Other Non-Current Assets',
|
|
7968
|
+
type: 'NON_CURRENT_ASSET_CONTRA',
|
|
7969
|
+
debitInr: 0,
|
|
7970
|
+
creditInr: otherImpairment,
|
|
7971
|
+
rule: 'Ind AS 36.104(b) Pro-rata Other Assets Allocation'
|
|
7972
|
+
});
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
|
|
7976
|
+
journalEntries.unshift({
|
|
7977
|
+
account: 'Impairment Loss on Assets (Statement of Profit & Loss)',
|
|
7978
|
+
type: 'OPERATING_EXPENSE',
|
|
7979
|
+
debitInr: totalImpairmentLoss,
|
|
7980
|
+
creditInr: 0,
|
|
7981
|
+
rule: 'Ind AS 36.60 Recognition in Profit or Loss'
|
|
7982
|
+
});
|
|
7983
|
+
}
|
|
7984
|
+
|
|
7985
|
+
const totalDebits = roundToPaisa(journalEntries.reduce((acc, e) => acc + e.debitInr, 0));
|
|
7986
|
+
const totalCredits = roundToPaisa(journalEntries.reduce((acc, e) => acc + e.creditInr, 0));
|
|
7987
|
+
const isDoubleEntryBalanced = Math.abs(totalDebits - totalCredits) < 0.001;
|
|
7988
|
+
|
|
7989
|
+
return {
|
|
7990
|
+
status: 'SUCCESS',
|
|
7991
|
+
standard: 'Ind AS 36 / IAS 36 / US GAAP ASC 360',
|
|
7992
|
+
asOfDate,
|
|
7993
|
+
cguId,
|
|
7994
|
+
cguName,
|
|
7995
|
+
valuation: {
|
|
7996
|
+
totalCarryingAmountInr: totalCarryingAmount,
|
|
7997
|
+
fairValueLessCostsOfDisposalInr: fvlcod,
|
|
7998
|
+
valueInUseInr: valueInUse,
|
|
7999
|
+
recoverableAmountInr: recoverableAmount,
|
|
8000
|
+
recoverableMethod,
|
|
8001
|
+
headroomInr: Math.max(0, recoverableAmount - totalCarryingAmount),
|
|
8002
|
+
impairmentRequired,
|
|
8003
|
+
totalImpairmentLossInr: totalImpairmentLoss,
|
|
8004
|
+
postImpairmentCarryingAmountInr: postImpairmentCarryingAmount
|
|
8005
|
+
},
|
|
8006
|
+
assetBreakdown: {
|
|
8007
|
+
goodwill: { initialCarryingInr: goodwill, impairmentInr: goodwillImpairment, closingCarryingInr: roundToPaisa(goodwill - goodwillImpairment) },
|
|
8008
|
+
propertyPlantEquipment: { initialCarryingInr: ppe, impairmentInr: ppeImpairment, closingCarryingInr: roundToPaisa(ppe - ppeImpairment) },
|
|
8009
|
+
intangibleAssets: { initialCarryingInr: intangibles, impairmentInr: intangiblesImpairment, closingCarryingInr: roundToPaisa(intangibles - intangiblesImpairment) },
|
|
8010
|
+
otherNonCurrentAssets: { initialCarryingInr: otherNonCurrent, impairmentInr: otherImpairment, closingCarryingInr: roundToPaisa(otherNonCurrent - otherImpairment) }
|
|
8011
|
+
},
|
|
8012
|
+
discountedCashFlowsSchedule: discountedCashFlows,
|
|
8013
|
+
journalEntries,
|
|
8014
|
+
isDoubleEntryBalanced,
|
|
8015
|
+
isAuthorizedToDisburse: false,
|
|
8016
|
+
statutoryRationale: impairmentRequired
|
|
8017
|
+
? `Ind AS 36 Impairment Test: CGU "${cguName}" carrying value INR ${totalCarryingAmount.toLocaleString('en-IN')} exceeds recoverable amount INR ${recoverableAmount.toLocaleString('en-IN')} (${recoverableMethod}). Impairment loss of INR ${totalImpairmentLoss.toLocaleString('en-IN')} recognized in P&L. Goodwill reduced by INR ${goodwillImpairment.toLocaleString('en-IN')}; non-current assets reduced pro-rata by INR ${(totalImpairmentLoss - goodwillImpairment).toLocaleString('en-IN')}. Impairment assessment does not authorize payment disbursement.`
|
|
8018
|
+
: `Ind AS 36 Impairment Test: CGU "${cguName}" carrying value INR ${totalCarryingAmount.toLocaleString('en-IN')} is fully supported by recoverable amount INR ${recoverableAmount.toLocaleString('en-IN')} (${recoverableMethod}) with headroom of INR ${(recoverableAmount - totalCarryingAmount).toLocaleString('en-IN')}. No impairment recognized. Impairment assessment does not authorize payment disbursement.`
|
|
8019
|
+
};
|
|
8020
|
+
}
|
|
8021
|
+
|
|
8022
|
+
|
|
6088
8023
|
// ===========================================================================
|
|
6089
8024
|
// COMMERCIAL TIER BOUNDARY SPECIFICATION (OKF v0.2 / ENTERPRISE BOUNDARY)
|
|
6090
8025
|
// ===========================================================================
|