shareneus 1.7.4 → 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/accounting/invoice/invoice-pdf/invoice-pdf.service.js +18 -45
- package/dist/accounting/invoice/reports/excel/analysis-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/category-wise-item-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/cust-wise-sales-details.js +17 -7
- package/dist/accounting/invoice/reports/excel/cust-wise-sales-summary-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/invoice-wise-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/item-wise-doctor-sale-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/items-wise-sales-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/manf-wise-sales-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/operator-wise-details.js +17 -7
- package/dist/accounting/invoice/reports/excel/operator-wise-summary.js +17 -7
- package/dist/accounting/invoice/reports/excel/sa-wise-labor-sales-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/sa-wise-part-sales-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/sale-summary-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/sales-by-service-summary-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/scheduled-drug-summary-excel.service.js +17 -7
- package/dist/accounting/invoice/reports/excel/scheduled-drugs-excel.service.js +17 -7
- package/dist/accounting/invoice/unified-invoice-pdf.service.d.ts +1 -1
- package/dist/accounting/payment-receive/reports/excel/cust-balance-excel.service.js +17 -7
- package/dist/accounting/payment-receive/reports/excel/payment-receive-excel.service.js +17 -7
- package/dist/common/reports/excel/product-without-owner-excel.service.js +17 -7
- package/dist/gst/excel/GSTR-RO-excel.service.js +17 -7
- package/dist/gst/excel/GSTR1-excel.service.js +17 -7
- package/dist/gst/excel/GSTR2-excel.service.js +17 -7
- package/dist/gst/excel/hsn-summary.js +17 -7
- package/dist/gst/excel/tally-sales-import.service.js +17 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/inventory/items/reports/excel/expiring-drugs-excel.service.js +17 -7
- package/dist/inventory/items/reports/excel/item-wise-mout-details.js +17 -7
- package/dist/inventory/items/reports/excel/item-wise-mout-summary.js +17 -7
- package/dist/inventory/items/reports/excel/spares-issue-excel.service.js +17 -7
- package/dist/inventory/items/reports/excel/stock-excel.service.js +17 -7
- package/dist/purchases/payment-made/reports/excel/payments-made-excel.service.js +17 -7
- package/dist/purchases/payment-made/reports/excel/ven-balance-excel.service.js +17 -7
- package/dist/services/reports/excel/insurance-expire-excel.service.js +17 -7
- package/dist/services/reports/excel/next-service-date-excel.service.js +17 -7
- package/dist/services/reports/excel/repair-orders-excel.service.js +17 -7
- package/dist/shared/table-section/pdf-table.header.d.ts +1 -1
- package/dist/shared/table-section/pdf-table.header.js +14 -2
- package/dist/shared/table-section/pdf-table.section.js +6 -13
- package/dist/{accounting/invoice/invoice-pdf.service.js → shared/transactions-pdf.service.js} +15 -15
- package/package.json +1 -1
- package/src/accounting/counter-sales/auto-sales-receipt-pdf.service.ts +569 -0
- package/src/accounting/counter-sales/pos-receipt-pdf.ts +577 -0
- package/src/accounting/counter-sales/sales-receipt-pdf.service.ts +628 -0
- package/src/accounting/counter-sales/sales-receipt-print.service.ts +506 -0
- package/src/accounting/credit-note/cn-print.service.ts +264 -0
- package/src/accounting/credit-note/credit-note-pdf.service.ts +602 -0
- package/src/accounting/credit-note/credit-note-totals.service.ts +424 -0
- package/src/accounting/debit-note/debit-note-pdf.service.ts +681 -0
- package/src/accounting/debit-note/debit-note-print.service.ts +276 -0
- package/src/accounting/debit-note/debit-note-totals.service.ts +361 -0
- package/src/accounting/invoice/hc-inv-pdf.service.ts +880 -0
- package/src/accounting/invoice/inv-pdf.service.ts +812 -0
- package/src/accounting/invoice/inv-print.service.ts +532 -0
- package/src/accounting/invoice/invoice-landscape-pdf.service.ts +947 -0
- package/src/accounting/invoice/invoice-letterhead-pdf.service.ts +813 -0
- package/src/accounting/invoice/invoice-pdf/invoice-pdf.service.ts +359 -0
- package/src/accounting/invoice/invoice-portrait-pdf.ts +972 -0
- package/src/accounting/invoice/invoice-print.service.ts +2906 -0
- package/src/accounting/invoice/invoice-total.service.ts +834 -0
- package/src/accounting/invoice/reports/excel/analysis-excel.service.ts +291 -0
- package/src/accounting/invoice/reports/excel/category-wise-item-excel.service.ts +267 -0
- package/src/accounting/invoice/reports/excel/cust-wise-sales-details.ts +321 -0
- package/src/accounting/invoice/reports/excel/cust-wise-sales-summary-excel.service.ts +300 -0
- package/src/accounting/invoice/reports/excel/invoice-wise-excel.service.ts +859 -0
- package/src/accounting/invoice/reports/excel/item-wise-doctor-sale-excel.service.ts +255 -0
- package/src/accounting/invoice/reports/excel/items-wise-sales-excel.service.ts +312 -0
- package/src/accounting/invoice/reports/excel/manf-wise-sales-excel.service.ts +273 -0
- package/src/accounting/invoice/reports/excel/operator-wise-details.ts +258 -0
- package/src/accounting/invoice/reports/excel/operator-wise-summary.ts +259 -0
- package/src/accounting/invoice/reports/excel/sa-wise-labor-sales-excel.service.ts +230 -0
- package/src/accounting/invoice/reports/excel/sa-wise-part-sales-excel.service.ts +231 -0
- package/src/accounting/invoice/reports/excel/sale-summary-excel.service.ts +326 -0
- package/src/accounting/invoice/reports/excel/sales-by-service-details-excel.service.ts +0 -0
- package/src/accounting/invoice/reports/excel/sales-by-service-summary-excel.service.ts +432 -0
- package/src/accounting/invoice/reports/excel/scheduled-drug-summary-excel.service.ts +373 -0
- package/src/accounting/invoice/reports/excel/scheduled-drugs-excel.service.ts +372 -0
- package/src/accounting/invoice/reports/pdf/analysis-pdf.service.ts +113 -0
- package/src/accounting/invoice/reports/pdf/category-wise-item-pdf.service.ts +107 -0
- package/src/accounting/invoice/reports/pdf/cust-wise-sales-details-pdf.service.ts +125 -0
- package/src/accounting/invoice/reports/pdf/cust-wise-sales-summary-pdf.service.ts +119 -0
- package/src/accounting/invoice/reports/pdf/item-wise-doctor-sale-pdf.service.ts +180 -0
- package/src/accounting/invoice/reports/pdf/item-wise-sales-pdf.service.ts +193 -0
- package/src/accounting/invoice/reports/pdf/manf-wise-sales-pdf.service.ts +188 -0
- package/src/accounting/invoice/reports/pdf/operator-wise-details-pdf.service.ts +118 -0
- package/src/accounting/invoice/reports/pdf/operator-wise-summary-pdf.ts +116 -0
- package/src/accounting/invoice/reports/pdf/sales-by-service-pdf.service.ts +132 -0
- package/src/accounting/invoice/reports/pdf/scheduled-drug-pdf.service.ts +191 -0
- package/src/accounting/invoice/reports/pdf/scheduled-drug-summary-pdf.service.ts +202 -0
- package/src/accounting/invoice/shared-inv-pdf.service.ts +787 -0
- package/src/accounting/invoice/unified-invoice-pdf.service.ts +937 -0
- package/src/accounting/payment-receive/payment-pdf.service.ts +410 -0
- package/src/accounting/payment-receive/payment-receipt-pdf/receipt-pdf.service.ts +470 -0
- package/src/accounting/payment-receive/receipt-print.service.ts +71 -0
- package/src/accounting/payment-receive/reports/excel/cust-balance-excel.service.ts +298 -0
- package/src/accounting/payment-receive/reports/excel/payment-receive-excel.service.ts +221 -0
- package/src/accounting/payment-receive/reports/pdf/customer-balances-pdf.service.ts +182 -0
- package/src/accounting/payment-receive/reports/pdf/payment-report-pdf.service.ts +116 -0
- package/src/aggregation/aggregation.ts +58 -0
- package/src/appointments/appointments/appointment-total.service.ts +298 -0
- package/src/appointments/consultations/consultation-fee-receipt.service.ts +407 -0
- package/src/appointments/consultations/consultation-full-pdf.service.ts +238 -0
- package/src/appointments/consultations/consultation-letterhead-pdf.service.ts +430 -0
- package/src/appointments/consultations/consultation-pdf.service.ts +417 -0
- package/src/common/reports/excel/product-without-owner-excel.service.ts +308 -0
- package/src/common/reports/pdf/product-without-owner-pdf.service.ts +146 -0
- package/src/enums/cache-enums.ts +33 -0
- package/src/enums/code-enums.ts +291 -0
- package/src/enums/country-enums.ts +9 -0
- package/src/enums/enums.ts +364 -0
- package/src/enums/industry-enums.ts +26 -0
- package/src/enums/treatment-enums.ts +9 -0
- package/src/gst/excel/GSTR-RO-excel.service.ts +926 -0
- package/src/gst/excel/GSTR1-excel.service.ts +313 -0
- package/src/gst/excel/GSTR2-excel.service.ts +314 -0
- package/src/gst/excel/hsn-summary.ts +314 -0
- package/src/gst/excel/tally-sales-import.service.ts +767 -0
- package/src/gst/pdf/hsn-summary-pdf.ts +176 -0
- package/src/index.ts +194 -0
- package/src/inventory/items/adjustment-pdf.service.ts +177 -0
- package/src/inventory/items/issue-parts-pdf.service.ts +795 -0
- package/src/inventory/items/item-bar-code-label-pdf.ts +194 -0
- package/src/inventory/items/item-detais-pdf.ts +141 -0
- package/src/inventory/items/item-price-for-pricelist.ts +368 -0
- package/src/inventory/items/reports/excel/expiring-drugs-excel.service.ts +290 -0
- package/src/inventory/items/reports/excel/item-wise-mout-details.ts +284 -0
- package/src/inventory/items/reports/excel/item-wise-mout-summary.ts +279 -0
- package/src/inventory/items/reports/excel/spares-issue-excel.service.ts +494 -0
- package/src/inventory/items/reports/excel/stock-excel.service.ts +319 -0
- package/src/inventory/items/reports/pdf/expiring-drugs-pdf.service.ts +172 -0
- package/src/inventory/items/reports/pdf/item-wise-mout-details-pdf.ts +122 -0
- package/src/inventory/items/reports/pdf/item-wise-mout-summary-pdf.ts +115 -0
- package/src/inventory/items/reports/pdf/reorder-point-pdf.service.ts +163 -0
- package/src/inventory/material-out/mout-pdf.service.ts +545 -0
- package/src/inventory/transfer-order/transfer-order-pdf.service.ts +154 -0
- package/src/purchases/bills/bill-pdf/bill-pdf.service.ts +211 -0
- package/src/purchases/bills/bill-pdf.service.ts +21 -0
- package/src/purchases/payment-made/reports/excel/payments-made-excel.service.ts +313 -0
- package/src/purchases/payment-made/reports/excel/ven-balance-excel.service.ts +307 -0
- package/src/purchases/payment-made/reports/pdf/vendor-balances-pdf.service.ts +114 -0
- package/src/purchases/purchase-order/po-totals.service.ts +343 -0
- package/src/purchases/purchase-order/purchase-order-pdf.service.ts +1016 -0
- package/src/purchases/purchase-order/purchase-order-print.service.ts +279 -0
- package/src/purchases/purchase-order/purchase-order-totals.service.ts +637 -0
- package/src/purchases/vendor-credit-note/vendor-credit-note-pdf.service.ts +1055 -0
- package/src/purchases/vendor-credit-note/vendor-credit-note-print.service.ts +145 -0
- package/src/purchases/vendor-credit-note/vendor-credit-note-totals.service.ts +399 -0
- package/src/purchases/vendor-debit-note/vendor-debit-note-pdf.service.ts +582 -0
- package/src/purchases/vendor-debit-note/vendor-debit-note-print.service.ts +295 -0
- package/src/purchases/vendor-debit-note/vendor-debit-note-totals.service.ts +377 -0
- package/src/sales/delivery-challan/dc-landscape-pdf.service.ts +922 -0
- package/src/sales/delivery-challan/dc-landscape-without-price-pdf.service.ts +869 -0
- package/src/sales/delivery-challan/dc-without-price-pdf.service.ts +505 -0
- package/src/sales/delivery-challan/delivery-challan-pdf.service.ts +461 -0
- package/src/sales/delivery-challan/delivery-challan-print.service.ts +229 -0
- package/src/sales/delivery-challan/delivery-challan-totals.ts +466 -0
- package/src/sales/sales/equipment-design-pdf.service.ts +153 -0
- package/src/sales/sales/pack-ship-pdf.service.ts +128 -0
- package/src/sales/sales/pack-ship-print.service.ts +198 -0
- package/src/sales/sales/sales-pdf.service.ts +658 -0
- package/src/sales/sales/sales-print.service.ts +376 -0
- package/src/sales/sales/sales-totals.service.ts +500 -0
- package/src/sales-receive/sales-receive-pdf.service.ts +602 -0
- package/src/sales-receive/sales-receive-print.service.ts +242 -0
- package/src/sales-receive/sales-receive-totals.service.ts +651 -0
- package/src/services/checklist-pdf.ts +151 -0
- package/src/services/checklists-pdf.ts +133 -0
- package/src/services/est.print-service.ts +1155 -0
- package/src/services/reports/excel/insurance-expire-excel.service.ts +292 -0
- package/src/services/reports/excel/next-service-date-excel.service.ts +317 -0
- package/src/services/reports/excel/repair-orders-excel.service.ts +249 -0
- package/src/services/reports/pdf/insurance-expire-pdf.service.ts +115 -0
- package/src/services/reports/pdf/next-service-date-pdf.service.ts +198 -0
- package/src/services/reports/pdf/repair-orders-pdf.service.ts +184 -0
- package/src/services/ro-pdf.service.ts +1917 -0
- package/src/services/ro-print-service.ts +881 -0
- package/src/services/ro-totals.service.ts +1314 -0
- package/src/services/separate-wo-print.service.ts +396 -0
- package/src/services/service-history-pdf.service.ts +145 -0
- package/src/services/service-price-for-pricelist.ts +649 -0
- package/src/services/technician-pdf.service.ts +234 -0
- package/src/services/technician-print.service.ts +95 -0
- package/src/shared/header-footer-section/pdf-header-footer.section.ts +519 -0
- package/src/shared/header-footer-section/pdf-shared.utils.ts +46 -0
- package/src/shared/math-operations.ts +208 -0
- package/src/shared/party-details-section/pdf-party-details.section.ts +602 -0
- package/src/shared/shared-pdf.service.ts +3042 -0
- package/src/shared/shared-print.service.ts +879 -0
- package/src/shared/table-section/pdf-table.config.ts +8 -0
- package/src/shared/table-section/pdf-table.header.ts +396 -0
- package/src/shared/table-section/pdf-table.row.ts +248 -0
- package/src/shared/table-section/pdf-table.section.ts +447 -0
- package/src/shared/totals-section/pdf-totals.section.ts +921 -0
- package/src/shared/transactions-pdf.service.ts +191 -0
- package/src/shared/util.ts +101 -0
- package/src/tasks/meetings/meeting-pdf.ts +410 -0
- package/src/tasks/tasks/task-pdf.service.ts +238 -0
- package/src/tasks/tasks/task-reports-pdf.service.ts +313 -0
- package/src/tax/index.ts +86 -0
- package/src/tax/tax-calculator.ts +1025 -0
- package/src/tax/tax.types.ts +535 -0
- package/src/transaction-calculations/discounts-distribution.ts +343 -0
- package/src/transaction-calculations/index.ts +3 -0
- package/src/transaction-calculations/total-calculation.ts +443 -0
- package/src/transaction-calculations/transaction-calculation-engine.ts +903 -0
- package/src/utils/my-date.ts +111 -0
- package/src/utils/tr-utils.ts +104 -0
- package/tsconfig.json +2 -2
- /package/dist/{accounting/invoice/invoice-pdf.service.d.ts → shared/transactions-pdf.service.d.ts} +0 -0
|
@@ -0,0 +1,1025 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tax Calculator — Shared tax calculation logic
|
|
3
|
+
*
|
|
4
|
+
* Used by:
|
|
5
|
+
* - UI (Angular): for real-time tax calculation on transaction screens
|
|
6
|
+
* - API (Node.js): for server-side validation and future recalculation
|
|
7
|
+
*
|
|
8
|
+
* This module has NO dependencies on Angular, Mongoose, Express, or any
|
|
9
|
+
* framework. It only depends on:
|
|
10
|
+
* - Big.js (for precise decimal arithmetic)
|
|
11
|
+
* - Types from ./tax.types
|
|
12
|
+
* - Utility functions from ../shared/math-operations and ../shared/util
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT: All monetary calculations use Big.js to avoid floating-point
|
|
15
|
+
* precision issues. Never use plain JS arithmetic (* / + -) for money.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import Big from "big.js";
|
|
19
|
+
import { GetNumber } from "../shared/util";
|
|
20
|
+
import { Add, Subtract, Multiply, Divide } from "../shared/math-operations";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
ITaxCode,
|
|
24
|
+
ITaxCodeComponent,
|
|
25
|
+
ITaxComponent,
|
|
26
|
+
ITaxSummary,
|
|
27
|
+
ITaxSummaryLine,
|
|
28
|
+
ILineAmountFields,
|
|
29
|
+
ITaxCalcLineInput,
|
|
30
|
+
ITaxSummaryInput,
|
|
31
|
+
IRoundingConfig,
|
|
32
|
+
IDocumentTotalsInput,
|
|
33
|
+
IDocumentTotals,
|
|
34
|
+
IComponentOverride,
|
|
35
|
+
CalcMethod,
|
|
36
|
+
ResolvedSupplyType,
|
|
37
|
+
IWithholding,
|
|
38
|
+
IWithholdingCalcInput,
|
|
39
|
+
ITaxIdLabel,
|
|
40
|
+
ITaxIdEntry,
|
|
41
|
+
} from "./tax.types";
|
|
42
|
+
|
|
43
|
+
// ============================================================
|
|
44
|
+
// Net Amount Calculation
|
|
45
|
+
// ============================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Computes the gross line amount before discounts and tax.
|
|
49
|
+
*
|
|
50
|
+
* Formula: GrossAmt = Qty × UnitPrice
|
|
51
|
+
*
|
|
52
|
+
* If `UnAmt` / `Amt` is already present on the line, that value is treated as
|
|
53
|
+
* the line's gross amount source of truth.
|
|
54
|
+
*
|
|
55
|
+
* This is the amount used for document `SubTotal` under the standard:
|
|
56
|
+
* SubTotal = sum of line values before line/document discounts and before tax.
|
|
57
|
+
*/
|
|
58
|
+
export function CalculateGrossAmt(input: ILineAmountFields): number {
|
|
59
|
+
const line = input as ILineAmountFields & {
|
|
60
|
+
UnAmt?: number;
|
|
61
|
+
Amt?: number;
|
|
62
|
+
UnPr?: number;
|
|
63
|
+
Pr?: number;
|
|
64
|
+
UnitPrice?: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (line.UnAmt != null || line.Amt != null) {
|
|
68
|
+
return GetNumber(line.UnAmt ?? line.Amt);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const unitPrice = line.UnitPrice ?? line.UnPr ?? line.Pr;
|
|
72
|
+
if (unitPrice != null) {
|
|
73
|
+
const qty = input.Qty == null ? 1 : GetNumber(input.Qty);
|
|
74
|
+
return Multiply(qty, GetNumber(unitPrice));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Computes the net taxable amount from line item fields.
|
|
82
|
+
*
|
|
83
|
+
* Formula: NetAmt = (Qty × UnitPrice) - Disc - RecDisc
|
|
84
|
+
*
|
|
85
|
+
* Generic across both goods and services:
|
|
86
|
+
* For Items (goods): pass UnitPrice = item.UnPr
|
|
87
|
+
* For Ops (services): pass UnitPrice = op.Pr
|
|
88
|
+
*
|
|
89
|
+
* @param input - Line amount fields: Qty, UnitPrice, Disc, RecDisc
|
|
90
|
+
* @returns The net taxable amount
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Item: 10 units × Rs 500, line discount Rs 200, record discount Rs 100
|
|
94
|
+
* CalculateNetAmt({ Qty: 10, UnitPrice: 500, Disc: 200, RecDisc: 100 });
|
|
95
|
+
* // Returns: 4700 (5000 - 200 - 100)
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Op: 2 hours × Rs 1500, no discounts
|
|
99
|
+
* CalculateNetAmt({ Qty: 2, UnitPrice: 1500 });
|
|
100
|
+
* // Returns: 3000
|
|
101
|
+
*/
|
|
102
|
+
export function CalculateNetAmt(input: ILineAmountFields): number {
|
|
103
|
+
const disc = GetNumber(input.Disc);
|
|
104
|
+
const recDisc = GetNumber(input.RecDisc);
|
|
105
|
+
|
|
106
|
+
return Subtract(CalculateGrossAmt(input), disc, recDisc);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================
|
|
110
|
+
// Core Tax Calculation
|
|
111
|
+
// ============================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Calculates tax for a single line item based on the provided TaxCode.
|
|
115
|
+
*
|
|
116
|
+
* This is the MAIN function used by the UI when a user selects a tax code
|
|
117
|
+
* and the system needs to compute CGST, SGST, IGST, Cess, etc.
|
|
118
|
+
*
|
|
119
|
+
* @param input - Line item context (Qty, UnitPrice, Disc, RecDisc, TaxCode, RCM flag)
|
|
120
|
+
* @param rounding - Rounding configuration (from TaxRegime.Rounding)
|
|
121
|
+
* @returns Array of ITaxComponent — one entry per tax component
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* // India GST 18% Intra-State: 10 units × Rs 500
|
|
125
|
+
* const result = CalculateLineTax({
|
|
126
|
+
* Qty: 10, UnitPrice: 500,
|
|
127
|
+
* TaxCode: gst18IntraTaxCode,
|
|
128
|
+
* });
|
|
129
|
+
* // NetAmt = 5000, Returns: [
|
|
130
|
+
* // { Code: "CGST", Rate: 9, Amt: 450, TaxCodeId: 106 },
|
|
131
|
+
* // { Code: "SGST", Rate: 9, Amt: 450, TaxCodeId: 106 },
|
|
132
|
+
* // ]
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // India GST 28% + Specific Cess (Rs 12/liter) on 100 liters × Rs 100
|
|
136
|
+
* const result = CalculateLineTax({
|
|
137
|
+
* Qty: 100, UnitPrice: 100,
|
|
138
|
+
* TaxCode: gst28CessDrinkTaxCode,
|
|
139
|
+
* });
|
|
140
|
+
* // NetAmt = 10000, Returns: [
|
|
141
|
+
* // { Code: "CGST", Rate: 14, Amt: 1400, TaxCodeId: 201 },
|
|
142
|
+
* // { Code: "SGST", Rate: 14, Amt: 1400, TaxCodeId: 201 },
|
|
143
|
+
* // { Code: "CESS", Rate: 0, Amt: 1200, TaxCodeId: 201 },
|
|
144
|
+
* // ]
|
|
145
|
+
*/
|
|
146
|
+
export function CalculateLineTax(
|
|
147
|
+
input: ITaxCalcLineInput,
|
|
148
|
+
rounding: IRoundingConfig = { Method: "Round", Precision: 2 }
|
|
149
|
+
): ITaxComponent[] {
|
|
150
|
+
const { TaxCode, RCM, ComponentOverrides } = input;
|
|
151
|
+
const netAmt = CalculateNetAmt(input);
|
|
152
|
+
const qty = GetNumber(input.Qty, 1);
|
|
153
|
+
|
|
154
|
+
// Exempt / Nil / NonTaxable — no components
|
|
155
|
+
if (!TaxCode.Components || TaxCode.Components.length === 0) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Determine line-level rounding precision:
|
|
160
|
+
// If LineTax is active, apply the configured rounding (Method + Precision)
|
|
161
|
+
// Otherwise, use standard currency precision (2 decimals, half-up)
|
|
162
|
+
// This distinction matters when Precision is 0 (India/Japan) — we don't want
|
|
163
|
+
// to round each line's tax to whole rupees unless LineTax is explicitly ON.
|
|
164
|
+
const lineRounding: IRoundingConfig = rounding.LineTax
|
|
165
|
+
? { Method: rounding.Method, Precision: rounding.Precision }
|
|
166
|
+
: { Method: "Round", Precision: 2 };
|
|
167
|
+
|
|
168
|
+
const result: ITaxComponent[] = [];
|
|
169
|
+
|
|
170
|
+
// First pass: calculate all "Tax" type components (primary taxes)
|
|
171
|
+
// These are needed because some components (Cess with AppliedOn: "PostTax")
|
|
172
|
+
// may need the sum of primary taxes as their base
|
|
173
|
+
let primaryTaxTotal = 0;
|
|
174
|
+
|
|
175
|
+
for (const comp of TaxCode.Components) {
|
|
176
|
+
if (comp.AppliedOn !== "PostTax") {
|
|
177
|
+
// Apply ComponentOverrides if provided (e.g., CESS Rate/PerUnitAmt from item master)
|
|
178
|
+
const effectiveComp = ApplyComponentOverride(comp, ComponentOverrides);
|
|
179
|
+
const calculated = CalculateSingleComponent(effectiveComp, netAmt, qty, lineRounding);
|
|
180
|
+
if (comp.Type === "Tax") {
|
|
181
|
+
primaryTaxTotal = Add(primaryTaxTotal, calculated.Amt);
|
|
182
|
+
}
|
|
183
|
+
result.push(BuildTaxComponent(effectiveComp, calculated.Amt, TaxCode._id));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Second pass: calculate components that apply on PostTax (tax-on-tax)
|
|
188
|
+
for (const comp of TaxCode.Components) {
|
|
189
|
+
if (comp.AppliedOn === "PostTax") {
|
|
190
|
+
const effectiveComp = ApplyComponentOverride(comp, ComponentOverrides);
|
|
191
|
+
const postTaxBase = Add(netAmt, primaryTaxTotal);
|
|
192
|
+
const calculated = CalculateSingleComponent(effectiveComp, postTaxBase, qty, lineRounding);
|
|
193
|
+
result.push(BuildTaxComponent(effectiveComp, calculated.Amt, TaxCode._id));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Merges ComponentOverrides into a TaxCode component.
|
|
202
|
+
*
|
|
203
|
+
* Only Rate and PerUnitAmt can be overridden — structural fields (Code, Name,
|
|
204
|
+
* Type, CalcMethod, AppliedOn) always come from the TaxCode definition.
|
|
205
|
+
*
|
|
206
|
+
* Primary use case: CESS components where the TaxCode has placeholder values
|
|
207
|
+
* (Rate: 0, PerUnitAmt: 0) and the actual values come from the item master.
|
|
208
|
+
*/
|
|
209
|
+
function ApplyComponentOverride(
|
|
210
|
+
comp: ITaxCodeComponent,
|
|
211
|
+
overrides?: Record<string, IComponentOverride>
|
|
212
|
+
): ITaxCodeComponent {
|
|
213
|
+
if (!overrides) return comp;
|
|
214
|
+
|
|
215
|
+
const override = overrides[comp.Code];
|
|
216
|
+
if (!override) return comp;
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
...comp,
|
|
220
|
+
Rate: override.Rate ?? comp.Rate,
|
|
221
|
+
PerUnitAmt: override.PerUnitAmt ?? comp.PerUnitAmt,
|
|
222
|
+
Unit: override.Unit ?? comp.Unit,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Calculates tax amount for a single component.
|
|
228
|
+
*
|
|
229
|
+
* Handles four calculation methods:
|
|
230
|
+
* - Percent: (netAmt * rate) / 100
|
|
231
|
+
* - PerUnit: perUnitAmt * qty
|
|
232
|
+
* - PerUnitPlusPercent: (perUnitAmt * qty) + (netAmt * rate / 100)
|
|
233
|
+
* - MaxOfPercentOrPerUnit: max(netAmt * rate / 100, perUnitAmt * qty)
|
|
234
|
+
*
|
|
235
|
+
* MaxOfPercentOrPerUnit is used for India CESS on tobacco categories where
|
|
236
|
+
* the cess is "X% or ₹Y per unit, whichever is higher" (CBIC notification).
|
|
237
|
+
*/
|
|
238
|
+
function CalculateSingleComponent(
|
|
239
|
+
comp: ITaxCodeComponent,
|
|
240
|
+
baseAmt: number,
|
|
241
|
+
qty: number,
|
|
242
|
+
rounding: IRoundingConfig
|
|
243
|
+
): { Amt: number; taxableAmt: number } {
|
|
244
|
+
|
|
245
|
+
const method: CalcMethod = comp.CalcMethod || "Percent";
|
|
246
|
+
let amt: number = 0;
|
|
247
|
+
let taxableAmt = baseAmt;
|
|
248
|
+
|
|
249
|
+
switch (method) {
|
|
250
|
+
case "Percent": {
|
|
251
|
+
const rate = GetNumber(comp.Rate);
|
|
252
|
+
if (rate === 0) {
|
|
253
|
+
amt = 0;
|
|
254
|
+
} else {
|
|
255
|
+
amt = Multiply(baseAmt, Divide(rate, 100));
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case "PerUnit": {
|
|
261
|
+
const perUnitAmt = GetNumber(comp.PerUnitAmt);
|
|
262
|
+
amt = Multiply(perUnitAmt, qty);
|
|
263
|
+
taxableAmt = baseAmt; // taxable base is still the net amount for reporting
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case "PerUnitPlusPercent": {
|
|
268
|
+
const perUnitAmt = GetNumber(comp.PerUnitAmt);
|
|
269
|
+
const rate = GetNumber(comp.Rate);
|
|
270
|
+
const fixedPart = Multiply(perUnitAmt, qty);
|
|
271
|
+
const percentPart = Multiply(baseAmt, Divide(rate, 100));
|
|
272
|
+
amt = Add(fixedPart, percentPart);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
case "MaxOfPercentOrPerUnit": {
|
|
277
|
+
const perUnitAmt = GetNumber(comp.PerUnitAmt);
|
|
278
|
+
const rate = GetNumber(comp.Rate);
|
|
279
|
+
const perUnitResult = Multiply(perUnitAmt, qty);
|
|
280
|
+
const percentResult = Multiply(baseAmt, Divide(rate, 100));
|
|
281
|
+
amt = Math.max(perUnitResult, percentResult);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Apply rounding
|
|
287
|
+
amt = RoundAmount(amt, rounding);
|
|
288
|
+
|
|
289
|
+
return { Amt: amt, taxableAmt };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Builds a lean ITaxComponent snapshot from a component definition and calculated amount.
|
|
294
|
+
*
|
|
295
|
+
* Core fields (always stored): Code, Rate, Amt, TaxCodeId
|
|
296
|
+
* Snapshot fields (only for non-percent): CalcMethod, PerUnitAmt
|
|
297
|
+
*
|
|
298
|
+
* For standard percent-based components (CGST, SGST, IGST), CalcMethod and PerUnitAmt
|
|
299
|
+
* are omitted to keep the snapshot lean. They're only included when the calculation
|
|
300
|
+
* method is PerUnit, PerUnitPlusPercent, or MaxOfPercentOrPerUnit — because these
|
|
301
|
+
* values may come from ComponentOverrides (item CessConfig) and must be preserved
|
|
302
|
+
* for invoice reprinting, credit notes, and audit.
|
|
303
|
+
*
|
|
304
|
+
* TaxableAmt is NOT stored — it's derivable from line item fields (Qty × UnitPrice - Discount).
|
|
305
|
+
* TaxAmt (sum of Taxes[].Amt) is NOT stored — it's a simple addition, computed on the fly.
|
|
306
|
+
*/
|
|
307
|
+
function BuildTaxComponent(
|
|
308
|
+
comp: ITaxCodeComponent,
|
|
309
|
+
amt: number,
|
|
310
|
+
taxCodeId: number,
|
|
311
|
+
): ITaxComponent {
|
|
312
|
+
const result: ITaxComponent = {
|
|
313
|
+
Code: comp.Code,
|
|
314
|
+
Rate: comp.Rate,
|
|
315
|
+
Amt: amt,
|
|
316
|
+
TaxCodeId: taxCodeId,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Snapshot CalcMethod, PerUnitAmt, and Unit for non-percent components
|
|
320
|
+
// so the invoice is self-contained even if item cess config changes later
|
|
321
|
+
const method: CalcMethod = comp.CalcMethod || "Percent";
|
|
322
|
+
if (method !== "Percent") {
|
|
323
|
+
result.CalcMethod = method;
|
|
324
|
+
if (comp.PerUnitAmt != null && comp.PerUnitAmt !== 0) {
|
|
325
|
+
result.PerUnitAmt = comp.PerUnitAmt;
|
|
326
|
+
}
|
|
327
|
+
if (comp.Unit) {
|
|
328
|
+
result.Unit = comp.Unit;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ============================================================
|
|
336
|
+
// Tax-Inclusive Price Back-Calculation
|
|
337
|
+
// ============================================================
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Extracts the pre-tax (net) amount from a tax-inclusive price.
|
|
341
|
+
*
|
|
342
|
+
* Used when the entity settings have TaxInc: "I" (tax inclusive pricing).
|
|
343
|
+
* Given a price that already includes tax, this function back-calculates
|
|
344
|
+
* what the net amount should be.
|
|
345
|
+
*
|
|
346
|
+
* Formula: NetAmt = InclusivePrice / (1 + CombinedRate/100)
|
|
347
|
+
*
|
|
348
|
+
* NOTE: This only works for Percent-based components. PerUnit components
|
|
349
|
+
* are subtracted directly: NetAmt = InclusivePrice - (PerUnitAmt * Qty)
|
|
350
|
+
* before applying the percentage back-calculation.
|
|
351
|
+
*
|
|
352
|
+
* @param inclusivePrice - The price including tax
|
|
353
|
+
* @param taxCode - The TaxCode to use for back-calculation
|
|
354
|
+
* @param qty - Quantity (needed for PerUnit cess components)
|
|
355
|
+
* @param rounding - Rounding configuration
|
|
356
|
+
* @returns The pre-tax net amount
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* // Price Rs 5,900 inclusive of GST 18%
|
|
360
|
+
* const netAmt = ExtractNetFromInclusive(5900, gst18IntraTaxCode);
|
|
361
|
+
* // Returns: 5000 (because 5000 + 18% = 5900)
|
|
362
|
+
*/
|
|
363
|
+
/**
|
|
364
|
+
* Result of tax-inclusive back-calculation.
|
|
365
|
+
*
|
|
366
|
+
* When the calculation is exact, `warning` is undefined.
|
|
367
|
+
* When the TaxCode contains unsupported component types (PerUnitPlusPercent, PostTax),
|
|
368
|
+
* an approximate result is returned with a `warning` flag so the caller can decide
|
|
369
|
+
* whether to show a warning or reject the result.
|
|
370
|
+
*/
|
|
371
|
+
export interface IInclusiveResult {
|
|
372
|
+
NetAmt: number;
|
|
373
|
+
/** If set, the back-calculation is approximate — component structure doesn't support exact inversion */
|
|
374
|
+
Warning?: string;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function ExtractNetFromInclusive(
|
|
378
|
+
inclusivePrice: number,
|
|
379
|
+
taxCode: ITaxCode,
|
|
380
|
+
qty: number = 1,
|
|
381
|
+
rounding: IRoundingConfig = { Method: "Round", Precision: 2 }
|
|
382
|
+
): IInclusiveResult {
|
|
383
|
+
if (!taxCode.Components || taxCode.Components.length === 0) {
|
|
384
|
+
return { NetAmt: inclusivePrice };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Guard: check for unsupported inversion cases
|
|
388
|
+
let warning: string | undefined;
|
|
389
|
+
for (const comp of taxCode.Components) {
|
|
390
|
+
if (comp.CalcMethod === "PerUnitPlusPercent") {
|
|
391
|
+
warning = "Inclusive back-calc is approximate: PerUnitPlusPercent component present";
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
if (comp.CalcMethod === "MaxOfPercentOrPerUnit") {
|
|
395
|
+
warning = "Inclusive back-calc is approximate: MaxOfPercentOrPerUnit component present";
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
if (comp.AppliedOn === "PostTax") {
|
|
399
|
+
warning = "Inclusive back-calc is approximate: PostTax (tax-on-tax) component present";
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let priceAfterFixedDeduction = GetNumber(inclusivePrice);
|
|
405
|
+
|
|
406
|
+
// Step 1: Subtract any PerUnit (fixed) components first
|
|
407
|
+
for (const comp of taxCode.Components) {
|
|
408
|
+
if (comp.CalcMethod === "PerUnit") {
|
|
409
|
+
const fixedTax = Multiply(GetNumber(comp.PerUnitAmt), GetNumber(qty));
|
|
410
|
+
priceAfterFixedDeduction = new Big(priceAfterFixedDeduction).minus(new Big(fixedTax)).toNumber();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Step 2: Back-calculate percentage-based components
|
|
415
|
+
// Sum all percentage rates (only Percent type, applied on NetAmt)
|
|
416
|
+
let totalPercentRate = 0;
|
|
417
|
+
for (const comp of taxCode.Components) {
|
|
418
|
+
if (comp.CalcMethod === "Percent" && comp.AppliedOn === "NetAmt") {
|
|
419
|
+
totalPercentRate = Add(totalPercentRate, GetNumber(comp.Rate));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// NetAmt = InclusivePrice / (1 + totalRate/100)
|
|
424
|
+
let netAmt: number;
|
|
425
|
+
if (totalPercentRate > 0) {
|
|
426
|
+
const divisor = new Big(1).plus(new Big(totalPercentRate).div(100));
|
|
427
|
+
netAmt = RoundAmount(new Big(priceAfterFixedDeduction).div(divisor).toNumber(), rounding);
|
|
428
|
+
} else {
|
|
429
|
+
netAmt = RoundAmount(priceAfterFixedDeduction, rounding);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return { NetAmt: netAmt, Warning: warning };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ============================================================
|
|
436
|
+
// Total Tax Amount (convenience)
|
|
437
|
+
// ============================================================
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Sums the Amt of all tax components on a line item.
|
|
441
|
+
*
|
|
442
|
+
* @param taxes - Array of ITaxComponent from a line item's Taxes[]
|
|
443
|
+
* @returns Total tax amount
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* const totalTax = SumTaxComponents(lineItem.Taxes);
|
|
447
|
+
* // CGST 450 + SGST 450 = 900
|
|
448
|
+
*/
|
|
449
|
+
export function SumTaxComponents(taxes: ITaxComponent[] | undefined | null): number {
|
|
450
|
+
if (!taxes || taxes.length === 0) return 0;
|
|
451
|
+
|
|
452
|
+
let total = 0;
|
|
453
|
+
for (const tax of taxes) {
|
|
454
|
+
total = Add(total, GetNumber(tax.Amt));
|
|
455
|
+
}
|
|
456
|
+
return total;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ============================================================
|
|
460
|
+
// TaxSummary — Document-Level Aggregation
|
|
461
|
+
// ============================================================
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Computes the document-level TaxSummary by aggregating all line items' Taxes[].
|
|
465
|
+
*
|
|
466
|
+
* This is a PURE FUNCTION — it does not read from database.
|
|
467
|
+
* Computes NetAmt per line internally from Qty/UnitPrice/Disc/RecDisc.
|
|
468
|
+
*
|
|
469
|
+
* Groups tax amounts by Code+Rate (e.g., CGST@9 and CGST@14 are separate buckets).
|
|
470
|
+
* TaxableAmt per bucket = sum of line NetAmt for lines that have that Code+Rate.
|
|
471
|
+
* TotalTaxable = sum of unique line NetAmts (not double-counted across components).
|
|
472
|
+
*
|
|
473
|
+
* Rounding behavior (via input.Rounding):
|
|
474
|
+
* - TaxComponentTotal ON: each bucket's Amt is rounded to Precision
|
|
475
|
+
* - Otherwise: Amts are at currency precision (from CalculateLineTax)
|
|
476
|
+
*
|
|
477
|
+
* @param input - All line items with Qty/UnitPrice/Disc/RecDisc and Taxes[], plus regime code and optional rounding
|
|
478
|
+
* @returns ITaxSummary — the aggregated summary (stored on the document)
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* const summary = ComputeTaxSummary({
|
|
482
|
+
* Lines: invoice.Items.map(item => ({
|
|
483
|
+
* Qty: item.Qty, UnitPrice: item.UnPr, Disc: item.Disc, RecDisc: item.RecDisc,
|
|
484
|
+
* Taxes: item.Taxes,
|
|
485
|
+
* })),
|
|
486
|
+
* RegimeCode: "IN_GST",
|
|
487
|
+
* Rounding: { Method: "Round", Precision: 0, TaxComponentTotal: true },
|
|
488
|
+
* });
|
|
489
|
+
* // Returns: {
|
|
490
|
+
* // Lines: [
|
|
491
|
+
* // { Code: "CGST", Rate: 9, TaxableAmt: 50000, Amt: 4500 },
|
|
492
|
+
* // { Code: "SGST", Rate: 9, TaxableAmt: 50000, Amt: 4500 }
|
|
493
|
+
* // ],
|
|
494
|
+
* // TotalTaxable: 50000,
|
|
495
|
+
* // TotalTax: 9000,
|
|
496
|
+
* // RegimeCode: "IN_GST"
|
|
497
|
+
* // }
|
|
498
|
+
*/
|
|
499
|
+
export function ComputeTaxSummary(input: ITaxSummaryInput): ITaxSummary {
|
|
500
|
+
const { Lines, RegimeCode, Rounding } = input;
|
|
501
|
+
// Group by Code+Rate (more granular than Code-only for rate-wise reporting)
|
|
502
|
+
const groupMap = new Map<string, {
|
|
503
|
+
Code: string;
|
|
504
|
+
Rate: number;
|
|
505
|
+
TaxableAmt: number;
|
|
506
|
+
Amt: number;
|
|
507
|
+
}>();
|
|
508
|
+
|
|
509
|
+
let totalTaxable = 0;
|
|
510
|
+
|
|
511
|
+
for (const line of Lines) {
|
|
512
|
+
if (!line.Taxes || line.Taxes.length === 0) continue;
|
|
513
|
+
|
|
514
|
+
const lineNetAmt = CalculateNetAmt(line);
|
|
515
|
+
// Track this line's taxable amount at document level (once per line, not per component)
|
|
516
|
+
totalTaxable = Add(totalTaxable, lineNetAmt);
|
|
517
|
+
|
|
518
|
+
// Track which Code+Rate buckets we've already added this line's NetAmt to
|
|
519
|
+
const bucketsTrackedForLine = new Set<string>();
|
|
520
|
+
|
|
521
|
+
for (const tax of line.Taxes) {
|
|
522
|
+
const rate = GetNumber(tax.Rate);
|
|
523
|
+
const key = `${tax.Code}|${rate}`;
|
|
524
|
+
|
|
525
|
+
if (!groupMap.has(key)) {
|
|
526
|
+
groupMap.set(key, {
|
|
527
|
+
Code: tax.Code,
|
|
528
|
+
Rate: rate,
|
|
529
|
+
TaxableAmt: 0,
|
|
530
|
+
Amt: 0,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const group = groupMap.get(key)!;
|
|
535
|
+
// Add line's NetAmt to this bucket's TaxableAmt (once per line per bucket)
|
|
536
|
+
if (!bucketsTrackedForLine.has(key)) {
|
|
537
|
+
group.TaxableAmt = Add(group.TaxableAmt, lineNetAmt);
|
|
538
|
+
bucketsTrackedForLine.add(key);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
group.Amt = Add(group.Amt, GetNumber(tax.Amt));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Build summary lines, applying TaxComponentTotal rounding if configured
|
|
546
|
+
const summaryLines: ITaxSummaryLine[] = [];
|
|
547
|
+
let totalTax = 0;
|
|
548
|
+
|
|
549
|
+
for (const [, group] of groupMap) {
|
|
550
|
+
let amt = group.Amt;
|
|
551
|
+
|
|
552
|
+
// If TaxComponentTotal rounding is active, round each bucket's Amt
|
|
553
|
+
if (Rounding?.TaxComponentTotal) {
|
|
554
|
+
amt = RoundAmount(amt, { Method: Rounding.Method, Precision: Rounding.Precision });
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
summaryLines.push({
|
|
558
|
+
Code: group.Code,
|
|
559
|
+
Rate: group.Rate,
|
|
560
|
+
TaxableAmt: group.TaxableAmt,
|
|
561
|
+
Amt: amt,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
totalTax = Add(totalTax, amt);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
Lines: summaryLines,
|
|
569
|
+
TotalTaxable: totalTaxable,
|
|
570
|
+
TotalTax: totalTax,
|
|
571
|
+
RegimeCode: RegimeCode,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// ============================================================
|
|
576
|
+
// Document Totals — Full document computation
|
|
577
|
+
// ============================================================
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Computes all document-level totals in one call: SubTotal, Discount,
|
|
581
|
+
* TaxableAmount, TaxTotal, TaxSummary, Round, and Total.
|
|
582
|
+
*
|
|
583
|
+
* This is the primary function UI/API should call at save time to produce
|
|
584
|
+
* the stored financial fields. All rounding is applied per the RoundingConfig.
|
|
585
|
+
*
|
|
586
|
+
* Flow:
|
|
587
|
+
* 1. SubTotal = sum of gross line values before discounts/tax
|
|
588
|
+
* 2. Discount = sum of line discount + prorated record discount allocations
|
|
589
|
+
* 3. TaxableAmount = sum of line NetAmts
|
|
590
|
+
* 4. TaxSummary = grouped by Code+Rate (rounded per TaxComponentTotal)
|
|
591
|
+
* 5. TaxTotal = sum of TaxSummary Amts
|
|
592
|
+
* 6. GrandTotal = TaxableAmount + TaxTotal
|
|
593
|
+
* 7. If DocTotal ON: round GrandTotal, compute Round adjustment
|
|
594
|
+
* 8. Total = GrandTotal + Round
|
|
595
|
+
*
|
|
596
|
+
* NOTE: Document-level Discount, Adjust, and Withholding are NOT handled here.
|
|
597
|
+
* If a document-level discount exists (e.g. India GST invoice discount), the
|
|
598
|
+
* caller must prorate it into line `RecDisc` values before calling this
|
|
599
|
+
* function so each line's taxable base is correct.
|
|
600
|
+
* Additional final settlement adjustments are applied after this function:
|
|
601
|
+
* FinalPayable = Total + Adjust - Withholding.Amt
|
|
602
|
+
*
|
|
603
|
+
* @param input - Line items with Qty/UnitPrice/Disc/RecDisc and Taxes[], plus RoundingConfig
|
|
604
|
+
* @returns IDocumentTotals — all stored financial fields
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* // India GST invoice with 2 lines
|
|
608
|
+
* const totals = ComputeDocumentTotals({
|
|
609
|
+
* Lines: [
|
|
610
|
+
* { Qty: 10, UnitPrice: 500,
|
|
611
|
+
* Taxes: [{ Code: "CGST", Rate: 9, Amt: 450, TaxCodeId: 1 },
|
|
612
|
+
* { Code: "SGST", Rate: 9, Amt: 450, TaxCodeId: 1 }] },
|
|
613
|
+
* { Qty: 6, UnitPrice: 500,
|
|
614
|
+
* Taxes: [{ Code: "CGST", Rate: 9, Amt: 270, TaxCodeId: 1 },
|
|
615
|
+
* { Code: "SGST", Rate: 9, Amt: 270, TaxCodeId: 1 }] },
|
|
616
|
+
* ],
|
|
617
|
+
* Rounding: { Method: "Round", Precision: 0, TaxComponentTotal: true, DocTotal: true },
|
|
618
|
+
* RegimeCode: "IN_GST",
|
|
619
|
+
* });
|
|
620
|
+
* // Returns: {
|
|
621
|
+
* // SubTotal: 8000, Discount: 0, TaxableAmount: 8000,
|
|
622
|
+
* // TaxTotal: 1440, Round: 0, Total: 9440, GrandTotal: 9440,
|
|
623
|
+
* // TaxSummary: [
|
|
624
|
+
* // { Code: "CGST", Rate: 9, TaxableAmt: 8000, Amt: 720 },
|
|
625
|
+
* // { Code: "SGST", Rate: 9, TaxableAmt: 8000, Amt: 720 },
|
|
626
|
+
* // ]
|
|
627
|
+
* // }
|
|
628
|
+
*/
|
|
629
|
+
export function ComputeDocumentTotals(input: IDocumentTotalsInput): IDocumentTotals {
|
|
630
|
+
const { Lines, Rounding, RegimeCode } = input;
|
|
631
|
+
// 1. SubTotal = sum of gross line values before discounts/tax
|
|
632
|
+
let subTotal = 0;
|
|
633
|
+
let discount = 0;
|
|
634
|
+
let taxableAmount = 0;
|
|
635
|
+
|
|
636
|
+
for (const line of Lines) {
|
|
637
|
+
subTotal = Add(subTotal, CalculateGrossAmt(line));
|
|
638
|
+
discount = Add(discount, GetNumber(line.Disc), GetNumber(line.RecDisc));
|
|
639
|
+
taxableAmount = Add(taxableAmount, CalculateNetAmt(line));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// 2. TaxSummary (handles TaxComponentTotal rounding internally)
|
|
643
|
+
const taxSummary = ComputeTaxSummary({
|
|
644
|
+
Lines,
|
|
645
|
+
RegimeCode,
|
|
646
|
+
Rounding,
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// 3. TaxTotal from the (potentially rounded) summary
|
|
650
|
+
const taxTotal = taxSummary.TotalTax;
|
|
651
|
+
|
|
652
|
+
// 4. GrandTotal before doc-level rounding
|
|
653
|
+
const grandTotal = Add(taxableAmount, taxTotal);
|
|
654
|
+
|
|
655
|
+
// 5. DocTotal rounding
|
|
656
|
+
let roundedTotal = grandTotal;
|
|
657
|
+
let roundAdj = 0;
|
|
658
|
+
|
|
659
|
+
if (Rounding.DocTotal !== false) { // default true
|
|
660
|
+
roundedTotal = RoundAmount(grandTotal, { Method: Rounding.Method, Precision: Rounding.Precision });
|
|
661
|
+
roundAdj = Subtract(roundedTotal, grandTotal);
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
SubTotal: subTotal,
|
|
665
|
+
Discount: discount,
|
|
666
|
+
TaxableAmount: taxableAmount,
|
|
667
|
+
TaxTotal: taxTotal,
|
|
668
|
+
TaxSummary: taxSummary.Lines,
|
|
669
|
+
GrandTotal: grandTotal,
|
|
670
|
+
Round: roundAdj,
|
|
671
|
+
Total: roundedTotal,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ============================================================
|
|
676
|
+
// Supply Type Detection
|
|
677
|
+
// ============================================================
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Determines Intra-State or Inter-State based on seller and buyer state codes.
|
|
681
|
+
*
|
|
682
|
+
* Used in India GST where Place of Supply determines which tax components apply.
|
|
683
|
+
*
|
|
684
|
+
* Returns "Unknown" if either state code is missing — callers MUST handle this case.
|
|
685
|
+
* UI should show a warning/prompt; API should reject or use a safe default.
|
|
686
|
+
*
|
|
687
|
+
* @param sellerStateCode - Seller's GST state code (e.g., "29" for Karnataka)
|
|
688
|
+
* @param buyerStateCode - Buyer's GST state code (e.g., "33" for Tamil Nadu)
|
|
689
|
+
* @returns "Intra" if same state, "Inter" if different states, "Unknown" if data missing
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* DetermineSupplyType("29", "29"); // "Intra" — both Karnataka
|
|
693
|
+
* DetermineSupplyType("29", "33"); // "Inter" — Karnataka to Tamil Nadu
|
|
694
|
+
* DetermineSupplyType("", "33"); // "Unknown" — seller state missing
|
|
695
|
+
*/
|
|
696
|
+
export function DetermineSupplyType(sellerStateCode: string, buyerStateCode: string): ResolvedSupplyType {
|
|
697
|
+
if (!sellerStateCode || !buyerStateCode) {
|
|
698
|
+
return "Unknown";
|
|
699
|
+
}
|
|
700
|
+
return sellerStateCode.trim() === buyerStateCode.trim() ? "Intra" : "Inter";
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Finds a TaxCode by combined rate and supply type.
|
|
705
|
+
*
|
|
706
|
+
* CombinedRate is a DISPLAY HINT only. It represents the sum of percentage-based
|
|
707
|
+
* rates and does NOT account for PerUnit or PostTax components. Use this function
|
|
708
|
+
* only for simple percentage-only tax codes (like standard India GST without cess).
|
|
709
|
+
*
|
|
710
|
+
* @param taxCodes - List of available TaxCodes
|
|
711
|
+
* @param combinedRate - The desired combined rate (e.g., 18)
|
|
712
|
+
* @param supplyType - "Intra" or "Inter"
|
|
713
|
+
* @returns The matching TaxCode, or undefined if not found
|
|
714
|
+
*
|
|
715
|
+
* @example
|
|
716
|
+
* // Find GST 18% Inter-State (simple percentage-only tax code)
|
|
717
|
+
* const interCode = FindTaxCodeByRateAndSupplyType(allTaxCodes, 18, "Inter");
|
|
718
|
+
*/
|
|
719
|
+
export function FindTaxCodeByRateAndSupplyType(
|
|
720
|
+
taxCodes: ITaxCode[],
|
|
721
|
+
combinedRate: number,
|
|
722
|
+
supplyType: "Intra" | "Inter"
|
|
723
|
+
): ITaxCode | undefined {
|
|
724
|
+
return taxCodes.find(tc =>
|
|
725
|
+
tc.IsActive &&
|
|
726
|
+
tc.CombinedRate === combinedRate &&
|
|
727
|
+
(tc.SupplyType === supplyType || tc.SupplyType === "All")
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// ============================================================
|
|
732
|
+
// Rounding Utilities
|
|
733
|
+
// ============================================================
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Rounds a number according to the specified rounding configuration.
|
|
737
|
+
*
|
|
738
|
+
* Different countries use different rounding rules:
|
|
739
|
+
* - India: standard rounding to 2 decimals
|
|
740
|
+
* - Japan: truncate (floor) to whole numbers
|
|
741
|
+
* - Some EU countries: banker's rounding
|
|
742
|
+
*/
|
|
743
|
+
export function RoundAmount(value: number, config: IRoundingConfig): number {
|
|
744
|
+
const precision = config.Precision ?? 2;
|
|
745
|
+
const big = new Big(GetNumber(value));
|
|
746
|
+
|
|
747
|
+
switch (config.Method) {
|
|
748
|
+
case "Round":
|
|
749
|
+
return Number(big.toFixed(precision, Big.roundHalfUp));
|
|
750
|
+
|
|
751
|
+
case "Floor":
|
|
752
|
+
return Number(big.toFixed(precision, Big.roundDown));
|
|
753
|
+
|
|
754
|
+
case "Ceil":
|
|
755
|
+
return Number(big.toFixed(precision, Big.roundUp));
|
|
756
|
+
|
|
757
|
+
case "BankersRound":
|
|
758
|
+
return Number(big.toFixed(precision, Big.roundHalfEven));
|
|
759
|
+
|
|
760
|
+
default:
|
|
761
|
+
return Number(big.toFixed(precision, Big.roundHalfUp));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// ============================================================
|
|
766
|
+
// Validation Utilities
|
|
767
|
+
// ============================================================
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Validates a tax identification number against the regime's format regex.
|
|
771
|
+
*
|
|
772
|
+
* @param taxId - The tax ID to validate (e.g., "29ABCDE1234F1Z5")
|
|
773
|
+
* @param formatRegex - Regex pattern from TaxRegime.Features.TaxIdFormat
|
|
774
|
+
* @returns true if valid, false if invalid
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* // Validate Indian GSTIN
|
|
778
|
+
* ValidateTaxId("29ABCDE1234F1Z5", "^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$");
|
|
779
|
+
* // Returns: true
|
|
780
|
+
*/
|
|
781
|
+
export function ValidateTaxId(taxId: string, formatRegex: string): boolean {
|
|
782
|
+
if (!taxId || !formatRegex) return false;
|
|
783
|
+
try {
|
|
784
|
+
const regex = new RegExp(formatRegex);
|
|
785
|
+
return regex.test(taxId.trim());
|
|
786
|
+
} catch {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Validates HSN/SAC code length against entity settings.
|
|
793
|
+
*
|
|
794
|
+
* @param code - The HSN or SAC code
|
|
795
|
+
* @param requiredLength - Required length from entity settings ("4", "6", "8")
|
|
796
|
+
* @returns true if valid, false if invalid
|
|
797
|
+
*
|
|
798
|
+
* @example
|
|
799
|
+
* ValidateHSNSACLength("8471", "4"); // true
|
|
800
|
+
* ValidateHSNSACLength("8471", "6"); // false — needs 6 digits
|
|
801
|
+
*/
|
|
802
|
+
export function ValidateHSNSACLength(code: string, requiredLength: string): boolean {
|
|
803
|
+
if (!code) return false;
|
|
804
|
+
const trimmed = code.trim();
|
|
805
|
+
const len = parseInt(requiredLength, 10);
|
|
806
|
+
if (isNaN(len)) return false;
|
|
807
|
+
return trimmed.length >= len;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Returns default TaxIdLabels for a given country.
|
|
812
|
+
*
|
|
813
|
+
* Used as seed data when creating TaxRegime records. The returned array is
|
|
814
|
+
* stored on TaxRegime.Features.TaxIdLabels[], which becomes the source of truth.
|
|
815
|
+
* At runtime, UI reads TaxIdLabels from TaxRegime — NOT from this function.
|
|
816
|
+
*
|
|
817
|
+
* @param country - ISO 3166-1 alpha-2 country code ("IN", "AU", "US", etc.)
|
|
818
|
+
* @returns Array of ITaxIdLabel — default tax ID definitions for the country
|
|
819
|
+
*
|
|
820
|
+
* @example
|
|
821
|
+
* // When seeding India GST regime:
|
|
822
|
+
* const labels = GetDefaultTaxIdLabels("IN");
|
|
823
|
+
* // Returns: [
|
|
824
|
+
* // { Label: "GSTIN", Primary: true, Required: false, RegexValidate: "..." },
|
|
825
|
+
* // { Label: "PAN", Primary: false, Required: true, RegexValidate: "..." },
|
|
826
|
+
* // { Label: "CIN", Primary: false, Required: false },
|
|
827
|
+
* // ]
|
|
828
|
+
*/
|
|
829
|
+
export function GetDefaultTaxIdLabels(country: string): ITaxIdLabel[] {
|
|
830
|
+
switch (country) {
|
|
831
|
+
case "IN": return [
|
|
832
|
+
{ Label: "GSTIN", Required: false, Primary: true, RegexValidate: "^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$" },
|
|
833
|
+
{ Label: "PAN", Required: true, Primary: false, RegexValidate: "^[A-Z]{5}[0-9]{4}[A-Z]{1}$" },
|
|
834
|
+
{ Label: "CIN", Required: false, Primary: false },
|
|
835
|
+
];
|
|
836
|
+
case "AU": return [
|
|
837
|
+
{ Label: "ABN", Required: true, Primary: true, RegexValidate: "^[0-9]{11}$" },
|
|
838
|
+
];
|
|
839
|
+
case "US": return [
|
|
840
|
+
{ Label: "EIN", Required: false, Primary: true, RegexValidate: "^[0-9]{2}-[0-9]{7}$" },
|
|
841
|
+
];
|
|
842
|
+
case "GB": return [
|
|
843
|
+
{ Label: "VAT No", Required: true, Primary: true, RegexValidate: "^GB[0-9]{9}$" },
|
|
844
|
+
];
|
|
845
|
+
case "AE": return [
|
|
846
|
+
{ Label: "TRN", Required: true, Primary: true, RegexValidate: "^[0-9]{15}$" },
|
|
847
|
+
];
|
|
848
|
+
case "CA": return [
|
|
849
|
+
{ Label: "BN", Required: true, Primary: true },
|
|
850
|
+
{ Label: "GST/HST No", Required: false, Primary: false },
|
|
851
|
+
];
|
|
852
|
+
default: return [
|
|
853
|
+
{ Label: "Tax ID", Required: false, Primary: true },
|
|
854
|
+
];
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Filters TaxIds[] entries to only those marked for printing.
|
|
860
|
+
*
|
|
861
|
+
* Print logic: include if `Print` is `true` or `undefined` (default = print).
|
|
862
|
+
* Exclude only when `Print` is explicitly `false`.
|
|
863
|
+
*
|
|
864
|
+
* Used by invoice/PDF templates to determine which entity and
|
|
865
|
+
* customer/vendor tax IDs to render.
|
|
866
|
+
*
|
|
867
|
+
* @param taxIds - The TaxIds[] array from Entity Settings, Customer, or Vendor
|
|
868
|
+
* @returns Filtered array of printable entries
|
|
869
|
+
*
|
|
870
|
+
* @example
|
|
871
|
+
* const entityTaxIds = [
|
|
872
|
+
* { Label: "GSTIN", Value: "29ABCDE1234F1Z5", Print: true },
|
|
873
|
+
* { Label: "PAN", Value: "ABCDE1234F", Print: false },
|
|
874
|
+
* { Label: "CIN", Value: "U12345MH2020PTC123456" }, // Print undefined = include
|
|
875
|
+
* ];
|
|
876
|
+
* GetPrintableTaxIds(entityTaxIds);
|
|
877
|
+
* // Returns: [{ Label: "GSTIN", Value: "29ABCDE1234F1Z5" }, { Label: "CIN", Value: "U12345MH2020PTC123456" }]
|
|
878
|
+
*/
|
|
879
|
+
export function GetPrintableTaxIds(taxIds: ITaxIdEntry[] | undefined): ITaxIdEntry[] {
|
|
880
|
+
if (!taxIds || taxIds.length === 0) return [];
|
|
881
|
+
return taxIds.filter(t => t.Print !== false);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ============================================================
|
|
885
|
+
// Withholding Tax — Generic TDS/TCS for all countries
|
|
886
|
+
// ============================================================
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Calculates a withholding tax entry.
|
|
890
|
+
*
|
|
891
|
+
* This is a generic function that handles both:
|
|
892
|
+
* - TDS (Type: "Deducted") — buyer deducts from payment
|
|
893
|
+
* - TCS (Type: "Collected") — seller collects from buyer
|
|
894
|
+
*
|
|
895
|
+
* The function is simple: Rate * BaseAmt = Amt.
|
|
896
|
+
* The CGST/SGST split for India is NOT done here — that's a presentation
|
|
897
|
+
* concern handled by the UI/reporting layer. The Withholding[] array stores
|
|
898
|
+
* the TOTAL withholding amount per section.
|
|
899
|
+
*
|
|
900
|
+
* @param input - Withholding calculation input
|
|
901
|
+
* @param rounding - Rounding configuration
|
|
902
|
+
* @returns IWithholding — the calculated withholding entry
|
|
903
|
+
*
|
|
904
|
+
* @example
|
|
905
|
+
* // India GST TDS — 2% on Rs 3,00,000 taxable value
|
|
906
|
+
* const tds = CalculateWithholding({
|
|
907
|
+
* Type: "Deducted",
|
|
908
|
+
* Section: "51",
|
|
909
|
+
* Rate: 2,
|
|
910
|
+
* BaseAmt: 300000,
|
|
911
|
+
* PartyLiable: "Buyer",
|
|
912
|
+
* });
|
|
913
|
+
* // Returns: { Type: "Deducted", Section: "51", Rate: 2, BaseAmt: 300000, Amt: 6000, PartyLiable: "Buyer" }
|
|
914
|
+
*
|
|
915
|
+
* @example
|
|
916
|
+
* // India GST TCS — 1% on Rs 5,00,000 net value
|
|
917
|
+
* const tcs = CalculateWithholding({
|
|
918
|
+
* Type: "Collected",
|
|
919
|
+
* Section: "52",
|
|
920
|
+
* Rate: 1,
|
|
921
|
+
* BaseAmt: 500000,
|
|
922
|
+
* PartyLiable: "Seller",
|
|
923
|
+
* });
|
|
924
|
+
* // Returns: { Type: "Collected", Section: "52", Rate: 1, BaseAmt: 500000, Amt: 5000, PartyLiable: "Seller" }
|
|
925
|
+
*
|
|
926
|
+
* @example
|
|
927
|
+
* // US Federal Withholding — 24% backup withholding
|
|
928
|
+
* const wht = CalculateWithholding({
|
|
929
|
+
* Type: "Deducted",
|
|
930
|
+
* Section: "FITW",
|
|
931
|
+
* Rate: 24,
|
|
932
|
+
* BaseAmt: 10000,
|
|
933
|
+
* PartyLiable: "Buyer",
|
|
934
|
+
* });
|
|
935
|
+
* // Returns: { Type: "Deducted", Section: "FITW", Rate: 24, BaseAmt: 10000, Amt: 2400, PartyLiable: "Buyer" }
|
|
936
|
+
*/
|
|
937
|
+
export function CalculateWithholding(
|
|
938
|
+
input: IWithholdingCalcInput,
|
|
939
|
+
rounding: IRoundingConfig = { Method: "Round", Precision: 2 }
|
|
940
|
+
): IWithholding {
|
|
941
|
+
const baseAmt = GetNumber(input.BaseAmt);
|
|
942
|
+
const rate = GetNumber(input.Rate);
|
|
943
|
+
|
|
944
|
+
const amt = RoundAmount(
|
|
945
|
+
Multiply(baseAmt, Divide(rate, 100)),
|
|
946
|
+
rounding
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
return {
|
|
950
|
+
Type: input.Type,
|
|
951
|
+
Section: input.Section,
|
|
952
|
+
Rate: rate,
|
|
953
|
+
BaseAmt: baseAmt,
|
|
954
|
+
Amt: amt,
|
|
955
|
+
PartyLiable: input.PartyLiable,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// ============================================================
|
|
960
|
+
// Migration Helpers — Flat Fields to Taxes[]
|
|
961
|
+
// ============================================================
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Converts legacy flat CGST/SGST/IGST fields to a Taxes[] array.
|
|
965
|
+
*
|
|
966
|
+
* Used during migration (Phase 3) and during the dual-write transition
|
|
967
|
+
* to generate Taxes[] from existing data.
|
|
968
|
+
*
|
|
969
|
+
* @param lineItem - Any line item object with flat CGST, SGST, IGST fields
|
|
970
|
+
* @param taxCode - The TaxCode that was used (for rate and metadata lookup)
|
|
971
|
+
* @returns Array of ITaxComponent
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* const taxes = ConvertFlatToTaxes(
|
|
975
|
+
* { CGST: 450, SGST: 450, IGST: 0, TCode: 106, NetAmt: 5000 },
|
|
976
|
+
* gst18IntraTaxCode
|
|
977
|
+
* );
|
|
978
|
+
*/
|
|
979
|
+
export function ConvertFlatToTaxes(
|
|
980
|
+
lineItem: { CGST?: number; SGST?: number; IGST?: number; TCode?: number; NetAmt?: number },
|
|
981
|
+
taxCode?: ITaxCode | null,
|
|
982
|
+
): ITaxComponent[] {
|
|
983
|
+
const taxes: ITaxComponent[] = [];
|
|
984
|
+
const netAmt = GetNumber(lineItem.NetAmt);
|
|
985
|
+
const taxCodeId = GetNumber(lineItem.TCode);
|
|
986
|
+
|
|
987
|
+
const cgst = GetNumber(lineItem.CGST);
|
|
988
|
+
const sgst = GetNumber(lineItem.SGST);
|
|
989
|
+
const igst = GetNumber(lineItem.IGST);
|
|
990
|
+
|
|
991
|
+
if (cgst > 0) {
|
|
992
|
+
const rate = taxCode?.Components?.find(c => c.Code === "CGST")?.Rate
|
|
993
|
+
?? (netAmt > 0 ? Multiply(Divide(cgst, netAmt), 100) : 0);
|
|
994
|
+
taxes.push({
|
|
995
|
+
Code: "CGST",
|
|
996
|
+
Rate: RoundAmount(rate, { Method: "Round", Precision: 4 }),
|
|
997
|
+
Amt: cgst,
|
|
998
|
+
TaxCodeId: taxCodeId,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (sgst > 0) {
|
|
1003
|
+
const rate = taxCode?.Components?.find(c => c.Code === "SGST")?.Rate
|
|
1004
|
+
?? (netAmt > 0 ? Multiply(Divide(sgst, netAmt), 100) : 0);
|
|
1005
|
+
taxes.push({
|
|
1006
|
+
Code: "SGST",
|
|
1007
|
+
Rate: RoundAmount(rate, { Method: "Round", Precision: 4 }),
|
|
1008
|
+
Amt: sgst,
|
|
1009
|
+
TaxCodeId: taxCodeId,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (igst > 0) {
|
|
1014
|
+
const rate = taxCode?.Components?.find(c => c.Code === "IGST")?.Rate
|
|
1015
|
+
?? (netAmt > 0 ? Multiply(Divide(igst, netAmt), 100) : 0);
|
|
1016
|
+
taxes.push({
|
|
1017
|
+
Code: "IGST",
|
|
1018
|
+
Rate: RoundAmount(rate, { Method: "Round", Precision: 4 }),
|
|
1019
|
+
Amt: igst,
|
|
1020
|
+
TaxCodeId: taxCodeId,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return taxes;
|
|
1025
|
+
}
|