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.
Files changed (212) hide show
  1. package/README.md +1 -0
  2. package/dist/accounting/invoice/invoice-pdf/invoice-pdf.service.js +18 -45
  3. package/dist/accounting/invoice/reports/excel/analysis-excel.service.js +17 -7
  4. package/dist/accounting/invoice/reports/excel/category-wise-item-excel.service.js +17 -7
  5. package/dist/accounting/invoice/reports/excel/cust-wise-sales-details.js +17 -7
  6. package/dist/accounting/invoice/reports/excel/cust-wise-sales-summary-excel.service.js +17 -7
  7. package/dist/accounting/invoice/reports/excel/invoice-wise-excel.service.js +17 -7
  8. package/dist/accounting/invoice/reports/excel/item-wise-doctor-sale-excel.service.js +17 -7
  9. package/dist/accounting/invoice/reports/excel/items-wise-sales-excel.service.js +17 -7
  10. package/dist/accounting/invoice/reports/excel/manf-wise-sales-excel.service.js +17 -7
  11. package/dist/accounting/invoice/reports/excel/operator-wise-details.js +17 -7
  12. package/dist/accounting/invoice/reports/excel/operator-wise-summary.js +17 -7
  13. package/dist/accounting/invoice/reports/excel/sa-wise-labor-sales-excel.service.js +17 -7
  14. package/dist/accounting/invoice/reports/excel/sa-wise-part-sales-excel.service.js +17 -7
  15. package/dist/accounting/invoice/reports/excel/sale-summary-excel.service.js +17 -7
  16. package/dist/accounting/invoice/reports/excel/sales-by-service-summary-excel.service.js +17 -7
  17. package/dist/accounting/invoice/reports/excel/scheduled-drug-summary-excel.service.js +17 -7
  18. package/dist/accounting/invoice/reports/excel/scheduled-drugs-excel.service.js +17 -7
  19. package/dist/accounting/invoice/unified-invoice-pdf.service.d.ts +1 -1
  20. package/dist/accounting/payment-receive/reports/excel/cust-balance-excel.service.js +17 -7
  21. package/dist/accounting/payment-receive/reports/excel/payment-receive-excel.service.js +17 -7
  22. package/dist/common/reports/excel/product-without-owner-excel.service.js +17 -7
  23. package/dist/gst/excel/GSTR-RO-excel.service.js +17 -7
  24. package/dist/gst/excel/GSTR1-excel.service.js +17 -7
  25. package/dist/gst/excel/GSTR2-excel.service.js +17 -7
  26. package/dist/gst/excel/hsn-summary.js +17 -7
  27. package/dist/gst/excel/tally-sales-import.service.js +17 -7
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +6 -6
  30. package/dist/inventory/items/reports/excel/expiring-drugs-excel.service.js +17 -7
  31. package/dist/inventory/items/reports/excel/item-wise-mout-details.js +17 -7
  32. package/dist/inventory/items/reports/excel/item-wise-mout-summary.js +17 -7
  33. package/dist/inventory/items/reports/excel/spares-issue-excel.service.js +17 -7
  34. package/dist/inventory/items/reports/excel/stock-excel.service.js +17 -7
  35. package/dist/purchases/payment-made/reports/excel/payments-made-excel.service.js +17 -7
  36. package/dist/purchases/payment-made/reports/excel/ven-balance-excel.service.js +17 -7
  37. package/dist/services/reports/excel/insurance-expire-excel.service.js +17 -7
  38. package/dist/services/reports/excel/next-service-date-excel.service.js +17 -7
  39. package/dist/services/reports/excel/repair-orders-excel.service.js +17 -7
  40. package/dist/shared/table-section/pdf-table.header.d.ts +1 -1
  41. package/dist/shared/table-section/pdf-table.header.js +14 -2
  42. package/dist/shared/table-section/pdf-table.section.js +6 -13
  43. package/dist/{accounting/invoice/invoice-pdf.service.js → shared/transactions-pdf.service.js} +15 -15
  44. package/package.json +1 -1
  45. package/src/accounting/counter-sales/auto-sales-receipt-pdf.service.ts +569 -0
  46. package/src/accounting/counter-sales/pos-receipt-pdf.ts +577 -0
  47. package/src/accounting/counter-sales/sales-receipt-pdf.service.ts +628 -0
  48. package/src/accounting/counter-sales/sales-receipt-print.service.ts +506 -0
  49. package/src/accounting/credit-note/cn-print.service.ts +264 -0
  50. package/src/accounting/credit-note/credit-note-pdf.service.ts +602 -0
  51. package/src/accounting/credit-note/credit-note-totals.service.ts +424 -0
  52. package/src/accounting/debit-note/debit-note-pdf.service.ts +681 -0
  53. package/src/accounting/debit-note/debit-note-print.service.ts +276 -0
  54. package/src/accounting/debit-note/debit-note-totals.service.ts +361 -0
  55. package/src/accounting/invoice/hc-inv-pdf.service.ts +880 -0
  56. package/src/accounting/invoice/inv-pdf.service.ts +812 -0
  57. package/src/accounting/invoice/inv-print.service.ts +532 -0
  58. package/src/accounting/invoice/invoice-landscape-pdf.service.ts +947 -0
  59. package/src/accounting/invoice/invoice-letterhead-pdf.service.ts +813 -0
  60. package/src/accounting/invoice/invoice-pdf/invoice-pdf.service.ts +359 -0
  61. package/src/accounting/invoice/invoice-portrait-pdf.ts +972 -0
  62. package/src/accounting/invoice/invoice-print.service.ts +2906 -0
  63. package/src/accounting/invoice/invoice-total.service.ts +834 -0
  64. package/src/accounting/invoice/reports/excel/analysis-excel.service.ts +291 -0
  65. package/src/accounting/invoice/reports/excel/category-wise-item-excel.service.ts +267 -0
  66. package/src/accounting/invoice/reports/excel/cust-wise-sales-details.ts +321 -0
  67. package/src/accounting/invoice/reports/excel/cust-wise-sales-summary-excel.service.ts +300 -0
  68. package/src/accounting/invoice/reports/excel/invoice-wise-excel.service.ts +859 -0
  69. package/src/accounting/invoice/reports/excel/item-wise-doctor-sale-excel.service.ts +255 -0
  70. package/src/accounting/invoice/reports/excel/items-wise-sales-excel.service.ts +312 -0
  71. package/src/accounting/invoice/reports/excel/manf-wise-sales-excel.service.ts +273 -0
  72. package/src/accounting/invoice/reports/excel/operator-wise-details.ts +258 -0
  73. package/src/accounting/invoice/reports/excel/operator-wise-summary.ts +259 -0
  74. package/src/accounting/invoice/reports/excel/sa-wise-labor-sales-excel.service.ts +230 -0
  75. package/src/accounting/invoice/reports/excel/sa-wise-part-sales-excel.service.ts +231 -0
  76. package/src/accounting/invoice/reports/excel/sale-summary-excel.service.ts +326 -0
  77. package/src/accounting/invoice/reports/excel/sales-by-service-details-excel.service.ts +0 -0
  78. package/src/accounting/invoice/reports/excel/sales-by-service-summary-excel.service.ts +432 -0
  79. package/src/accounting/invoice/reports/excel/scheduled-drug-summary-excel.service.ts +373 -0
  80. package/src/accounting/invoice/reports/excel/scheduled-drugs-excel.service.ts +372 -0
  81. package/src/accounting/invoice/reports/pdf/analysis-pdf.service.ts +113 -0
  82. package/src/accounting/invoice/reports/pdf/category-wise-item-pdf.service.ts +107 -0
  83. package/src/accounting/invoice/reports/pdf/cust-wise-sales-details-pdf.service.ts +125 -0
  84. package/src/accounting/invoice/reports/pdf/cust-wise-sales-summary-pdf.service.ts +119 -0
  85. package/src/accounting/invoice/reports/pdf/item-wise-doctor-sale-pdf.service.ts +180 -0
  86. package/src/accounting/invoice/reports/pdf/item-wise-sales-pdf.service.ts +193 -0
  87. package/src/accounting/invoice/reports/pdf/manf-wise-sales-pdf.service.ts +188 -0
  88. package/src/accounting/invoice/reports/pdf/operator-wise-details-pdf.service.ts +118 -0
  89. package/src/accounting/invoice/reports/pdf/operator-wise-summary-pdf.ts +116 -0
  90. package/src/accounting/invoice/reports/pdf/sales-by-service-pdf.service.ts +132 -0
  91. package/src/accounting/invoice/reports/pdf/scheduled-drug-pdf.service.ts +191 -0
  92. package/src/accounting/invoice/reports/pdf/scheduled-drug-summary-pdf.service.ts +202 -0
  93. package/src/accounting/invoice/shared-inv-pdf.service.ts +787 -0
  94. package/src/accounting/invoice/unified-invoice-pdf.service.ts +937 -0
  95. package/src/accounting/payment-receive/payment-pdf.service.ts +410 -0
  96. package/src/accounting/payment-receive/payment-receipt-pdf/receipt-pdf.service.ts +470 -0
  97. package/src/accounting/payment-receive/receipt-print.service.ts +71 -0
  98. package/src/accounting/payment-receive/reports/excel/cust-balance-excel.service.ts +298 -0
  99. package/src/accounting/payment-receive/reports/excel/payment-receive-excel.service.ts +221 -0
  100. package/src/accounting/payment-receive/reports/pdf/customer-balances-pdf.service.ts +182 -0
  101. package/src/accounting/payment-receive/reports/pdf/payment-report-pdf.service.ts +116 -0
  102. package/src/aggregation/aggregation.ts +58 -0
  103. package/src/appointments/appointments/appointment-total.service.ts +298 -0
  104. package/src/appointments/consultations/consultation-fee-receipt.service.ts +407 -0
  105. package/src/appointments/consultations/consultation-full-pdf.service.ts +238 -0
  106. package/src/appointments/consultations/consultation-letterhead-pdf.service.ts +430 -0
  107. package/src/appointments/consultations/consultation-pdf.service.ts +417 -0
  108. package/src/common/reports/excel/product-without-owner-excel.service.ts +308 -0
  109. package/src/common/reports/pdf/product-without-owner-pdf.service.ts +146 -0
  110. package/src/enums/cache-enums.ts +33 -0
  111. package/src/enums/code-enums.ts +291 -0
  112. package/src/enums/country-enums.ts +9 -0
  113. package/src/enums/enums.ts +364 -0
  114. package/src/enums/industry-enums.ts +26 -0
  115. package/src/enums/treatment-enums.ts +9 -0
  116. package/src/gst/excel/GSTR-RO-excel.service.ts +926 -0
  117. package/src/gst/excel/GSTR1-excel.service.ts +313 -0
  118. package/src/gst/excel/GSTR2-excel.service.ts +314 -0
  119. package/src/gst/excel/hsn-summary.ts +314 -0
  120. package/src/gst/excel/tally-sales-import.service.ts +767 -0
  121. package/src/gst/pdf/hsn-summary-pdf.ts +176 -0
  122. package/src/index.ts +194 -0
  123. package/src/inventory/items/adjustment-pdf.service.ts +177 -0
  124. package/src/inventory/items/issue-parts-pdf.service.ts +795 -0
  125. package/src/inventory/items/item-bar-code-label-pdf.ts +194 -0
  126. package/src/inventory/items/item-detais-pdf.ts +141 -0
  127. package/src/inventory/items/item-price-for-pricelist.ts +368 -0
  128. package/src/inventory/items/reports/excel/expiring-drugs-excel.service.ts +290 -0
  129. package/src/inventory/items/reports/excel/item-wise-mout-details.ts +284 -0
  130. package/src/inventory/items/reports/excel/item-wise-mout-summary.ts +279 -0
  131. package/src/inventory/items/reports/excel/spares-issue-excel.service.ts +494 -0
  132. package/src/inventory/items/reports/excel/stock-excel.service.ts +319 -0
  133. package/src/inventory/items/reports/pdf/expiring-drugs-pdf.service.ts +172 -0
  134. package/src/inventory/items/reports/pdf/item-wise-mout-details-pdf.ts +122 -0
  135. package/src/inventory/items/reports/pdf/item-wise-mout-summary-pdf.ts +115 -0
  136. package/src/inventory/items/reports/pdf/reorder-point-pdf.service.ts +163 -0
  137. package/src/inventory/material-out/mout-pdf.service.ts +545 -0
  138. package/src/inventory/transfer-order/transfer-order-pdf.service.ts +154 -0
  139. package/src/purchases/bills/bill-pdf/bill-pdf.service.ts +211 -0
  140. package/src/purchases/bills/bill-pdf.service.ts +21 -0
  141. package/src/purchases/payment-made/reports/excel/payments-made-excel.service.ts +313 -0
  142. package/src/purchases/payment-made/reports/excel/ven-balance-excel.service.ts +307 -0
  143. package/src/purchases/payment-made/reports/pdf/vendor-balances-pdf.service.ts +114 -0
  144. package/src/purchases/purchase-order/po-totals.service.ts +343 -0
  145. package/src/purchases/purchase-order/purchase-order-pdf.service.ts +1016 -0
  146. package/src/purchases/purchase-order/purchase-order-print.service.ts +279 -0
  147. package/src/purchases/purchase-order/purchase-order-totals.service.ts +637 -0
  148. package/src/purchases/vendor-credit-note/vendor-credit-note-pdf.service.ts +1055 -0
  149. package/src/purchases/vendor-credit-note/vendor-credit-note-print.service.ts +145 -0
  150. package/src/purchases/vendor-credit-note/vendor-credit-note-totals.service.ts +399 -0
  151. package/src/purchases/vendor-debit-note/vendor-debit-note-pdf.service.ts +582 -0
  152. package/src/purchases/vendor-debit-note/vendor-debit-note-print.service.ts +295 -0
  153. package/src/purchases/vendor-debit-note/vendor-debit-note-totals.service.ts +377 -0
  154. package/src/sales/delivery-challan/dc-landscape-pdf.service.ts +922 -0
  155. package/src/sales/delivery-challan/dc-landscape-without-price-pdf.service.ts +869 -0
  156. package/src/sales/delivery-challan/dc-without-price-pdf.service.ts +505 -0
  157. package/src/sales/delivery-challan/delivery-challan-pdf.service.ts +461 -0
  158. package/src/sales/delivery-challan/delivery-challan-print.service.ts +229 -0
  159. package/src/sales/delivery-challan/delivery-challan-totals.ts +466 -0
  160. package/src/sales/sales/equipment-design-pdf.service.ts +153 -0
  161. package/src/sales/sales/pack-ship-pdf.service.ts +128 -0
  162. package/src/sales/sales/pack-ship-print.service.ts +198 -0
  163. package/src/sales/sales/sales-pdf.service.ts +658 -0
  164. package/src/sales/sales/sales-print.service.ts +376 -0
  165. package/src/sales/sales/sales-totals.service.ts +500 -0
  166. package/src/sales-receive/sales-receive-pdf.service.ts +602 -0
  167. package/src/sales-receive/sales-receive-print.service.ts +242 -0
  168. package/src/sales-receive/sales-receive-totals.service.ts +651 -0
  169. package/src/services/checklist-pdf.ts +151 -0
  170. package/src/services/checklists-pdf.ts +133 -0
  171. package/src/services/est.print-service.ts +1155 -0
  172. package/src/services/reports/excel/insurance-expire-excel.service.ts +292 -0
  173. package/src/services/reports/excel/next-service-date-excel.service.ts +317 -0
  174. package/src/services/reports/excel/repair-orders-excel.service.ts +249 -0
  175. package/src/services/reports/pdf/insurance-expire-pdf.service.ts +115 -0
  176. package/src/services/reports/pdf/next-service-date-pdf.service.ts +198 -0
  177. package/src/services/reports/pdf/repair-orders-pdf.service.ts +184 -0
  178. package/src/services/ro-pdf.service.ts +1917 -0
  179. package/src/services/ro-print-service.ts +881 -0
  180. package/src/services/ro-totals.service.ts +1314 -0
  181. package/src/services/separate-wo-print.service.ts +396 -0
  182. package/src/services/service-history-pdf.service.ts +145 -0
  183. package/src/services/service-price-for-pricelist.ts +649 -0
  184. package/src/services/technician-pdf.service.ts +234 -0
  185. package/src/services/technician-print.service.ts +95 -0
  186. package/src/shared/header-footer-section/pdf-header-footer.section.ts +519 -0
  187. package/src/shared/header-footer-section/pdf-shared.utils.ts +46 -0
  188. package/src/shared/math-operations.ts +208 -0
  189. package/src/shared/party-details-section/pdf-party-details.section.ts +602 -0
  190. package/src/shared/shared-pdf.service.ts +3042 -0
  191. package/src/shared/shared-print.service.ts +879 -0
  192. package/src/shared/table-section/pdf-table.config.ts +8 -0
  193. package/src/shared/table-section/pdf-table.header.ts +396 -0
  194. package/src/shared/table-section/pdf-table.row.ts +248 -0
  195. package/src/shared/table-section/pdf-table.section.ts +447 -0
  196. package/src/shared/totals-section/pdf-totals.section.ts +921 -0
  197. package/src/shared/transactions-pdf.service.ts +191 -0
  198. package/src/shared/util.ts +101 -0
  199. package/src/tasks/meetings/meeting-pdf.ts +410 -0
  200. package/src/tasks/tasks/task-pdf.service.ts +238 -0
  201. package/src/tasks/tasks/task-reports-pdf.service.ts +313 -0
  202. package/src/tax/index.ts +86 -0
  203. package/src/tax/tax-calculator.ts +1025 -0
  204. package/src/tax/tax.types.ts +535 -0
  205. package/src/transaction-calculations/discounts-distribution.ts +343 -0
  206. package/src/transaction-calculations/index.ts +3 -0
  207. package/src/transaction-calculations/total-calculation.ts +443 -0
  208. package/src/transaction-calculations/transaction-calculation-engine.ts +903 -0
  209. package/src/utils/my-date.ts +111 -0
  210. package/src/utils/tr-utils.ts +104 -0
  211. package/tsconfig.json +2 -2
  212. /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
+ }