shareneus 1.7.336 → 1.7.338

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.
@@ -3,9 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DiscountAndTaxCalculation = DiscountAndTaxCalculation;
4
4
  exports.calculateRecordDiscountForLine = calculateRecordDiscountForLine;
5
5
  const math_operations_1 = require("../shared/math-operations");
6
- const util_1 = require("../shared/util");
7
- const tax_calculator_1 = require("../tax/tax-calculator");
8
6
  const tr_utils_1 = require("../utils/tr-utils");
7
+ const transaction_calculation_engine_1 = require("./transaction-calculation-engine");
9
8
  function DiscountAndTaxCalculation(recordData, isTaxable, taxcodes = []) {
10
9
  // ---------- STEP 0: FETCH TAX CODES ----------
11
10
  // const taxcodes:any = isTaxable
@@ -79,7 +78,7 @@ function DiscountAndTaxCalculation(recordData, isTaxable, taxcodes = []) {
79
78
  labor.RecDisc = totalTransactionDiscount;
80
79
  const laborafterDiscPrice = (0, math_operations_1.Subtract)(labor.Amt, labor.Disc);
81
80
  const afterDisc = (0, math_operations_1.Subtract)(laborafterDiscPrice, totalTransactionDiscount);
82
- labor.Taxes = recalculateTaxes(labor, afterDisc, taxcodes);
81
+ labor.Taxes = recalculateTaxes(afterDisc, labor.TCode, taxcodes, labor.Taxes, labor.Qty || 1);
83
82
  return labor;
84
83
  });
85
84
  // ---------- STEP 8: PROCESS INVENTORY ITEMS ----------
@@ -100,7 +99,7 @@ function DiscountAndTaxCalculation(recordData, isTaxable, taxcodes = []) {
100
99
  item.RecDisc = totalTransactionDiscount;
101
100
  const ItemafterDiscPrice = (0, math_operations_1.Subtract)(item.UnAmt, item.Disc);
102
101
  const afterDisc = (0, math_operations_1.Subtract)(ItemafterDiscPrice, totalTransactionDiscount);
103
- item.Taxes = recalculateTaxes(item, afterDisc, taxcodes);
102
+ item.Taxes = recalculateTaxes(afterDisc, item.TCode, taxcodes, item.Taxes, item.Qty);
104
103
  return item;
105
104
  });
106
105
  // ---------- STEP 9: FINALIZE ----------
@@ -108,81 +107,80 @@ function DiscountAndTaxCalculation(recordData, isTaxable, taxcodes = []) {
108
107
  recordData.Ops = [...finalServices, ...nonDiscountableOps];
109
108
  return recordData;
110
109
  }
111
- function recalculateTaxes(line, amount, taxCodes = []) {
112
- const existingTaxes = Array.isArray(line === null || line === void 0 ? void 0 : line.Taxes) ? line.Taxes : [];
113
- const taxCode = getTaxCodeForLine(line, taxCodes, existingTaxes);
114
- if (!taxCode) {
115
- return recalculateExistingTaxes(amount, line === null || line === void 0 ? void 0 : line.TCode, existingTaxes);
116
- }
117
- const qty = (0, util_1.GetNumber)(line === null || line === void 0 ? void 0 : line.Qty, 1) || 1;
118
- const calculatedTaxes = (0, tax_calculator_1.CalculateLineTax)({
119
- Qty: qty,
120
- UnitPrice: (0, math_operations_1.Divide)(amount, qty),
121
- Disc: 0,
122
- RecDisc: 0,
123
- TaxCode: normalizeTaxCodeForCalculation(taxCode, existingTaxes),
124
- ComponentOverrides: line === null || line === void 0 ? void 0 : line.ComponentOverrides,
110
+ function recalculateTaxes(amount, taxCode, taxCodes = [], existingTaxes = [], qty = 1) {
111
+ const normalizedTaxCode = tr_utils_1.TrUtils.IsNull(taxCode) ? null : Number(taxCode);
112
+ const defaultRates = getDefaultTaxRates(taxCode, taxCodes);
113
+ const normalizedQty = toFiniteNumber(qty, 1);
114
+ return (Array.isArray(existingTaxes) ? existingTaxes : []).map((tax) => {
115
+ var _a, _b;
116
+ const code = tax === null || tax === void 0 ? void 0 : tax.Code;
117
+ const rate = Number(tax === null || tax === void 0 ? void 0 : tax.Rate);
118
+ const normalizedRate = Number.isFinite(rate) ? rate : (defaultRates[code] || 0);
119
+ const calcMethod = normalizeCalcMethod(tax === null || tax === void 0 ? void 0 : tax.CalcMethod);
120
+ return Object.assign(Object.assign({}, tax), { Rate: normalizedRate, Amt: calculateComponentTaxAmount(amount, normalizedRate, tax, normalizedQty), TaxCodeId: !tr_utils_1.TrUtils.IsNull(tax === null || tax === void 0 ? void 0 : tax.TaxCodeId)
121
+ ? Number(tax.TaxCodeId)
122
+ : normalizedTaxCode, PerUnitAmt: (tax === null || tax === void 0 ? void 0 : tax.PerUnitAmt) || undefined, CalcMethod: calcMethod, Unit: (_b = (_a = tax === null || tax === void 0 ? void 0 : tax.Unit) !== null && _a !== void 0 ? _a : tax === null || tax === void 0 ? void 0 : tax.Units) !== null && _b !== void 0 ? _b : undefined });
125
123
  });
126
- const existingByCode = new Map(existingTaxes
127
- .filter((tax) => tax === null || tax === void 0 ? void 0 : tax.Code)
128
- .map((tax) => [tax.Code, tax]));
129
- return calculatedTaxes.map((tax) => (Object.assign(Object.assign({}, (existingByCode.get(tax.Code) || {})), tax)));
130
124
  }
131
- function getTaxCodeForLine(line, taxCodes = [], existingTaxes = []) {
132
- var _a, _b;
133
- if ((_a = line === null || line === void 0 ? void 0 : line.TCode) === null || _a === void 0 ? void 0 : _a.Components) {
134
- return line.TCode;
135
- }
136
- const taxCodeId = !tr_utils_1.TrUtils.IsNull(line === null || line === void 0 ? void 0 : line.TCode)
137
- ? line.TCode
138
- : (_b = existingTaxes.find((tax) => !tr_utils_1.TrUtils.IsNull(tax === null || tax === void 0 ? void 0 : tax.TaxCodeId))) === null || _b === void 0 ? void 0 : _b.TaxCodeId;
139
- if (tr_utils_1.TrUtils.IsNull(taxCodeId) || !Array.isArray(taxCodes)) {
140
- return null;
125
+ function calculateComponentTaxAmount(amount, rate, tax, qty) {
126
+ const baseAmt = toFiniteNumber(amount);
127
+ const calcMethod = normalizeCalcMethod(tax === null || tax === void 0 ? void 0 : tax.CalcMethod);
128
+ const perUnitAmt = toFiniteNumber(tax === null || tax === void 0 ? void 0 : tax.PerUnitAmt);
129
+ const perUnitResult = (0, math_operations_1.Multiply)(perUnitAmt, qty);
130
+ const percentResult = (0, math_operations_1.Multiply)(baseAmt, (0, math_operations_1.Divide)(rate, 100));
131
+ switch (calcMethod) {
132
+ case 'PerUnit':
133
+ return perUnitResult;
134
+ case 'PerUnitPlusPercent':
135
+ return (0, math_operations_1.Add)(perUnitResult, percentResult);
136
+ case 'MaxOfPercentOrPerUnit':
137
+ return Math.max(perUnitResult, percentResult);
138
+ case 'Percent':
139
+ default:
140
+ return percentResult;
141
141
  }
142
- return taxCodes.find((code) => String(code === null || code === void 0 ? void 0 : code._id) === String(taxCodeId)) || null;
143
142
  }
144
- function normalizeTaxCodeForCalculation(taxCode, existingTaxes = []) {
145
- if (Array.isArray(taxCode === null || taxCode === void 0 ? void 0 : taxCode.Components) && taxCode.Components.length > 0) {
146
- return taxCode;
147
- }
148
- return Object.assign(Object.assign({}, taxCode), { Components: buildLegacyComponents(taxCode, existingTaxes) });
143
+ function normalizeCalcMethod(calcMethod) {
144
+ return calcMethod === 'Percentage' || tr_utils_1.TrUtils.IsNull(calcMethod)
145
+ ? 'Percent'
146
+ : calcMethod;
149
147
  }
150
- function buildLegacyComponents(taxCode, existingTaxes = []) {
151
- const components = ["CGST", "SGST", "IGST"]
152
- .map((code) => ({
153
- Code: code,
154
- Name: code,
155
- Rate: (0, util_1.GetNumber)(taxCode === null || taxCode === void 0 ? void 0 : taxCode[code]),
156
- Type: "Tax",
157
- CalcMethod: "Percent",
158
- AppliedOn: "NetAmt",
159
- }))
160
- .filter((component) => component.Rate > 0);
161
- existingTaxes.forEach((tax) => {
162
- if (!(tax === null || tax === void 0 ? void 0 : tax.Code) || components.some((component) => component.Code === tax.Code)) {
163
- return;
164
- }
165
- components.push({
166
- Code: tax.Code,
167
- Name: tax.Name || tax.Code,
168
- Rate: (0, util_1.GetNumber)(tax.Rate),
169
- Type: tax.Type || (String(tax.Code).toUpperCase().includes("CESS") ? "Cess" : "Tax"),
170
- CalcMethod: tax.CalcMethod || "Percent",
171
- PerUnitAmt: tax.PerUnitAmt,
172
- Unit: tax.Unit,
173
- AppliedOn: tax.AppliedOn || "NetAmt",
174
- });
175
- });
176
- return components;
148
+ function toFiniteNumber(value, fallback = 0) {
149
+ const normalized = Number(value);
150
+ return Number.isFinite(normalized) ? normalized : fallback;
177
151
  }
178
- function recalculateExistingTaxes(amount, taxCode, existingTaxes = []) {
179
- const normalizedTaxCode = tr_utils_1.TrUtils.IsNull(taxCode) ? null : Number(taxCode);
180
- return existingTaxes.map((tax) => {
181
- const rate = (0, util_1.GetNumber)(tax === null || tax === void 0 ? void 0 : tax.Rate);
182
- return Object.assign(Object.assign({}, tax), { Rate: rate, Amt: (0, math_operations_1.Multiply)(amount, (0, math_operations_1.Divide)(rate, 100)), TaxCodeId: !tr_utils_1.TrUtils.IsNull(tax === null || tax === void 0 ? void 0 : tax.TaxCodeId)
183
- ? Number(tax.TaxCodeId)
184
- : normalizedTaxCode });
185
- });
152
+ function getDefaultTaxRates(taxCode, taxCodes = []) {
153
+ const normalizedTaxCode = tr_utils_1.TrUtils.IsNull(taxCode) ? null : taxCode;
154
+ const taxCodeRow = Array.isArray(taxCodes)
155
+ ? taxCodes.find((code) => (code === null || code === void 0 ? void 0 : code._id) === normalizedTaxCode)
156
+ : null;
157
+ const components = Array.isArray(taxCodeRow === null || taxCodeRow === void 0 ? void 0 : taxCodeRow.Components)
158
+ ? taxCodeRow.Components
159
+ : [];
160
+ const getComponentRate = (code) => {
161
+ const match = components.find((component) => {
162
+ var _a, _b;
163
+ const compCode = (_b = (_a = component === null || component === void 0 ? void 0 : component.Code) !== null && _a !== void 0 ? _a : component === null || component === void 0 ? void 0 : component.Type) !== null && _b !== void 0 ? _b : component === null || component === void 0 ? void 0 : component.Name;
164
+ return compCode === code;
165
+ });
166
+ const rate = Number(match === null || match === void 0 ? void 0 : match.Rate);
167
+ return Number.isFinite(rate) ? rate : 0;
168
+ };
169
+ let CGST = getComponentRate('CGST');
170
+ let SGST = getComponentRate('SGST');
171
+ let IGST = getComponentRate('IGST');
172
+ const CESS = getComponentRate('CESS');
173
+ if (CGST === 0 && SGST === 0 && IGST === 0) {
174
+ const gstRateSum = transaction_calculation_engine_1.TransactionCalculationEngine.getGSTRateSumByTaxCode(normalizedTaxCode, taxCodes);
175
+ if (components.length === 1) {
176
+ IGST = gstRateSum;
177
+ }
178
+ else if (components.length === 2) {
179
+ CGST = gstRateSum / 2;
180
+ SGST = gstRateSum / 2;
181
+ }
182
+ }
183
+ return { CGST, SGST, IGST, CESS };
186
184
  }
187
185
  /**
188
186
  * Calculates the record-level discount amount (RecDisc) for a single line.
@@ -31,6 +31,7 @@ export declare class TransactionCalculationEngine {
31
31
  total: number;
32
32
  taxRows: unknown;
33
33
  includeTaxInTotal: boolean;
34
+ qty?: number;
34
35
  }): number;
35
36
  static reverseCalculateAmountAndUnitPriceFromTotal(input: {
36
37
  qty: number;
@@ -48,6 +48,59 @@ function sumTaxRateFromTaxes(taxes) {
48
48
  const rows = getTaxesArray(taxes);
49
49
  return rows.reduce((sum, row) => { var _a; return (0, math_operations_1.Add)(sum, toNum((_a = row === null || row === void 0 ? void 0 : row.Rate) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.rate)); }, 0);
50
50
  }
51
+ function normalizeCalcMethod(calcMethod) {
52
+ return calcMethod === 'Percentage' || isNullOrEmpty(calcMethod)
53
+ ? 'Percent'
54
+ : String(calcMethod);
55
+ }
56
+ function getTaxRowQty(row, fallbackQty) {
57
+ var _a, _b, _c;
58
+ return toNum((_c = (_b = (_a = row === null || row === void 0 ? void 0 : row.Qty) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.qty) !== null && _b !== void 0 ? _b : row === null || row === void 0 ? void 0 : row.Quantity) !== null && _c !== void 0 ? _c : row === null || row === void 0 ? void 0 : row.quantity) || fallbackQty;
59
+ }
60
+ function getPerUnitTaxAmount(row, fallbackQty, fallbackToAmt = true) {
61
+ var _a, _b;
62
+ const perUnitAmt = toNum((_a = row === null || row === void 0 ? void 0 : row.PerUnitAmt) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.perUnitAmt);
63
+ const qty = getTaxRowQty(row, fallbackQty);
64
+ if (perUnitAmt !== 0 && qty !== 0) {
65
+ return (0, math_operations_1.Multiply)(perUnitAmt, qty);
66
+ }
67
+ return fallbackToAmt ? toNum((_b = row === null || row === void 0 ? void 0 : row.Amt) !== null && _b !== void 0 ? _b : row === null || row === void 0 ? void 0 : row.amt) : 0;
68
+ }
69
+ function getReverseTaxBreakdown(taxes, qty) {
70
+ return getTaxesArray(taxes).reduce((summary, row) => {
71
+ var _a, _b, _c;
72
+ const calcMethod = normalizeCalcMethod((_a = row === null || row === void 0 ? void 0 : row.CalcMethod) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.calcMethod);
73
+ const rate = toNum((_b = row === null || row === void 0 ? void 0 : row.Rate) !== null && _b !== void 0 ? _b : row === null || row === void 0 ? void 0 : row.rate);
74
+ switch (calcMethod) {
75
+ case 'PerUnit':
76
+ summary.fixedTaxAmount = (0, math_operations_1.Add)(summary.fixedTaxAmount, getPerUnitTaxAmount(row, qty));
77
+ break;
78
+ case 'PerUnitPlusPercent':
79
+ summary.fixedTaxAmount = (0, math_operations_1.Add)(summary.fixedTaxAmount, getPerUnitTaxAmount(row, qty, false));
80
+ summary.percentTaxRate = (0, math_operations_1.Add)(summary.percentTaxRate, rate);
81
+ break;
82
+ case 'MaxOfPercentOrPerUnit': {
83
+ const amt = toNum((_c = row === null || row === void 0 ? void 0 : row.Amt) !== null && _c !== void 0 ? _c : row === null || row === void 0 ? void 0 : row.amt);
84
+ const perUnitAmt = getPerUnitTaxAmount(row, qty);
85
+ if (amt !== 0) {
86
+ summary.fixedTaxAmount = (0, math_operations_1.Add)(summary.fixedTaxAmount, amt);
87
+ }
88
+ else if (perUnitAmt !== 0) {
89
+ summary.fixedTaxAmount = (0, math_operations_1.Add)(summary.fixedTaxAmount, perUnitAmt);
90
+ }
91
+ else {
92
+ summary.percentTaxRate = (0, math_operations_1.Add)(summary.percentTaxRate, rate);
93
+ }
94
+ break;
95
+ }
96
+ case 'Percent':
97
+ default:
98
+ summary.percentTaxRate = (0, math_operations_1.Add)(summary.percentTaxRate, rate);
99
+ break;
100
+ }
101
+ return summary;
102
+ }, { fixedTaxAmount: 0, percentTaxRate: 0 });
103
+ }
51
104
  /**
52
105
  * Distribute a flat discount amount across a set of rows proportionally
53
106
  * by each row's `baseKey` value. Returns an array of share amounts aligned
@@ -115,12 +168,13 @@ class TransactionCalculationEngine {
115
168
  if (!input.includeTaxInTotal) {
116
169
  return total;
117
170
  }
118
- const taxSummary = TransactionCalculationEngine.getTaxesSummary(input.taxRows);
119
- if (taxSummary.taxRate) {
120
- const divisor = (0, math_operations_1.Add)(1, (0, math_operations_1.Divide)(taxSummary.taxRate, 100));
121
- return divisor === 0 ? total : (0, math_operations_1.Divide)(total, divisor);
171
+ const taxBreakdown = getReverseTaxBreakdown(input.taxRows, toNum(input.qty) || 1);
172
+ const totalAfterFixedTax = (0, math_operations_1.Subtract)(total, taxBreakdown.fixedTaxAmount);
173
+ if (taxBreakdown.percentTaxRate) {
174
+ const divisor = (0, math_operations_1.Add)(1, (0, math_operations_1.Divide)(taxBreakdown.percentTaxRate, 100));
175
+ return divisor === 0 ? totalAfterFixedTax : (0, math_operations_1.Divide)(totalAfterFixedTax, divisor);
122
176
  }
123
- return (0, math_operations_1.Subtract)(total, taxSummary.taxAmount);
177
+ return totalAfterFixedTax;
124
178
  }
125
179
  static reverseCalculateAmountAndUnitPriceFromTotal(input) {
126
180
  const qty = toNum(input.qty) || 1;
@@ -128,6 +182,7 @@ class TransactionCalculationEngine {
128
182
  total: input.total,
129
183
  taxRows: input.taxRows,
130
184
  includeTaxInTotal: input.includeTaxInTotal,
185
+ qty,
131
186
  });
132
187
  const disc = toNum(input.disc);
133
188
  const recDisc = toNum(input.recDisc);
@@ -369,15 +424,14 @@ class TransactionCalculationEngine {
369
424
  if (qty === 0) {
370
425
  return Object.assign({}, item);
371
426
  }
372
- const taxSummary = TransactionCalculationEngine.getTaxesSummary((_a = item === null || item === void 0 ? void 0 : item.Taxes) !== null && _a !== void 0 ? _a : item === null || item === void 0 ? void 0 : item.taxes);
373
427
  // If total doesn't include tax, treat it as "already stripped".
374
428
  const subtotal = includeTaxInTotal
375
- ? taxSummary.taxAmount !== 0
376
- ? (0, math_operations_1.Subtract)(total, taxSummary.taxAmount)
377
- : (() => {
378
- const divisor = (0, math_operations_1.Add)(1, (0, math_operations_1.Divide)(taxSummary.taxRate, 100));
379
- return (0, math_operations_1.Divide)(total, divisor === 0 ? 1 : divisor);
380
- })()
429
+ ? TransactionCalculationEngine.calculateSubtotalFromTotalUsingTaxes({
430
+ total,
431
+ taxRows: (_a = item === null || item === void 0 ? void 0 : item.Taxes) !== null && _a !== void 0 ? _a : item === null || item === void 0 ? void 0 : item.taxes,
432
+ includeTaxInTotal,
433
+ qty,
434
+ })
381
435
  : total;
382
436
  if (item.NoDisc) {
383
437
  const unpr = (0, math_operations_1.Divide)(subtotal, qty);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shareneus",
3
- "version": "1.7.336",
3
+ "version": "1.7.338",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,7 +1,6 @@
1
- import { Add, Divide, Multiply, Subtract } from "../shared/math-operations";
2
- import { GetNumber } from "../shared/util";
3
- import { CalculateLineTax } from "../tax/tax-calculator";
4
- import { TrUtils } from "../utils/tr-utils";
1
+ import { Add, Divide, Multiply, Subtract } from "../shared/math-operations";
2
+ import { TrUtils } from "../utils/tr-utils";
3
+ import { TransactionCalculationEngine } from "./transaction-calculation-engine";
5
4
 
6
5
  export function DiscountAndTaxCalculation(recordData: any, isTaxable: boolean, taxcodes: any = []) {
7
6
 
@@ -102,7 +101,7 @@ export function DiscountAndTaxCalculation(recordData: any, isTaxable: boolean, t
102
101
  const laborafterDiscPrice = Subtract(labor.Amt, labor.Disc);
103
102
  const afterDisc = Subtract(laborafterDiscPrice, totalTransactionDiscount);
104
103
 
105
- labor.Taxes = recalculateTaxes(labor, afterDisc, taxcodes);
104
+ labor.Taxes = recalculateTaxes(afterDisc, labor.TCode, taxcodes, labor.Taxes, labor.Qty || 1);
106
105
 
107
106
  return labor;
108
107
  });
@@ -135,7 +134,7 @@ export function DiscountAndTaxCalculation(recordData: any, isTaxable: boolean, t
135
134
  const ItemafterDiscPrice = Subtract(item.UnAmt, item.Disc);
136
135
  const afterDisc = Subtract(ItemafterDiscPrice, totalTransactionDiscount);
137
136
 
138
- item.Taxes = recalculateTaxes(item, afterDisc, taxcodes);
137
+ item.Taxes = recalculateTaxes(afterDisc, item.TCode, taxcodes, item.Taxes, item.Qty);
139
138
 
140
139
  return item;
141
140
  });
@@ -147,111 +146,101 @@ export function DiscountAndTaxCalculation(recordData: any, isTaxable: boolean, t
147
146
  return recordData;
148
147
  }
149
148
 
150
- function recalculateTaxes(line: any, amount: any, taxCodes: any[] = []): any[] {
151
- const existingTaxes = Array.isArray(line?.Taxes) ? line.Taxes : [];
152
- const taxCode = getTaxCodeForLine(line, taxCodes, existingTaxes);
153
-
154
- if (!taxCode) {
155
- return recalculateExistingTaxes(amount, line?.TCode, existingTaxes);
156
- }
157
-
158
- const qty = GetNumber(line?.Qty, 1) || 1;
159
- const calculatedTaxes = CalculateLineTax({
160
- Qty: qty,
161
- UnitPrice: Divide(amount, qty),
162
- Disc: 0,
163
- RecDisc: 0,
164
- TaxCode: normalizeTaxCodeForCalculation(taxCode, existingTaxes),
165
- ComponentOverrides: line?.ComponentOverrides,
166
- });
167
-
168
- const existingByCode = new Map(
169
- existingTaxes
170
- .filter((tax: any) => tax?.Code)
171
- .map((tax: any) => [tax.Code, tax])
172
- );
173
-
174
- return calculatedTaxes.map((tax: any) => ({
175
- ...(existingByCode.get(tax.Code) || {}),
176
- ...tax,
177
- }));
178
- }
179
-
180
- function getTaxCodeForLine(line: any, taxCodes: any[] = [], existingTaxes: any[] = []): any {
181
- if (line?.TCode?.Components) {
182
- return line.TCode;
183
- }
184
-
185
- const taxCodeId = !TrUtils.IsNull(line?.TCode)
186
- ? line.TCode
187
- : existingTaxes.find((tax: any) => !TrUtils.IsNull(tax?.TaxCodeId))?.TaxCodeId;
188
-
189
- if (TrUtils.IsNull(taxCodeId) || !Array.isArray(taxCodes)) {
190
- return null;
191
- }
192
-
193
- return taxCodes.find((code: any) => String(code?._id) === String(taxCodeId)) || null;
194
- }
195
-
196
- function normalizeTaxCodeForCalculation(taxCode: any, existingTaxes: any[] = []): any {
197
- if (Array.isArray(taxCode?.Components) && taxCode.Components.length > 0) {
198
- return taxCode;
199
- }
200
-
201
- return {
202
- ...taxCode,
203
- Components: buildLegacyComponents(taxCode, existingTaxes),
204
- };
205
- }
206
-
207
- function buildLegacyComponents(taxCode: any, existingTaxes: any[] = []): any[] {
208
- const components: any[] = ["CGST", "SGST", "IGST"]
209
- .map((code: string) => ({
210
- Code: code,
211
- Name: code,
212
- Rate: GetNumber(taxCode?.[code]),
213
- Type: "Tax",
214
- CalcMethod: "Percent",
215
- AppliedOn: "NetAmt",
216
- }))
217
- .filter((component: any) => component.Rate > 0);
218
-
219
- existingTaxes.forEach((tax: any) => {
220
- if (!tax?.Code || components.some((component: any) => component.Code === tax.Code)) {
221
- return;
222
- }
223
-
224
- components.push({
225
- Code: tax.Code,
226
- Name: tax.Name || tax.Code,
227
- Rate: GetNumber(tax.Rate),
228
- Type: tax.Type || (String(tax.Code).toUpperCase().includes("CESS") ? "Cess" : "Tax"),
229
- CalcMethod: tax.CalcMethod || "Percent",
230
- PerUnitAmt: tax.PerUnitAmt,
231
- Unit: tax.Unit,
232
- AppliedOn: tax.AppliedOn || "NetAmt",
233
- });
234
- });
235
-
236
- return components;
237
- }
238
-
239
- function recalculateExistingTaxes(amount: any, taxCode: any, existingTaxes: any[] = []): any[] {
240
- const normalizedTaxCode = TrUtils.IsNull(taxCode) ? null : Number(taxCode);
241
-
242
- return existingTaxes.map((tax: any) => {
243
- const rate = GetNumber(tax?.Rate);
244
-
245
- return {
246
- ...tax,
247
- Rate: rate,
248
- Amt: Multiply(amount, Divide(rate, 100)),
249
- TaxCodeId: !TrUtils.IsNull(tax?.TaxCodeId)
250
- ? Number(tax.TaxCodeId)
251
- : normalizedTaxCode
252
- };
253
- });
254
- }
149
+ function recalculateTaxes(amount: any, taxCode: any, taxCodes: any[] = [], existingTaxes: any[] = [], qty: any = 1): any[] {
150
+ const normalizedTaxCode = TrUtils.IsNull(taxCode) ? null : Number(taxCode);
151
+ const defaultRates = getDefaultTaxRates(taxCode, taxCodes);
152
+ const normalizedQty = toFiniteNumber(qty, 1);
153
+
154
+ return (Array.isArray(existingTaxes) ? existingTaxes : []).map((tax: any) => {
155
+ const code = tax?.Code;
156
+ const rate = Number(tax?.Rate);
157
+ const normalizedRate = Number.isFinite(rate) ? rate : (defaultRates[code] || 0);
158
+ const calcMethod = normalizeCalcMethod(tax?.CalcMethod);
159
+
160
+ return {
161
+ ...tax,
162
+ Rate: normalizedRate,
163
+ Amt: calculateComponentTaxAmount(amount, normalizedRate, tax, normalizedQty),
164
+ TaxCodeId: !TrUtils.IsNull(tax?.TaxCodeId)
165
+ ? Number(tax.TaxCodeId)
166
+ : normalizedTaxCode,
167
+ PerUnitAmt: tax?.PerUnitAmt || undefined,
168
+ CalcMethod: calcMethod,
169
+ Unit: tax?.Unit ?? tax?.Units ?? undefined,
170
+ };
171
+ });
172
+ }
173
+
174
+ function calculateComponentTaxAmount(amount: any, rate: number, tax: any, qty: number): number {
175
+ const baseAmt = toFiniteNumber(amount);
176
+ const calcMethod = normalizeCalcMethod(tax?.CalcMethod);
177
+ const perUnitAmt = toFiniteNumber(tax?.PerUnitAmt);
178
+ const perUnitResult = Multiply(perUnitAmt, qty);
179
+ const percentResult = Multiply(baseAmt, Divide(rate, 100));
180
+
181
+ switch (calcMethod) {
182
+ case 'PerUnit':
183
+ return perUnitResult;
184
+ case 'PerUnitPlusPercent':
185
+ return Add(perUnitResult, percentResult);
186
+ case 'MaxOfPercentOrPerUnit':
187
+ return Math.max(perUnitResult, percentResult);
188
+ case 'Percent':
189
+ default:
190
+ return percentResult;
191
+ }
192
+ }
193
+
194
+ function normalizeCalcMethod(calcMethod: any): string {
195
+ return calcMethod === 'Percentage' || TrUtils.IsNull(calcMethod)
196
+ ? 'Percent'
197
+ : calcMethod;
198
+ }
199
+
200
+ function toFiniteNumber(value: any, fallback = 0): number {
201
+ const normalized = Number(value);
202
+ return Number.isFinite(normalized) ? normalized : fallback;
203
+ }
204
+
205
+ function getDefaultTaxRates(taxCode: any, taxCodes: any[] = []): any {
206
+ const normalizedTaxCode = TrUtils.IsNull(taxCode) ? null : taxCode;
207
+ const taxCodeRow = Array.isArray(taxCodes)
208
+ ? taxCodes.find((code: any) => code?._id === normalizedTaxCode)
209
+ : null;
210
+ const components = Array.isArray(taxCodeRow?.Components)
211
+ ? taxCodeRow.Components
212
+ : [];
213
+
214
+ const getComponentRate = (code: string): number => {
215
+ const match = components.find((component: any) => {
216
+ const compCode = component?.Code ?? component?.Type ?? component?.Name;
217
+ return compCode === code;
218
+ });
219
+ const rate = Number(match?.Rate);
220
+ return Number.isFinite(rate) ? rate : 0;
221
+ };
222
+
223
+ let CGST = getComponentRate('CGST');
224
+ let SGST = getComponentRate('SGST');
225
+ let IGST = getComponentRate('IGST');
226
+ const CESS = getComponentRate('CESS');
227
+
228
+ if (CGST === 0 && SGST === 0 && IGST === 0) {
229
+ const gstRateSum =
230
+ TransactionCalculationEngine.getGSTRateSumByTaxCode(
231
+ normalizedTaxCode,
232
+ taxCodes,
233
+ );
234
+ if (components.length === 1) {
235
+ IGST = gstRateSum;
236
+ } else if (components.length === 2) {
237
+ CGST = gstRateSum / 2;
238
+ SGST = gstRateSum / 2;
239
+ }
240
+ }
241
+
242
+ return { CGST, SGST, IGST, CESS };
243
+ }
255
244
 
256
245
  /**
257
246
  * Calculates the record-level discount amount (RecDisc) for a single line.
@@ -65,13 +65,88 @@ function sumTaxAmountFromTaxes(taxes: any): number {
65
65
  );
66
66
  }
67
67
 
68
- function sumTaxRateFromTaxes(taxes: any): number {
69
- const rows = getTaxesArray(taxes);
70
- return rows.reduce(
71
- (sum, row) => Add(sum, toNum(row?.Rate ?? row?.rate)),
72
- 0,
73
- );
74
- }
68
+ function sumTaxRateFromTaxes(taxes: any): number {
69
+ const rows = getTaxesArray(taxes);
70
+ return rows.reduce(
71
+ (sum, row) => Add(sum, toNum(row?.Rate ?? row?.rate)),
72
+ 0,
73
+ );
74
+ }
75
+
76
+ function normalizeCalcMethod(calcMethod: any): string {
77
+ return calcMethod === 'Percentage' || isNullOrEmpty(calcMethod)
78
+ ? 'Percent'
79
+ : String(calcMethod);
80
+ }
81
+
82
+ function getTaxRowQty(row: any, fallbackQty: number): number {
83
+ return toNum(row?.Qty ?? row?.qty ?? row?.Quantity ?? row?.quantity) || fallbackQty;
84
+ }
85
+
86
+ function getPerUnitTaxAmount(
87
+ row: any,
88
+ fallbackQty: number,
89
+ fallbackToAmt = true,
90
+ ): number {
91
+ const perUnitAmt = toNum(row?.PerUnitAmt ?? row?.perUnitAmt);
92
+ const qty = getTaxRowQty(row, fallbackQty);
93
+
94
+ if (perUnitAmt !== 0 && qty !== 0) {
95
+ return Multiply(perUnitAmt, qty);
96
+ }
97
+
98
+ return fallbackToAmt ? toNum(row?.Amt ?? row?.amt) : 0;
99
+ }
100
+
101
+ function getReverseTaxBreakdown(taxes: any, qty: number): {
102
+ fixedTaxAmount: number;
103
+ percentTaxRate: number;
104
+ } {
105
+ return getTaxesArray(taxes).reduce(
106
+ (summary, row) => {
107
+ const calcMethod = normalizeCalcMethod(row?.CalcMethod ?? row?.calcMethod);
108
+ const rate = toNum(row?.Rate ?? row?.rate);
109
+
110
+ switch (calcMethod) {
111
+ case 'PerUnit':
112
+ summary.fixedTaxAmount = Add(
113
+ summary.fixedTaxAmount,
114
+ getPerUnitTaxAmount(row, qty),
115
+ );
116
+ break;
117
+
118
+ case 'PerUnitPlusPercent':
119
+ summary.fixedTaxAmount = Add(
120
+ summary.fixedTaxAmount,
121
+ getPerUnitTaxAmount(row, qty, false),
122
+ );
123
+ summary.percentTaxRate = Add(summary.percentTaxRate, rate);
124
+ break;
125
+
126
+ case 'MaxOfPercentOrPerUnit': {
127
+ const amt = toNum(row?.Amt ?? row?.amt);
128
+ const perUnitAmt = getPerUnitTaxAmount(row, qty);
129
+ if (amt !== 0) {
130
+ summary.fixedTaxAmount = Add(summary.fixedTaxAmount, amt);
131
+ } else if (perUnitAmt !== 0) {
132
+ summary.fixedTaxAmount = Add(summary.fixedTaxAmount, perUnitAmt);
133
+ } else {
134
+ summary.percentTaxRate = Add(summary.percentTaxRate, rate);
135
+ }
136
+ break;
137
+ }
138
+
139
+ case 'Percent':
140
+ default:
141
+ summary.percentTaxRate = Add(summary.percentTaxRate, rate);
142
+ break;
143
+ }
144
+
145
+ return summary;
146
+ },
147
+ { fixedTaxAmount: 0, percentTaxRate: 0 },
148
+ );
149
+ }
75
150
 
76
151
  /**
77
152
  * Distribute a flat discount amount across a set of rows proportionally
@@ -153,26 +228,30 @@ export class TransactionCalculationEngine {
153
228
  : baseAmount;
154
229
  }
155
230
 
156
- static calculateSubtotalFromTotalUsingTaxes(input: {
157
- total: number;
158
- taxRows: unknown;
159
- includeTaxInTotal: boolean;
160
- }): number {
161
- const total = toNum(input.total);
162
- if (!input.includeTaxInTotal) {
163
- return total;
164
- }
165
-
166
- const taxSummary = TransactionCalculationEngine.getTaxesSummary(
167
- input.taxRows,
168
- );
169
- if (taxSummary.taxRate) {
170
- const divisor = Add(1, Divide(taxSummary.taxRate, 100));
171
- return divisor === 0 ? total : Divide(total, divisor);
172
- }
173
-
174
- return Subtract(total, taxSummary.taxAmount);
175
- }
231
+ static calculateSubtotalFromTotalUsingTaxes(input: {
232
+ total: number;
233
+ taxRows: unknown;
234
+ includeTaxInTotal: boolean;
235
+ qty?: number;
236
+ }): number {
237
+ const total = toNum(input.total);
238
+ if (!input.includeTaxInTotal) {
239
+ return total;
240
+ }
241
+
242
+ const taxBreakdown = getReverseTaxBreakdown(
243
+ input.taxRows,
244
+ toNum(input.qty) || 1,
245
+ );
246
+ const totalAfterFixedTax = Subtract(total, taxBreakdown.fixedTaxAmount);
247
+
248
+ if (taxBreakdown.percentTaxRate) {
249
+ const divisor = Add(1, Divide(taxBreakdown.percentTaxRate, 100));
250
+ return divisor === 0 ? totalAfterFixedTax : Divide(totalAfterFixedTax, divisor);
251
+ }
252
+
253
+ return totalAfterFixedTax;
254
+ }
176
255
 
177
256
  static reverseCalculateAmountAndUnitPriceFromTotal(input: {
178
257
  qty: number;
@@ -185,11 +264,12 @@ export class TransactionCalculationEngine {
185
264
  }): { amount: number; unitPrice: number } {
186
265
  const qty = toNum(input.qty) || 1;
187
266
  const subtotal =
188
- TransactionCalculationEngine.calculateSubtotalFromTotalUsingTaxes({
189
- total: input.total,
190
- taxRows: input.taxRows,
191
- includeTaxInTotal: input.includeTaxInTotal,
192
- });
267
+ TransactionCalculationEngine.calculateSubtotalFromTotalUsingTaxes({
268
+ total: input.total,
269
+ taxRows: input.taxRows,
270
+ includeTaxInTotal: input.includeTaxInTotal,
271
+ qty,
272
+ });
193
273
 
194
274
  const disc = toNum(input.disc);
195
275
  const recDisc = toNum(input.recDisc);
@@ -474,19 +554,15 @@ export class TransactionCalculationEngine {
474
554
  return { ...item };
475
555
  }
476
556
 
477
- const taxSummary = TransactionCalculationEngine.getTaxesSummary(
478
- item?.Taxes ?? item?.taxes,
479
- );
480
-
481
- // If total doesn't include tax, treat it as "already stripped".
482
- const subtotal = includeTaxInTotal
483
- ? taxSummary.taxAmount !== 0
484
- ? Subtract(total, taxSummary.taxAmount)
485
- : (() => {
486
- const divisor = Add(1, Divide(taxSummary.taxRate, 100));
487
- return Divide(total, divisor === 0 ? 1 : divisor);
488
- })()
489
- : total;
557
+ // If total doesn't include tax, treat it as "already stripped".
558
+ const subtotal = includeTaxInTotal
559
+ ? TransactionCalculationEngine.calculateSubtotalFromTotalUsingTaxes({
560
+ total,
561
+ taxRows: item?.Taxes ?? item?.taxes,
562
+ includeTaxInTotal,
563
+ qty,
564
+ })
565
+ : total;
490
566
 
491
567
  if (item.NoDisc) {
492
568
  const unpr = Divide(subtotal, qty);