shareneus 1.5.91 → 1.5.93
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/dist/shared/table-section/pdf-table.header.js +25 -22
- package/dist/tax/index.d.ts +2 -2
- package/dist/tax/index.js +2 -1
- package/dist/tax/tax-calculator.d.ts +58 -9
- package/dist/tax/tax-calculator.js +187 -32
- package/dist/tax/tax.types.d.ts +115 -13
- package/package.json +1 -1
|
@@ -73,38 +73,31 @@ function getHeadersBySequence(headers) {
|
|
|
73
73
|
.map((entry) => entry.header);
|
|
74
74
|
}
|
|
75
75
|
function getRenderableColumns(headerConfig) {
|
|
76
|
-
const hasTaxPercent = headerConfig.some((header) => header.text === 'Tax %');
|
|
77
|
-
const hasTaxAmount = headerConfig.some((header) => header.text === 'Tax Amount');
|
|
78
|
-
const hasTaxGroup = hasTaxPercent && hasTaxAmount;
|
|
79
76
|
const columns = [];
|
|
80
77
|
for (const header of headerConfig) {
|
|
81
|
-
if (
|
|
78
|
+
if (isTaxPercentHeader(header)) {
|
|
82
79
|
columns.push({
|
|
83
80
|
type: 'tax',
|
|
84
81
|
taxKind: 'rate',
|
|
85
|
-
taxName: '%',
|
|
86
82
|
dbField: 'CGST',
|
|
87
83
|
width: getTaxSubWidth(header.width),
|
|
88
84
|
}, {
|
|
89
85
|
type: 'tax',
|
|
90
|
-
taxKind: '
|
|
91
|
-
dbField: '
|
|
92
|
-
taxName: 'Amount',
|
|
86
|
+
taxKind: 'rate',
|
|
87
|
+
dbField: 'SGST',
|
|
93
88
|
width: getTaxSubWidth(header.width),
|
|
94
89
|
});
|
|
95
90
|
continue;
|
|
96
91
|
}
|
|
97
|
-
if (
|
|
92
|
+
if (isTaxAmountHeader(header)) {
|
|
98
93
|
columns.push({
|
|
99
94
|
type: 'tax',
|
|
100
|
-
taxKind: '
|
|
101
|
-
|
|
102
|
-
dbField: 'SGST',
|
|
95
|
+
taxKind: 'amount',
|
|
96
|
+
dbField: 'CGST',
|
|
103
97
|
width: getTaxSubWidth(header.width),
|
|
104
98
|
}, {
|
|
105
99
|
type: 'tax',
|
|
106
100
|
taxKind: 'amount',
|
|
107
|
-
taxName: 'Amount',
|
|
108
101
|
dbField: 'SGST',
|
|
109
102
|
width: getTaxSubWidth(header.width),
|
|
110
103
|
});
|
|
@@ -119,9 +112,7 @@ function getRenderableColumns(headerConfig) {
|
|
|
119
112
|
return columns;
|
|
120
113
|
}
|
|
121
114
|
function buildHeaderRows(headerConfig) {
|
|
122
|
-
const
|
|
123
|
-
const hasTaxAmount = headerConfig.some((header) => header.text === 'Tax Amount');
|
|
124
|
-
const hasTaxGroup = hasTaxPercent && hasTaxAmount;
|
|
115
|
+
const hasTaxGroup = headerConfig.some((header) => isTaxPercentHeader(header) || isTaxAmountHeader(header));
|
|
125
116
|
const hasDiscPercent = headerConfig.some((header) => header.text === 'Disc %');
|
|
126
117
|
const hasDiscAmount = headerConfig.some((header) => isDiscountAmountHeader(header.text));
|
|
127
118
|
const hasDiscGroup = hasDiscPercent && hasDiscAmount;
|
|
@@ -133,14 +124,14 @@ function buildHeaderRows(headerConfig) {
|
|
|
133
124
|
const topRow = [];
|
|
134
125
|
const subRow = [];
|
|
135
126
|
for (const header of headerConfig) {
|
|
136
|
-
if (
|
|
137
|
-
topRow.push(Object.assign({ text:
|
|
138
|
-
subRow.push(Object.assign({ text: '
|
|
127
|
+
if (isTaxPercentHeader(header)) {
|
|
128
|
+
topRow.push(Object.assign({ text: header.text, colSpan: 2, alignment: 'center' }, getHeaderCellStyle()), {});
|
|
129
|
+
subRow.push(Object.assign({ text: 'CGST', alignment: 'center' }, getHeaderCellStyle()), Object.assign({ text: 'SGST/UGST', alignment: 'center' }, getHeaderCellStyle()));
|
|
139
130
|
continue;
|
|
140
131
|
}
|
|
141
|
-
if (
|
|
142
|
-
topRow.push(Object.assign({ text:
|
|
143
|
-
subRow.push(Object.assign({ text: '
|
|
132
|
+
if (isTaxAmountHeader(header)) {
|
|
133
|
+
topRow.push(Object.assign({ text: header.text, colSpan: 2, alignment: 'center' }, getHeaderCellStyle()), {});
|
|
134
|
+
subRow.push(Object.assign({ text: 'CGST', alignment: 'center' }, getHeaderCellStyle()), Object.assign({ text: 'SGST/UGST', alignment: 'center' }, getHeaderCellStyle()));
|
|
144
135
|
continue;
|
|
145
136
|
}
|
|
146
137
|
if (hasDiscGroup && header.text === 'Disc %') {
|
|
@@ -165,6 +156,18 @@ function getTaxSubWidth(width) {
|
|
|
165
156
|
function isDiscountAmountHeader(text) {
|
|
166
157
|
return text === 'Disc Amount' || text === 'Adisc Amount';
|
|
167
158
|
}
|
|
159
|
+
function isTaxPercentHeader(header) {
|
|
160
|
+
return normalizeHeaderText(header === null || header === void 0 ? void 0 : header.text) === 'TAX%';
|
|
161
|
+
}
|
|
162
|
+
function isTaxAmountHeader(header) {
|
|
163
|
+
return normalizeHeaderText(header === null || header === void 0 ? void 0 : header.text) === 'TAXAMOUNT';
|
|
164
|
+
}
|
|
165
|
+
function normalizeHeaderText(value) {
|
|
166
|
+
return String(value !== null && value !== void 0 ? value : '')
|
|
167
|
+
.replace(/\s+/g, '')
|
|
168
|
+
.trim()
|
|
169
|
+
.toUpperCase();
|
|
170
|
+
}
|
|
168
171
|
function getHeaderCellStyle() {
|
|
169
172
|
return {
|
|
170
173
|
bold: true,
|
package/dist/tax/index.d.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* This is the entry point for all tax-related functionality.
|
|
5
5
|
* Import from "shareneus/tax" in both UI and API code.
|
|
6
6
|
*/
|
|
7
|
-
export type { TaxComponentType, CalcMethod, AppliedOn, RoundingMethod, TaxCategory, SupplyType, ResolvedSupplyType, WithholdingType, ResolverType, IRegimeComponent, IRegimeFeatures, IRounding, ITreatment, ITaxRegime, ITaxCodeComponent, ITaxCode, ITaxComponent, ITaxSummaryLine, ITaxSummary, IWithholding, IWithholdingCalcInput, ITaxCalcLineInput, ITaxSummaryInput, IRoundingConfig, ITaxExemption, ITaxAuthority, IExternalProvider, } from "./tax.types";
|
|
7
|
+
export type { TaxComponentType, CalcMethod, AppliedOn, RoundingMethod, TaxCategory, SupplyType, ResolvedSupplyType, WithholdingType, ResolverType, IRegimeComponent, IRegimeFeatures, IRounding, ITreatment, ITaxRegime, ITaxCodeComponent, ITaxCode, ITaxComponent, ITaxSummaryLine, ITaxSummary, IWithholding, IWithholdingCalcInput, IComponentOverride, ITaxCalcLineInput, ITaxSummaryInput, IRoundingConfig, IDocumentTotalsInput, IDocumentTotals, ITaxExemption, ITaxAuthority, IExternalProvider, } from "./tax.types";
|
|
8
8
|
export type { IInclusiveResult } from "./tax-calculator";
|
|
9
|
-
export { calculateLineTax, extractNetFromInclusive, sumTaxComponents, computeTaxSummary, determineSupplyType, findTaxCodeByRateAndSupplyType, calculateWithholding, roundAmount, validateTaxId, validateHSNSACLength, convertFlatToTaxes, } from "./tax-calculator";
|
|
9
|
+
export { calculateLineTax, extractNetFromInclusive, sumTaxComponents, computeTaxSummary, computeDocumentTotals, determineSupplyType, findTaxCodeByRateAndSupplyType, calculateWithholding, roundAmount, validateTaxId, validateHSNSACLength, convertFlatToTaxes, } from "./tax-calculator";
|
package/dist/tax/index.js
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
* Import from "shareneus/tax" in both UI and API code.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.convertFlatToTaxes = exports.validateHSNSACLength = exports.validateTaxId = exports.roundAmount = exports.calculateWithholding = exports.findTaxCodeByRateAndSupplyType = exports.determineSupplyType = exports.computeTaxSummary = exports.sumTaxComponents = exports.extractNetFromInclusive = exports.calculateLineTax = void 0;
|
|
9
|
+
exports.convertFlatToTaxes = exports.validateHSNSACLength = exports.validateTaxId = exports.roundAmount = exports.calculateWithholding = exports.findTaxCodeByRateAndSupplyType = exports.determineSupplyType = exports.computeDocumentTotals = exports.computeTaxSummary = exports.sumTaxComponents = exports.extractNetFromInclusive = exports.calculateLineTax = void 0;
|
|
10
10
|
var tax_calculator_1 = require("./tax-calculator");
|
|
11
11
|
Object.defineProperty(exports, "calculateLineTax", { enumerable: true, get: function () { return tax_calculator_1.calculateLineTax; } });
|
|
12
12
|
Object.defineProperty(exports, "extractNetFromInclusive", { enumerable: true, get: function () { return tax_calculator_1.extractNetFromInclusive; } });
|
|
13
13
|
Object.defineProperty(exports, "sumTaxComponents", { enumerable: true, get: function () { return tax_calculator_1.sumTaxComponents; } });
|
|
14
14
|
Object.defineProperty(exports, "computeTaxSummary", { enumerable: true, get: function () { return tax_calculator_1.computeTaxSummary; } });
|
|
15
|
+
Object.defineProperty(exports, "computeDocumentTotals", { enumerable: true, get: function () { return tax_calculator_1.computeDocumentTotals; } });
|
|
15
16
|
Object.defineProperty(exports, "determineSupplyType", { enumerable: true, get: function () { return tax_calculator_1.determineSupplyType; } });
|
|
16
17
|
Object.defineProperty(exports, "findTaxCodeByRateAndSupplyType", { enumerable: true, get: function () { return tax_calculator_1.findTaxCodeByRateAndSupplyType; } });
|
|
17
18
|
Object.defineProperty(exports, "calculateWithholding", { enumerable: true, get: function () { return tax_calculator_1.calculateWithholding; } });
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* IMPORTANT: All monetary calculations use Big.js to avoid floating-point
|
|
15
15
|
* precision issues. Never use plain JS arithmetic (* / + -) for money.
|
|
16
16
|
*/
|
|
17
|
-
import { ITaxCode, ITaxComponent, ITaxSummary, ITaxCalcLineInput, ITaxSummaryInput, IRoundingConfig, ResolvedSupplyType, IWithholding, IWithholdingCalcInput } from "./tax.types";
|
|
17
|
+
import { ITaxCode, ITaxComponent, ITaxSummary, ITaxCalcLineInput, ITaxSummaryInput, IRoundingConfig, IDocumentTotalsInput, IDocumentTotals, ResolvedSupplyType, IWithholding, IWithholdingCalcInput } from "./tax.types";
|
|
18
18
|
/**
|
|
19
19
|
* Calculates tax for a single line item based on the provided TaxCode.
|
|
20
20
|
*
|
|
@@ -105,25 +105,30 @@ export declare function sumTaxComponents(taxes: ITaxComponent[] | undefined | nu
|
|
|
105
105
|
* This is a PURE FUNCTION — it does not read from database.
|
|
106
106
|
* Caller must derive NetAmt from line item fields and pass it in.
|
|
107
107
|
*
|
|
108
|
-
* Groups tax amounts by
|
|
109
|
-
* TaxableAmt per
|
|
108
|
+
* Groups tax amounts by Code+Rate (e.g., CGST@9 and CGST@14 are separate buckets).
|
|
109
|
+
* TaxableAmt per bucket = sum of line NetAmt for lines that have that Code+Rate.
|
|
110
110
|
* TotalTaxable = sum of unique line NetAmts (not double-counted across components).
|
|
111
111
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
112
|
+
* Rounding behavior (via input.Rounding):
|
|
113
|
+
* - TaxComponentTotal ON: each bucket's Amt is rounded to Precision
|
|
114
|
+
* - Otherwise: Amts are at currency precision (from calculateLineTax)
|
|
115
|
+
*
|
|
116
|
+
* @param input - All line items with Taxes[] and NetAmt, plus regime code and optional rounding
|
|
117
|
+
* @returns ITaxSummary — the aggregated summary (stored on the document)
|
|
114
118
|
*
|
|
115
119
|
* @example
|
|
116
120
|
* const summary = computeTaxSummary({
|
|
117
121
|
* Lines: invoice.Items.map(item => ({
|
|
118
122
|
* Taxes: item.Taxes,
|
|
119
|
-
* NetAmt: item.
|
|
123
|
+
* NetAmt: item.UnAmt - item.Disc - item.RecDisc,
|
|
120
124
|
* })),
|
|
121
|
-
* RegimeCode: "IN_GST"
|
|
125
|
+
* RegimeCode: "IN_GST",
|
|
126
|
+
* Rounding: { Method: "Round", Precision: 0, TaxComponentTotal: true },
|
|
122
127
|
* });
|
|
123
128
|
* // Returns: {
|
|
124
129
|
* // Lines: [
|
|
125
|
-
* // { Code: "CGST", TaxableAmt: 50000, Amt: 4500
|
|
126
|
-
* // { Code: "SGST", TaxableAmt: 50000, Amt: 4500
|
|
130
|
+
* // { Code: "CGST", Rate: 9, TaxableAmt: 50000, Amt: 4500 },
|
|
131
|
+
* // { Code: "SGST", Rate: 9, TaxableAmt: 50000, Amt: 4500 }
|
|
127
132
|
* // ],
|
|
128
133
|
* // TotalTaxable: 50000,
|
|
129
134
|
* // TotalTax: 9000,
|
|
@@ -131,6 +136,50 @@ export declare function sumTaxComponents(taxes: ITaxComponent[] | undefined | nu
|
|
|
131
136
|
* // }
|
|
132
137
|
*/
|
|
133
138
|
export declare function computeTaxSummary(input: ITaxSummaryInput): ITaxSummary;
|
|
139
|
+
/**
|
|
140
|
+
* Computes all document-level totals in one call: SubTotal, TaxTotal,
|
|
141
|
+
* TaxSummary, Round, and Total.
|
|
142
|
+
*
|
|
143
|
+
* This is the primary function UI/API should call at save time to produce
|
|
144
|
+
* the stored financial fields. All rounding is applied per the RoundingConfig.
|
|
145
|
+
*
|
|
146
|
+
* Flow:
|
|
147
|
+
* 1. SubTotal = sum of line NetAmts
|
|
148
|
+
* 2. TaxSummary = grouped by Code+Rate (rounded per TaxComponentTotal)
|
|
149
|
+
* 3. TaxTotal = sum of TaxSummary Amts
|
|
150
|
+
* 4. GrandTotal = SubTotal + TaxTotal
|
|
151
|
+
* 5. If DocTotal ON: round GrandTotal, compute Round adjustment
|
|
152
|
+
* 6. Total = GrandTotal + Round
|
|
153
|
+
*
|
|
154
|
+
* NOTE: Document-level Discount, Adjust, and Withholding are NOT handled here.
|
|
155
|
+
* The caller applies those after this function returns:
|
|
156
|
+
* FinalPayable = Total + Adjust - Withholding.Amt
|
|
157
|
+
*
|
|
158
|
+
* @param input - Line items with NetAmt and Taxes[], plus RoundingConfig
|
|
159
|
+
* @returns IDocumentTotals — all stored financial fields
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* // India GST invoice with 2 lines
|
|
163
|
+
* const totals = computeDocumentTotals({
|
|
164
|
+
* Lines: [
|
|
165
|
+
* { NetAmt: 5000, Taxes: [{ Code: "CGST", Rate: 9, Amt: 450, TaxCodeId: 1 },
|
|
166
|
+
* { Code: "SGST", Rate: 9, Amt: 450, TaxCodeId: 1 }] },
|
|
167
|
+
* { NetAmt: 3000, Taxes: [{ Code: "CGST", Rate: 9, Amt: 270, TaxCodeId: 1 },
|
|
168
|
+
* { Code: "SGST", Rate: 9, Amt: 270, TaxCodeId: 1 }] },
|
|
169
|
+
* ],
|
|
170
|
+
* Rounding: { Method: "Round", Precision: 0, TaxComponentTotal: true, DocTotal: true },
|
|
171
|
+
* RegimeCode: "IN_GST",
|
|
172
|
+
* });
|
|
173
|
+
* // Returns: {
|
|
174
|
+
* // SubTotal: 8000, TaxTotal: 1440, Round: 0, Total: 9440,
|
|
175
|
+
* // GrandTotal: 9440,
|
|
176
|
+
* // TaxSummary: [
|
|
177
|
+
* // { Code: "CGST", Rate: 9, TaxableAmt: 8000, Amt: 720 },
|
|
178
|
+
* // { Code: "SGST", Rate: 9, TaxableAmt: 8000, Amt: 720 },
|
|
179
|
+
* // ]
|
|
180
|
+
* // }
|
|
181
|
+
*/
|
|
182
|
+
export declare function computeDocumentTotals(input: IDocumentTotalsInput): IDocumentTotals;
|
|
134
183
|
/**
|
|
135
184
|
* Determines Intra-State or Inter-State based on seller and buyer state codes.
|
|
136
185
|
*
|
|
@@ -23,6 +23,7 @@ exports.calculateLineTax = calculateLineTax;
|
|
|
23
23
|
exports.extractNetFromInclusive = extractNetFromInclusive;
|
|
24
24
|
exports.sumTaxComponents = sumTaxComponents;
|
|
25
25
|
exports.computeTaxSummary = computeTaxSummary;
|
|
26
|
+
exports.computeDocumentTotals = computeDocumentTotals;
|
|
26
27
|
exports.determineSupplyType = determineSupplyType;
|
|
27
28
|
exports.findTaxCodeByRateAndSupplyType = findTaxCodeByRateAndSupplyType;
|
|
28
29
|
exports.roundAmount = roundAmount;
|
|
@@ -71,13 +72,21 @@ const math_operations_1 = require("../shared/math-operations");
|
|
|
71
72
|
* // ]
|
|
72
73
|
*/
|
|
73
74
|
function calculateLineTax(input, rounding = { Method: "Round", Precision: 2 }) {
|
|
74
|
-
const { TaxCode, RCM } = input;
|
|
75
|
+
const { TaxCode, RCM, ComponentOverrides } = input;
|
|
75
76
|
const netAmt = (0, util_1.GetNumber)(input.NetAmt);
|
|
76
77
|
const qty = (0, util_1.GetNumber)(input.Qty, 1);
|
|
77
78
|
// Exempt / Nil / NonTaxable — no components
|
|
78
79
|
if (!TaxCode.Components || TaxCode.Components.length === 0) {
|
|
79
80
|
return [];
|
|
80
81
|
}
|
|
82
|
+
// Determine line-level rounding precision:
|
|
83
|
+
// If LineTax is active, apply the configured rounding (Method + Precision)
|
|
84
|
+
// Otherwise, use standard currency precision (2 decimals, half-up)
|
|
85
|
+
// This distinction matters when Precision is 0 (India/Japan) — we don't want
|
|
86
|
+
// to round each line's tax to whole rupees unless LineTax is explicitly ON.
|
|
87
|
+
const lineRounding = rounding.LineTax
|
|
88
|
+
? { Method: rounding.Method, Precision: rounding.Precision }
|
|
89
|
+
: { Method: "Round", Precision: 2 };
|
|
81
90
|
const result = [];
|
|
82
91
|
// First pass: calculate all "Tax" type components (primary taxes)
|
|
83
92
|
// These are needed because some components (Cess with AppliedOn: "PostTax")
|
|
@@ -85,30 +94,55 @@ function calculateLineTax(input, rounding = { Method: "Round", Precision: 2 }) {
|
|
|
85
94
|
let primaryTaxTotal = 0;
|
|
86
95
|
for (const comp of TaxCode.Components) {
|
|
87
96
|
if (comp.AppliedOn !== "PostTax") {
|
|
88
|
-
|
|
97
|
+
// Apply ComponentOverrides if provided (e.g., CESS Rate/PerUnitAmt from item master)
|
|
98
|
+
const effectiveComp = applyComponentOverride(comp, ComponentOverrides);
|
|
99
|
+
const calculated = calculateSingleComponent(effectiveComp, netAmt, qty, lineRounding);
|
|
89
100
|
if (comp.Type === "Tax") {
|
|
90
101
|
primaryTaxTotal = (0, math_operations_1.Add)(primaryTaxTotal, calculated.Amt);
|
|
91
102
|
}
|
|
92
|
-
result.push(buildTaxComponent(
|
|
103
|
+
result.push(buildTaxComponent(effectiveComp, calculated.Amt, TaxCode._id));
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
// Second pass: calculate components that apply on PostTax (tax-on-tax)
|
|
96
107
|
for (const comp of TaxCode.Components) {
|
|
97
108
|
if (comp.AppliedOn === "PostTax") {
|
|
109
|
+
const effectiveComp = applyComponentOverride(comp, ComponentOverrides);
|
|
98
110
|
const postTaxBase = (0, math_operations_1.Add)(netAmt, primaryTaxTotal);
|
|
99
|
-
const calculated = calculateSingleComponent(
|
|
100
|
-
result.push(buildTaxComponent(
|
|
111
|
+
const calculated = calculateSingleComponent(effectiveComp, postTaxBase, qty, lineRounding);
|
|
112
|
+
result.push(buildTaxComponent(effectiveComp, calculated.Amt, TaxCode._id));
|
|
101
113
|
}
|
|
102
114
|
}
|
|
103
115
|
return result;
|
|
104
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Merges ComponentOverrides into a TaxCode component.
|
|
119
|
+
*
|
|
120
|
+
* Only Rate and PerUnitAmt can be overridden — structural fields (Code, Name,
|
|
121
|
+
* Type, CalcMethod, AppliedOn) always come from the TaxCode definition.
|
|
122
|
+
*
|
|
123
|
+
* Primary use case: CESS components where the TaxCode has placeholder values
|
|
124
|
+
* (Rate: 0, PerUnitAmt: 0) and the actual values come from the item master.
|
|
125
|
+
*/
|
|
126
|
+
function applyComponentOverride(comp, overrides) {
|
|
127
|
+
var _a, _b, _c;
|
|
128
|
+
if (!overrides)
|
|
129
|
+
return comp;
|
|
130
|
+
const override = overrides[comp.Code];
|
|
131
|
+
if (!override)
|
|
132
|
+
return comp;
|
|
133
|
+
return Object.assign(Object.assign({}, comp), { Rate: (_a = override.Rate) !== null && _a !== void 0 ? _a : comp.Rate, PerUnitAmt: (_b = override.PerUnitAmt) !== null && _b !== void 0 ? _b : comp.PerUnitAmt, Unit: (_c = override.Unit) !== null && _c !== void 0 ? _c : comp.Unit });
|
|
134
|
+
}
|
|
105
135
|
/**
|
|
106
136
|
* Calculates tax amount for a single component.
|
|
107
137
|
*
|
|
108
|
-
* Handles
|
|
138
|
+
* Handles four calculation methods:
|
|
109
139
|
* - Percent: (netAmt * rate) / 100
|
|
110
140
|
* - PerUnit: perUnitAmt * qty
|
|
111
141
|
* - PerUnitPlusPercent: (perUnitAmt * qty) + (netAmt * rate / 100)
|
|
142
|
+
* - MaxOfPercentOrPerUnit: max(netAmt * rate / 100, perUnitAmt * qty)
|
|
143
|
+
*
|
|
144
|
+
* MaxOfPercentOrPerUnit is used for India CESS on tobacco categories where
|
|
145
|
+
* the cess is "X% or ₹Y per unit, whichever is higher" (CBIC notification).
|
|
112
146
|
*/
|
|
113
147
|
function calculateSingleComponent(comp, baseAmt, qty, rounding) {
|
|
114
148
|
const method = comp.CalcMethod || "Percent";
|
|
@@ -139,6 +173,14 @@ function calculateSingleComponent(comp, baseAmt, qty, rounding) {
|
|
|
139
173
|
amt = (0, math_operations_1.Add)(fixedPart, percentPart);
|
|
140
174
|
break;
|
|
141
175
|
}
|
|
176
|
+
case "MaxOfPercentOrPerUnit": {
|
|
177
|
+
const perUnitAmt = (0, util_1.GetNumber)(comp.PerUnitAmt);
|
|
178
|
+
const rate = (0, util_1.GetNumber)(comp.Rate);
|
|
179
|
+
const perUnitResult = (0, math_operations_1.Multiply)(perUnitAmt, qty);
|
|
180
|
+
const percentResult = (0, math_operations_1.Multiply)(baseAmt, (0, math_operations_1.Divide)(rate, 100));
|
|
181
|
+
amt = Math.max(perUnitResult, percentResult);
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
142
184
|
}
|
|
143
185
|
// Apply rounding
|
|
144
186
|
amt = roundAmount(amt, rounding);
|
|
@@ -146,18 +188,39 @@ function calculateSingleComponent(comp, baseAmt, qty, rounding) {
|
|
|
146
188
|
}
|
|
147
189
|
/**
|
|
148
190
|
* Builds a lean ITaxComponent snapshot from a component definition and calculated amount.
|
|
149
|
-
*
|
|
191
|
+
*
|
|
192
|
+
* Core fields (always stored): Code, Rate, Amt, TaxCodeId
|
|
193
|
+
* Snapshot fields (only for non-percent): CalcMethod, PerUnitAmt
|
|
194
|
+
*
|
|
195
|
+
* For standard percent-based components (CGST, SGST, IGST), CalcMethod and PerUnitAmt
|
|
196
|
+
* are omitted to keep the snapshot lean. They're only included when the calculation
|
|
197
|
+
* method is PerUnit, PerUnitPlusPercent, or MaxOfPercentOrPerUnit — because these
|
|
198
|
+
* values may come from ComponentOverrides (item CessConfig) and must be preserved
|
|
199
|
+
* for invoice reprinting, credit notes, and audit.
|
|
150
200
|
*
|
|
151
201
|
* TaxableAmt is NOT stored — it's derivable from line item fields (Qty × UnitPrice - Discount).
|
|
152
202
|
* TaxAmt (sum of Taxes[].Amt) is NOT stored — it's a simple addition, computed on the fly.
|
|
153
203
|
*/
|
|
154
204
|
function buildTaxComponent(comp, amt, taxCodeId) {
|
|
155
|
-
|
|
205
|
+
const result = {
|
|
156
206
|
Code: comp.Code,
|
|
157
207
|
Rate: comp.Rate,
|
|
158
208
|
Amt: amt,
|
|
159
209
|
TaxCodeId: taxCodeId,
|
|
160
210
|
};
|
|
211
|
+
// Snapshot CalcMethod, PerUnitAmt, and Unit for non-percent components
|
|
212
|
+
// so the invoice is self-contained even if item cess config changes later
|
|
213
|
+
const method = comp.CalcMethod || "Percent";
|
|
214
|
+
if (method !== "Percent") {
|
|
215
|
+
result.CalcMethod = method;
|
|
216
|
+
if (comp.PerUnitAmt != null && comp.PerUnitAmt !== 0) {
|
|
217
|
+
result.PerUnitAmt = comp.PerUnitAmt;
|
|
218
|
+
}
|
|
219
|
+
if (comp.Unit) {
|
|
220
|
+
result.Unit = comp.Unit;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
161
224
|
}
|
|
162
225
|
function extractNetFromInclusive(inclusivePrice, taxCode, qty = 1, rounding = { Method: "Round", Precision: 2 }) {
|
|
163
226
|
if (!taxCode.Components || taxCode.Components.length === 0) {
|
|
@@ -170,6 +233,10 @@ function extractNetFromInclusive(inclusivePrice, taxCode, qty = 1, rounding = {
|
|
|
170
233
|
warning = "Inclusive back-calc is approximate: PerUnitPlusPercent component present";
|
|
171
234
|
break;
|
|
172
235
|
}
|
|
236
|
+
if (comp.CalcMethod === "MaxOfPercentOrPerUnit") {
|
|
237
|
+
warning = "Inclusive back-calc is approximate: MaxOfPercentOrPerUnit component present";
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
173
240
|
if (comp.AppliedOn === "PostTax") {
|
|
174
241
|
warning = "Inclusive back-calc is approximate: PostTax (tax-on-tax) component present";
|
|
175
242
|
break;
|
|
@@ -233,25 +300,30 @@ function sumTaxComponents(taxes) {
|
|
|
233
300
|
* This is a PURE FUNCTION — it does not read from database.
|
|
234
301
|
* Caller must derive NetAmt from line item fields and pass it in.
|
|
235
302
|
*
|
|
236
|
-
* Groups tax amounts by
|
|
237
|
-
* TaxableAmt per
|
|
303
|
+
* Groups tax amounts by Code+Rate (e.g., CGST@9 and CGST@14 are separate buckets).
|
|
304
|
+
* TaxableAmt per bucket = sum of line NetAmt for lines that have that Code+Rate.
|
|
238
305
|
* TotalTaxable = sum of unique line NetAmts (not double-counted across components).
|
|
239
306
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
307
|
+
* Rounding behavior (via input.Rounding):
|
|
308
|
+
* - TaxComponentTotal ON: each bucket's Amt is rounded to Precision
|
|
309
|
+
* - Otherwise: Amts are at currency precision (from calculateLineTax)
|
|
310
|
+
*
|
|
311
|
+
* @param input - All line items with Taxes[] and NetAmt, plus regime code and optional rounding
|
|
312
|
+
* @returns ITaxSummary — the aggregated summary (stored on the document)
|
|
242
313
|
*
|
|
243
314
|
* @example
|
|
244
315
|
* const summary = computeTaxSummary({
|
|
245
316
|
* Lines: invoice.Items.map(item => ({
|
|
246
317
|
* Taxes: item.Taxes,
|
|
247
|
-
* NetAmt: item.
|
|
318
|
+
* NetAmt: item.UnAmt - item.Disc - item.RecDisc,
|
|
248
319
|
* })),
|
|
249
|
-
* RegimeCode: "IN_GST"
|
|
320
|
+
* RegimeCode: "IN_GST",
|
|
321
|
+
* Rounding: { Method: "Round", Precision: 0, TaxComponentTotal: true },
|
|
250
322
|
* });
|
|
251
323
|
* // Returns: {
|
|
252
324
|
* // Lines: [
|
|
253
|
-
* // { Code: "CGST", TaxableAmt: 50000, Amt: 4500
|
|
254
|
-
* // { Code: "SGST", TaxableAmt: 50000, Amt: 4500
|
|
325
|
+
* // { Code: "CGST", Rate: 9, TaxableAmt: 50000, Amt: 4500 },
|
|
326
|
+
* // { Code: "SGST", Rate: 9, TaxableAmt: 50000, Amt: 4500 }
|
|
255
327
|
* // ],
|
|
256
328
|
* // TotalTaxable: 50000,
|
|
257
329
|
* // TotalTax: 9000,
|
|
@@ -259,50 +331,54 @@ function sumTaxComponents(taxes) {
|
|
|
259
331
|
* // }
|
|
260
332
|
*/
|
|
261
333
|
function computeTaxSummary(input) {
|
|
262
|
-
const { Lines, RegimeCode } = input;
|
|
263
|
-
// Group by
|
|
334
|
+
const { Lines, RegimeCode, Rounding } = input;
|
|
335
|
+
// Group by Code+Rate (more granular than Code-only for rate-wise reporting)
|
|
264
336
|
const groupMap = new Map();
|
|
265
337
|
let totalTaxable = 0;
|
|
266
|
-
let totalTax = 0;
|
|
267
338
|
for (const line of Lines) {
|
|
268
339
|
if (!line.Taxes || line.Taxes.length === 0)
|
|
269
340
|
continue;
|
|
270
341
|
const lineNetAmt = (0, util_1.GetNumber)(line.NetAmt);
|
|
271
342
|
// Track this line's taxable amount at document level (once per line, not per component)
|
|
272
343
|
totalTaxable = (0, math_operations_1.Add)(totalTaxable, lineNetAmt);
|
|
273
|
-
// Track which
|
|
274
|
-
const
|
|
344
|
+
// Track which Code+Rate buckets we've already added this line's NetAmt to
|
|
345
|
+
const bucketsTrackedForLine = new Set();
|
|
275
346
|
for (const tax of line.Taxes) {
|
|
276
|
-
const
|
|
347
|
+
const rate = (0, util_1.GetNumber)(tax.Rate);
|
|
348
|
+
const key = `${tax.Code}|${rate}`;
|
|
277
349
|
if (!groupMap.has(key)) {
|
|
278
350
|
groupMap.set(key, {
|
|
279
351
|
Code: tax.Code,
|
|
352
|
+
Rate: rate,
|
|
280
353
|
TaxableAmt: 0,
|
|
281
354
|
Amt: 0,
|
|
282
|
-
Rates: new Set(),
|
|
283
355
|
});
|
|
284
356
|
}
|
|
285
357
|
const group = groupMap.get(key);
|
|
286
|
-
// Add line's NetAmt to this
|
|
287
|
-
if (!
|
|
358
|
+
// Add line's NetAmt to this bucket's TaxableAmt (once per line per bucket)
|
|
359
|
+
if (!bucketsTrackedForLine.has(key)) {
|
|
288
360
|
group.TaxableAmt = (0, math_operations_1.Add)(group.TaxableAmt, lineNetAmt);
|
|
289
|
-
|
|
361
|
+
bucketsTrackedForLine.add(key);
|
|
290
362
|
}
|
|
291
363
|
group.Amt = (0, math_operations_1.Add)(group.Amt, (0, util_1.GetNumber)(tax.Amt));
|
|
292
|
-
group.Rates.add((0, util_1.GetNumber)(tax.Rate));
|
|
293
|
-
totalTax = (0, math_operations_1.Add)(totalTax, (0, util_1.GetNumber)(tax.Amt));
|
|
294
364
|
}
|
|
295
365
|
}
|
|
296
|
-
// Build summary lines
|
|
366
|
+
// Build summary lines, applying TaxComponentTotal rounding if configured
|
|
297
367
|
const summaryLines = [];
|
|
368
|
+
let totalTax = 0;
|
|
298
369
|
for (const [, group] of groupMap) {
|
|
370
|
+
let amt = group.Amt;
|
|
371
|
+
// If TaxComponentTotal rounding is active, round each bucket's Amt
|
|
372
|
+
if (Rounding === null || Rounding === void 0 ? void 0 : Rounding.TaxComponentTotal) {
|
|
373
|
+
amt = roundAmount(amt, { Method: Rounding.Method, Precision: Rounding.Precision });
|
|
374
|
+
}
|
|
299
375
|
summaryLines.push({
|
|
300
376
|
Code: group.Code,
|
|
377
|
+
Rate: group.Rate,
|
|
301
378
|
TaxableAmt: group.TaxableAmt,
|
|
302
|
-
Amt:
|
|
303
|
-
// Rate is only set if uniform across all lines, otherwise undefined
|
|
304
|
-
Rate: group.Rates.size === 1 ? Array.from(group.Rates)[0] : undefined,
|
|
379
|
+
Amt: amt,
|
|
305
380
|
});
|
|
381
|
+
totalTax = (0, math_operations_1.Add)(totalTax, amt);
|
|
306
382
|
}
|
|
307
383
|
return {
|
|
308
384
|
Lines: summaryLines,
|
|
@@ -312,6 +388,85 @@ function computeTaxSummary(input) {
|
|
|
312
388
|
};
|
|
313
389
|
}
|
|
314
390
|
// ============================================================
|
|
391
|
+
// Document Totals — Full document computation
|
|
392
|
+
// ============================================================
|
|
393
|
+
/**
|
|
394
|
+
* Computes all document-level totals in one call: SubTotal, TaxTotal,
|
|
395
|
+
* TaxSummary, Round, and Total.
|
|
396
|
+
*
|
|
397
|
+
* This is the primary function UI/API should call at save time to produce
|
|
398
|
+
* the stored financial fields. All rounding is applied per the RoundingConfig.
|
|
399
|
+
*
|
|
400
|
+
* Flow:
|
|
401
|
+
* 1. SubTotal = sum of line NetAmts
|
|
402
|
+
* 2. TaxSummary = grouped by Code+Rate (rounded per TaxComponentTotal)
|
|
403
|
+
* 3. TaxTotal = sum of TaxSummary Amts
|
|
404
|
+
* 4. GrandTotal = SubTotal + TaxTotal
|
|
405
|
+
* 5. If DocTotal ON: round GrandTotal, compute Round adjustment
|
|
406
|
+
* 6. Total = GrandTotal + Round
|
|
407
|
+
*
|
|
408
|
+
* NOTE: Document-level Discount, Adjust, and Withholding are NOT handled here.
|
|
409
|
+
* The caller applies those after this function returns:
|
|
410
|
+
* FinalPayable = Total + Adjust - Withholding.Amt
|
|
411
|
+
*
|
|
412
|
+
* @param input - Line items with NetAmt and Taxes[], plus RoundingConfig
|
|
413
|
+
* @returns IDocumentTotals — all stored financial fields
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* // India GST invoice with 2 lines
|
|
417
|
+
* const totals = computeDocumentTotals({
|
|
418
|
+
* Lines: [
|
|
419
|
+
* { NetAmt: 5000, Taxes: [{ Code: "CGST", Rate: 9, Amt: 450, TaxCodeId: 1 },
|
|
420
|
+
* { Code: "SGST", Rate: 9, Amt: 450, TaxCodeId: 1 }] },
|
|
421
|
+
* { NetAmt: 3000, Taxes: [{ Code: "CGST", Rate: 9, Amt: 270, TaxCodeId: 1 },
|
|
422
|
+
* { Code: "SGST", Rate: 9, Amt: 270, TaxCodeId: 1 }] },
|
|
423
|
+
* ],
|
|
424
|
+
* Rounding: { Method: "Round", Precision: 0, TaxComponentTotal: true, DocTotal: true },
|
|
425
|
+
* RegimeCode: "IN_GST",
|
|
426
|
+
* });
|
|
427
|
+
* // Returns: {
|
|
428
|
+
* // SubTotal: 8000, TaxTotal: 1440, Round: 0, Total: 9440,
|
|
429
|
+
* // GrandTotal: 9440,
|
|
430
|
+
* // TaxSummary: [
|
|
431
|
+
* // { Code: "CGST", Rate: 9, TaxableAmt: 8000, Amt: 720 },
|
|
432
|
+
* // { Code: "SGST", Rate: 9, TaxableAmt: 8000, Amt: 720 },
|
|
433
|
+
* // ]
|
|
434
|
+
* // }
|
|
435
|
+
*/
|
|
436
|
+
function computeDocumentTotals(input) {
|
|
437
|
+
const { Lines, Rounding, RegimeCode } = input;
|
|
438
|
+
// 1. SubTotal = sum of line NetAmts
|
|
439
|
+
let subTotal = 0;
|
|
440
|
+
for (const line of Lines) {
|
|
441
|
+
subTotal = (0, math_operations_1.Add)(subTotal, (0, util_1.GetNumber)(line.NetAmt));
|
|
442
|
+
}
|
|
443
|
+
// 2. TaxSummary (handles TaxComponentTotal rounding internally)
|
|
444
|
+
const taxSummary = computeTaxSummary({
|
|
445
|
+
Lines,
|
|
446
|
+
RegimeCode,
|
|
447
|
+
Rounding,
|
|
448
|
+
});
|
|
449
|
+
// 3. TaxTotal from the (potentially rounded) summary
|
|
450
|
+
const taxTotal = taxSummary.TotalTax;
|
|
451
|
+
// 4. GrandTotal before doc-level rounding
|
|
452
|
+
const grandTotal = (0, math_operations_1.Add)(subTotal, taxTotal);
|
|
453
|
+
// 5. DocTotal rounding
|
|
454
|
+
let roundedTotal = grandTotal;
|
|
455
|
+
let roundAdj = 0;
|
|
456
|
+
if (Rounding.DocTotal !== false) { // default true
|
|
457
|
+
roundedTotal = roundAmount(grandTotal, { Method: Rounding.Method, Precision: Rounding.Precision });
|
|
458
|
+
roundAdj = (0, math_operations_1.Subtract)(roundedTotal, grandTotal);
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
SubTotal: subTotal,
|
|
462
|
+
TaxTotal: taxTotal,
|
|
463
|
+
TaxSummary: taxSummary.Lines,
|
|
464
|
+
GrandTotal: grandTotal,
|
|
465
|
+
Round: roundAdj,
|
|
466
|
+
Total: roundedTotal,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
// ============================================================
|
|
315
470
|
// Supply Type Detection
|
|
316
471
|
// ============================================================
|
|
317
472
|
/**
|
package/dist/tax/tax.types.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/** Tax component types — what kind of tax is this? */
|
|
11
11
|
export type TaxComponentType = "Tax" | "Cess" | "Levy" | "Excise" | "Surcharge" | "Duty";
|
|
12
12
|
/** How the tax is calculated */
|
|
13
|
-
export type CalcMethod = "Percent" | "PerUnit" | "PerUnitPlusPercent";
|
|
13
|
+
export type CalcMethod = "Percent" | "PerUnit" | "PerUnitPlusPercent" | "MaxOfPercentOrPerUnit";
|
|
14
14
|
/** What base amount the tax applies to */
|
|
15
15
|
export type AppliedOn = "NetAmt" | "PostTax" | "Qty";
|
|
16
16
|
/** Rounding methods */
|
|
@@ -43,11 +43,23 @@ export interface IRegimeFeatures {
|
|
|
43
43
|
TaxIdLabel?: string;
|
|
44
44
|
TaxIdFormat?: string;
|
|
45
45
|
}
|
|
46
|
-
/** Rounding rules for a regime
|
|
46
|
+
/** Rounding rules for a regime.
|
|
47
|
+
* Three rounding points control WHERE in the calculation chain rounding is applied.
|
|
48
|
+
* LineTax and TaxComponentTotal are mutually exclusive in practice (line-level OR document-level tax rounding).
|
|
49
|
+
* DocTotal controls whether the grand total is rounded independently.
|
|
50
|
+
*
|
|
51
|
+
* Country presets:
|
|
52
|
+
* India GST: { Method: "Round", Precision: 0, TaxComponentTotal: true, DocTotal: true }
|
|
53
|
+
* US Sales Tax: { Method: "Round", Precision: 2, LineTax: true, DocTotal: true }
|
|
54
|
+
* Japan CT: { Method: "Floor", Precision: 0, TaxComponentTotal: true }
|
|
55
|
+
* EU/UK/AU: { Method: "Round", Precision: 2, DocTotal: true }
|
|
56
|
+
*/
|
|
47
57
|
export interface IRounding {
|
|
48
58
|
Method: RoundingMethod;
|
|
49
59
|
Precision: number;
|
|
50
|
-
|
|
60
|
+
LineTax: boolean;
|
|
61
|
+
TaxComponentTotal: boolean;
|
|
62
|
+
DocTotal: boolean;
|
|
51
63
|
}
|
|
52
64
|
/** A registration/treatment type valid for this regime */
|
|
53
65
|
export interface ITreatment {
|
|
@@ -80,6 +92,7 @@ export interface ITaxCodeComponent {
|
|
|
80
92
|
Type: TaxComponentType;
|
|
81
93
|
CalcMethod: CalcMethod;
|
|
82
94
|
PerUnitAmt?: number;
|
|
95
|
+
Unit?: string;
|
|
83
96
|
AppliedOn: AppliedOn;
|
|
84
97
|
}
|
|
85
98
|
/** The full TaxCode object */
|
|
@@ -119,8 +132,10 @@ export interface ITaxCode {
|
|
|
119
132
|
* so NOT stored here — line item is the source of truth.
|
|
120
133
|
* - TaxAmt (sum of Taxes[].Amt) is a simple addition → computed on the fly via sumTaxComponents().
|
|
121
134
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
135
|
+
* CalcMethod and PerUnitAmt are optional snapshot fields for non-percent cess.
|
|
136
|
+
* They capture the resolved calculation parameters at save time so the invoice
|
|
137
|
+
* remains self-contained and reproducible even if item cess config changes later.
|
|
138
|
+
* For standard percent-based components (CGST, SGST, IGST), these are omitted.
|
|
124
139
|
*
|
|
125
140
|
* NOTE: ITC is NOT stored here. ITC is at the line-item and document
|
|
126
141
|
* level (purchase-side only).
|
|
@@ -130,22 +145,29 @@ export interface ITaxComponent {
|
|
|
130
145
|
Rate: number;
|
|
131
146
|
Amt: number;
|
|
132
147
|
TaxCodeId?: number;
|
|
148
|
+
CalcMethod?: CalcMethod;
|
|
149
|
+
PerUnitAmt?: number;
|
|
150
|
+
Unit?: string;
|
|
133
151
|
}
|
|
134
152
|
/**
|
|
135
|
-
* TaxSummary is
|
|
153
|
+
* TaxSummary is computed by shareneus at save time and stored on the document.
|
|
154
|
+
* Line-item Taxes[] remains the source of truth. TaxSummary is a materialized
|
|
155
|
+
* snapshot for reporting efficiency — avoids complex $unwind/$group in aggregation.
|
|
136
156
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
157
|
+
* Grouped by Code+Rate (e.g., CGST@9, CGST@14 are separate entries).
|
|
158
|
+
* Rounding is applied per the RoundingConfig:
|
|
159
|
+
* - If TaxComponentTotal ON: each entry's Amt is rounded to Precision
|
|
160
|
+
* - If LineTax ON: Amts are already rounded at line level, summary just sums them
|
|
161
|
+
* - Otherwise: Amts are at currency precision (2 decimals)
|
|
140
162
|
*/
|
|
141
|
-
/** One line in the TaxSummary
|
|
163
|
+
/** One line in the TaxSummary — grouped by Code+Rate */
|
|
142
164
|
export interface ITaxSummaryLine {
|
|
143
165
|
Code: string;
|
|
166
|
+
Rate: number;
|
|
144
167
|
TaxableAmt: number;
|
|
145
168
|
Amt: number;
|
|
146
|
-
Rate?: number;
|
|
147
169
|
}
|
|
148
|
-
/** Document-level tax summary
|
|
170
|
+
/** Document-level tax summary — stored on Invoice, Bill, CreditNote, etc. */
|
|
149
171
|
export interface ITaxSummary {
|
|
150
172
|
Lines: ITaxSummaryLine[];
|
|
151
173
|
TotalTaxable: number;
|
|
@@ -193,6 +215,24 @@ export interface IWithholdingCalcInput {
|
|
|
193
215
|
BaseAmt: number;
|
|
194
216
|
PartyLiable?: "Buyer" | "Seller";
|
|
195
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Component-level overrides for tax calculation.
|
|
220
|
+
*
|
|
221
|
+
* Keyed by component Code (e.g., "CESS"). When provided, these values
|
|
222
|
+
* replace the TaxCode component's defaults at calculation time.
|
|
223
|
+
*
|
|
224
|
+
* Primary use case: CESS rates are HSN-specific, so the TaxCode defines
|
|
225
|
+
* the structure (CalcMethod, AppliedOn) while the actual Rate/PerUnitAmt
|
|
226
|
+
* comes from the item master's CessConfig.
|
|
227
|
+
*
|
|
228
|
+
* Only Rate and PerUnitAmt can be overridden — structural fields (Code,
|
|
229
|
+
* Name, Type, CalcMethod, AppliedOn) always come from the TaxCode.
|
|
230
|
+
*/
|
|
231
|
+
export interface IComponentOverride {
|
|
232
|
+
Rate?: number;
|
|
233
|
+
PerUnitAmt?: number;
|
|
234
|
+
Unit?: string;
|
|
235
|
+
}
|
|
196
236
|
/** Input context for calculating tax on a single line item */
|
|
197
237
|
export interface ITaxCalcLineInput {
|
|
198
238
|
/** Net amount after discounts (taxable value) */
|
|
@@ -203,6 +243,21 @@ export interface ITaxCalcLineInput {
|
|
|
203
243
|
TaxCode: ITaxCode;
|
|
204
244
|
/** Is this a reverse charge line? */
|
|
205
245
|
RCM?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Optional component-level overrides, keyed by component Code.
|
|
248
|
+
*
|
|
249
|
+
* Used when CESS Rate/PerUnitAmt comes from item master (CessConfig)
|
|
250
|
+
* rather than the TaxCode itself (which has placeholder values like Rate: 0).
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* // Item has CessRate: 12 (percent cess from HSN config)
|
|
254
|
+
* ComponentOverrides: { CESS: { Rate: 12 } }
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* // Item has CessPerUnitAmt: 5 (₹5/liter from HSN config)
|
|
258
|
+
* ComponentOverrides: { CESS: { PerUnitAmt: 5 } }
|
|
259
|
+
*/
|
|
260
|
+
ComponentOverrides?: Record<string, IComponentOverride>;
|
|
206
261
|
}
|
|
207
262
|
/** Input context for computing document-level TaxSummary.
|
|
208
263
|
*
|
|
@@ -218,11 +273,58 @@ export interface ITaxSummaryInput {
|
|
|
218
273
|
}>;
|
|
219
274
|
/** Tax regime code for the snapshot */
|
|
220
275
|
RegimeCode?: string;
|
|
276
|
+
/** Rounding config — if provided, applies TaxComponentTotal rounding to summary Amts */
|
|
277
|
+
Rounding?: IRoundingConfig;
|
|
221
278
|
}
|
|
222
|
-
/**
|
|
279
|
+
/**
|
|
280
|
+
* Rounding settings passed to calculator functions.
|
|
281
|
+
*
|
|
282
|
+
* Method + Precision define HOW to round.
|
|
283
|
+
* LineTax / TaxComponentTotal / DocTotal define WHERE (which rounding points are active).
|
|
284
|
+
*
|
|
285
|
+
* When passed to calculateLineTax:
|
|
286
|
+
* - LineTax ON → round each Taxes[].Amt to Precision
|
|
287
|
+
* - LineTax OFF → round to standard currency precision (2 decimals)
|
|
288
|
+
*
|
|
289
|
+
* When passed to computeTaxSummary:
|
|
290
|
+
* - TaxComponentTotal ON → round each Code+Rate bucket's Amt to Precision
|
|
291
|
+
*
|
|
292
|
+
* When passed to computeDocumentTotals:
|
|
293
|
+
* - DocTotal ON → round the grand total to Precision
|
|
294
|
+
*/
|
|
223
295
|
export interface IRoundingConfig {
|
|
224
296
|
Method: RoundingMethod;
|
|
225
297
|
Precision: number;
|
|
298
|
+
LineTax?: boolean;
|
|
299
|
+
TaxComponentTotal?: boolean;
|
|
300
|
+
DocTotal?: boolean;
|
|
301
|
+
}
|
|
302
|
+
/** Input for computing all document-level totals in one call */
|
|
303
|
+
export interface IDocumentTotalsInput {
|
|
304
|
+
/** All line items — caller derives NetAmt from line item fields (UnAmt - Disc - RecDisc) */
|
|
305
|
+
Lines: Array<{
|
|
306
|
+
NetAmt: number;
|
|
307
|
+
Taxes?: ITaxComponent[];
|
|
308
|
+
}>;
|
|
309
|
+
/** Rounding configuration (from TaxRegime.Rounding or Entity Settings) */
|
|
310
|
+
Rounding: IRoundingConfig;
|
|
311
|
+
/** Tax regime code for the TaxSummary snapshot */
|
|
312
|
+
RegimeCode?: string;
|
|
313
|
+
}
|
|
314
|
+
/** Result of full document totals computation */
|
|
315
|
+
export interface IDocumentTotals {
|
|
316
|
+
/** Sum of all line NetAmts (before tax) */
|
|
317
|
+
SubTotal: number;
|
|
318
|
+
/** Total tax amount — sum of rounded component Amts per RoundingConfig */
|
|
319
|
+
TaxTotal: number;
|
|
320
|
+
/** Rate-wise tax breakdown — grouped by Code+Rate, rounded per config */
|
|
321
|
+
TaxSummary: ITaxSummaryLine[];
|
|
322
|
+
/** SubTotal + TaxTotal (before DocTotal rounding) */
|
|
323
|
+
GrandTotal: number;
|
|
324
|
+
/** Rounding adjustment: RoundedTotal - GrandTotal (0 if DocTotal is off) */
|
|
325
|
+
Round: number;
|
|
326
|
+
/** Final amount: GrandTotal + Round */
|
|
327
|
+
Total: number;
|
|
226
328
|
}
|
|
227
329
|
/**
|
|
228
330
|
* Tax exemption reasons — used when a line item or contact is exempt from tax.
|