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,903 @@
1
+ import { Add, Divide, Multiply, Subtract } from '../shared/math-operations';
2
+
3
+ export interface ReverseCalculationInput {
4
+ total: number;
5
+ qty: number;
6
+ discount: number;
7
+ overallDiscount: number;
8
+ totalTaxRate: number;
9
+ isIndependentTax: boolean;
10
+ }
11
+
12
+ export interface TaxTotalOptions {
13
+ includeTaxInTotal: boolean;
14
+ }
15
+
16
+ /**
17
+ * TransactionCalculationEngine
18
+ *
19
+ * Reusable pure-calculation module for ERP-style transactions:
20
+ * Invoice | Bill | Sales Order | Delivery Challan | Purchase Order
21
+ *
22
+ * All methods are static and side-effect-free.
23
+ * Callers are responsible for writing results back to Reactive Form controls
24
+ * using { emitEvent: false } to prevent infinite loops.
25
+ *
26
+ * Monetary values are not rounded by this engine.
27
+ *
28
+ * Item fields used:
29
+ * qty, unpr, unAmt, disc, perc, recDisc, tax, total, NoDisc
30
+ *
31
+ * Ops fields used:
32
+ * amount, recDisc
33
+ *
34
+ * Transaction-level discount fields:
35
+ * PDisc / PPerc → items only
36
+ * LDisc / LPerc → ops only
37
+ * Disc / Prec → items + ops proportionally
38
+ */
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Internal helpers
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function toNum(value: any): number {
45
+ const n = Number(value);
46
+ return Number.isFinite(n) ? n : 0;
47
+ }
48
+
49
+ function isNullOrEmpty(value: any): boolean {
50
+ return (
51
+ value === null || value === undefined || value === '' || value === 'null'
52
+ );
53
+ }
54
+
55
+ function getTaxesArray(value: any): any[] {
56
+ const taxes = value?.Taxes ?? value?.taxes ?? value;
57
+ return Array.isArray(taxes) ? taxes : [];
58
+ }
59
+
60
+ function sumTaxAmountFromTaxes(taxes: any): number {
61
+ const rows = getTaxesArray(taxes);
62
+ return rows.reduce(
63
+ (sum, row) => Add(sum, toNum(row?.Amt ?? row?.amt)),
64
+ 0,
65
+ );
66
+ }
67
+
68
+ function sumTaxRateFromTaxes(taxes: any): number {
69
+ const rows = getTaxesArray(taxes);
70
+ return rows.reduce(
71
+ (sum, row) => Add(sum, toNum(row?.Rate ?? row?.rate)),
72
+ 0,
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Distribute a flat discount amount across a set of rows proportionally
78
+ * by each row's `baseKey` value. Returns an array of share amounts aligned
79
+ * with the input rows.
80
+ *
81
+ * The last row absorbs any remainder so the sum always equals discAmt.
82
+ */
83
+ function distributeProportionally(
84
+ rows: any[],
85
+ discAmt: number,
86
+ baseKey: string,
87
+ ): number[] {
88
+ const total = rows.reduce((sum, row) => Add(sum, toNum(row[baseKey])), 0);
89
+ if (total === 0) {
90
+ return rows.map(() => 0);
91
+ }
92
+
93
+ const shares: number[] = [];
94
+ let allocated = 0;
95
+
96
+ rows.forEach((row, index) => {
97
+ if (index === rows.length - 1) {
98
+ // Last row: give the remainder to avoid floating-point drift
99
+ shares.push(Subtract(discAmt, allocated));
100
+ } else {
101
+ const share = Multiply(Divide(toNum(row[baseKey]), total), discAmt);
102
+ shares.push(share);
103
+ allocated = Add(allocated, share);
104
+ }
105
+ });
106
+
107
+ return shares;
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Public engine
112
+ // ---------------------------------------------------------------------------
113
+
114
+ export class TransactionCalculationEngine {
115
+ // -------------------------------------------------------------------------
116
+ // Taxes helpers (Amt-based + Rate-based)
117
+ // -------------------------------------------------------------------------
118
+ /**
119
+ * Summarizes a taxes array.
120
+ *
121
+ * Supported row shapes (case-tolerant):
122
+ * - `{ Amt: number }` (preferred for totals)
123
+ * - `{ Rate: number }` (preferred for reverse calc if Amt not present)
124
+ */
125
+ static getTaxesSummary(taxes: any): { taxAmount: number; taxRate: number } {
126
+ return {
127
+ taxAmount: sumTaxAmountFromTaxes(taxes),
128
+ taxRate: sumTaxRateFromTaxes(taxes),
129
+ };
130
+ }
131
+
132
+ static sumTaxAmountFromRows(rows: unknown): number {
133
+ if (!Array.isArray(rows)) {
134
+ return 0;
135
+ }
136
+
137
+ return rows.reduce((sum: number, row: any) => {
138
+ return Add(sum, toNum(row?.Amt));
139
+ }, 0);
140
+ }
141
+
142
+ static calculateTotalFromTaxRows(input: {
143
+ baseAmount: number;
144
+ taxRows: unknown;
145
+ options: TaxTotalOptions;
146
+ }): number {
147
+ const baseAmount = toNum(input.baseAmount);
148
+ const taxAmount = TransactionCalculationEngine.sumTaxAmountFromRows(
149
+ input.taxRows,
150
+ );
151
+ return input.options.includeTaxInTotal
152
+ ? Add(baseAmount, taxAmount)
153
+ : baseAmount;
154
+ }
155
+
156
+ static calculateSubtotalFromTotalUsingTaxes(input: {
157
+ total: number;
158
+ taxRows: unknown;
159
+ includeTaxInTotal: boolean;
160
+ }): number {
161
+ const total = toNum(input.total);
162
+ if (!input.includeTaxInTotal) {
163
+ return total;
164
+ }
165
+
166
+ const taxSummary = TransactionCalculationEngine.getTaxesSummary(
167
+ input.taxRows,
168
+ );
169
+ if (taxSummary.taxRate) {
170
+ const divisor = Add(1, Divide(taxSummary.taxRate, 100));
171
+ return divisor === 0 ? total : Divide(total, divisor);
172
+ }
173
+
174
+ return Subtract(total, taxSummary.taxAmount);
175
+ }
176
+
177
+ static reverseCalculateAmountAndUnitPriceFromTotal(input: {
178
+ qty: number;
179
+ total: number;
180
+ disc: number;
181
+ recDisc: number;
182
+ noDisc: boolean;
183
+ taxRows: unknown;
184
+ includeTaxInTotal: boolean;
185
+ }): { amount: number; unitPrice: number } {
186
+ const qty = toNum(input.qty) || 1;
187
+ const subtotal =
188
+ TransactionCalculationEngine.calculateSubtotalFromTotalUsingTaxes({
189
+ total: input.total,
190
+ taxRows: input.taxRows,
191
+ includeTaxInTotal: input.includeTaxInTotal,
192
+ });
193
+
194
+ const disc = toNum(input.disc);
195
+ const recDisc = toNum(input.recDisc);
196
+ const amount = input.noDisc
197
+ ? subtotal
198
+ : Add(subtotal, Add(disc, recDisc));
199
+ const unitPrice = Divide(amount, qty);
200
+
201
+ return { amount, unitPrice };
202
+ }
203
+
204
+ // Shared reverse-calculation formula. Components only need to supply their own field mapping.
205
+ static calculatePriceFromTotal(input: ReverseCalculationInput): number {
206
+ const qty = toNum(input.qty) || 1;
207
+ const total = toNum(input.total);
208
+ const discount = toNum(input.discount);
209
+ const overallDiscount = toNum(input.overallDiscount);
210
+ const totalTaxRate = toNum(input.totalTaxRate);
211
+ const taxableBase = input.isIndependentTax
212
+ ? Divide(total, Add(1, Divide(totalTaxRate, 100)))
213
+ : total;
214
+
215
+ return Divide(Add(Add(taxableBase, discount), overallDiscount), qty);
216
+ }
217
+
218
+ /**
219
+ * Returns the sum of all component rates for a tax code id.
220
+ *
221
+ * Safe for missing inputs:
222
+ * - empty TaxCodes
223
+ * - TCode not found
224
+ * - empty Components
225
+ */
226
+ static getGSTRateSumByTaxCode(TCode: any, TaxCodes: any[]): number {
227
+ if (isNullOrEmpty(TCode) || !Array.isArray(TaxCodes)) {
228
+ return 0;
229
+ }
230
+
231
+ const taxCode = TaxCodes.find((code: any) => code?._id === TCode);
232
+ const components = Array.isArray(taxCode?.Components)
233
+ ? taxCode.Components
234
+ : [];
235
+
236
+ return components.reduce((sum:number, comp: any) => Add(sum, toNum(comp?.Rate)), 0);
237
+ }
238
+
239
+ // -------------------------------------------------------------------------
240
+ // 1. calculateItemAmount
241
+ // -------------------------------------------------------------------------
242
+ /**
243
+ * Calculates the raw base amount for an item:
244
+ * unAmt = qty * unpr
245
+ *
246
+ * Returns a shallow copy of the item with `unAmt` set.
247
+ */
248
+ static calculateItemAmount(item: any): any {
249
+ const qty = toNum(item.qty);
250
+ const unpr = toNum(item.unpr);
251
+ return { ...item, unAmt: Multiply(qty, unpr) };
252
+ }
253
+
254
+ // -------------------------------------------------------------------------
255
+ // 2. calculateItemDiscount
256
+ // -------------------------------------------------------------------------
257
+ /**
258
+ * Calculates the item-level (record-level) discount applied to a single item.
259
+ *
260
+ * Rules:
261
+ * - If `NoDisc` is true → recDisc = 0
262
+ * - Else if `perc` exists → recDisc = qty * unpr * (perc / 100)
263
+ * - Else → recDisc = disc (flat amount)
264
+ *
265
+ * Returns a shallow copy of the item with `recDisc` set.
266
+ */
267
+ static calculateItemDiscount(item: any): any {
268
+ if (item.NoDisc) {
269
+ return { ...item, recDisc: 0 };
270
+ }
271
+
272
+ const qty = toNum(item.qty);
273
+ const unpr = toNum(item.unpr);
274
+ const perc = toNum(item.perc);
275
+ const disc = toNum(item.disc);
276
+
277
+ const recDisc =
278
+ !isNullOrEmpty(item.perc) && perc !== 0
279
+ ? Multiply(Multiply(qty, unpr), Divide(perc, 100))
280
+ : disc;
281
+
282
+ return { ...item, recDisc };
283
+ }
284
+
285
+ // -------------------------------------------------------------------------
286
+ // 3. calculateItemForwardTotal
287
+ // -------------------------------------------------------------------------
288
+ /**
289
+ * Calculates subtotal, tax amount, and total for an item using forward logic
290
+ * (price/qty → total).
291
+ *
292
+ * subtotal = (qty * unpr) − recDisc
293
+ * taxAmount = subtotal * (tax / 100)
294
+ * total = subtotal + taxAmount
295
+ *
296
+ * Returns a shallow copy of the item with `subtotal`, `taxAmount`, and
297
+ * `total` set.
298
+ */
299
+ static calculateItemForwardTotal(item: any): any {
300
+ const qty = toNum(item.qty);
301
+ const unpr = toNum(item.unpr);
302
+ const recDisc = toNum(item.recDisc);
303
+ const tax = toNum(item.tax);
304
+
305
+ const subtotal = Subtract(Multiply(qty, unpr), recDisc);
306
+ const taxAmount = Divide(Multiply(subtotal, tax), 100);
307
+ const total = Add(subtotal, taxAmount);
308
+
309
+ return { ...item, subtotal, taxAmount, total };
310
+ }
311
+
312
+ // -------------------------------------------------------------------------
313
+ // 3b. calculateItemForwardTotalUsingTaxes
314
+ // -------------------------------------------------------------------------
315
+ /**
316
+ * Calculates subtotal, taxAmount, and total for an item using a `Taxes` array.
317
+ *
318
+ * Rules:
319
+ * - subtotal = (qty * unpr) − recDisc
320
+ * - taxAmount = Σ Taxes[].Amt (if missing/0, falls back to Σ Taxes[].Rate %)
321
+ * - total = subtotal + taxAmount (when includeTaxInTotal = true)
322
+ *
323
+ * Notes:
324
+ * - If you want a "pre-tax total" line, pass includeTaxInTotal = false.
325
+ */
326
+ static calculateItemForwardTotalUsingTaxes(
327
+ item: any,
328
+ options?: { includeTaxInTotal?: boolean },
329
+ ): any {
330
+ const includeTaxInTotal = options?.includeTaxInTotal !== false;
331
+
332
+ const qty = toNum(item.qty);
333
+ const unpr = toNum(item.unpr);
334
+ const recDisc = toNum(item.recDisc);
335
+
336
+ const subtotal = Subtract(Multiply(qty, unpr), recDisc);
337
+
338
+ const taxes = item?.Taxes ?? item?.taxes;
339
+ const taxSummary = TransactionCalculationEngine.getTaxesSummary(taxes);
340
+ const taxAmount =
341
+ taxSummary.taxAmount !== 0
342
+ ? taxSummary.taxAmount
343
+ : Divide(Multiply(subtotal, taxSummary.taxRate), 100);
344
+
345
+ const total = Add(subtotal, includeTaxInTotal ? taxAmount : 0);
346
+
347
+ return { ...item, subtotal, taxAmount, total };
348
+ }
349
+
350
+ // -------------------------------------------------------------------------
351
+ // 4. calculateReverseFromTotal
352
+ // -------------------------------------------------------------------------
353
+ /**
354
+ * Reverse-calculates `unpr` from a manually entered `total`.
355
+ *
356
+ * Steps:
357
+ * subtotal = total / (1 + tax / 100) [strips tax]
358
+ * baseAmt = subtotal + recDisc [adds back discount]
359
+ * unpr = baseAmt / qty [per-unit price]
360
+ *
361
+ * Guards:
362
+ * - If `qty` is 0 the calculation is skipped and the item is returned unchanged.
363
+ * - If `tax` is 0 the divisor reduces to 1 (safe).
364
+ *
365
+ * Returns a shallow copy of the item with `unpr` (and derived `unAmt`) updated.
366
+ */
367
+ static calculateReverseFromTotal(item: any): any {
368
+ const qty = toNum(item.qty);
369
+ const total = toNum(item.total);
370
+ const recDisc = toNum(item.recDisc);
371
+ const tax = toNum(item.tax);
372
+
373
+ if (qty === 0) {
374
+ // Cannot reverse without a quantity – return item unchanged
375
+ return { ...item };
376
+ }
377
+
378
+ const divisor = Add(1, Divide(tax, 100));
379
+ const subtotal = Divide(total, divisor === 0 ? 1 : divisor);
380
+ const baseAmt = Add(subtotal, recDisc);
381
+ const unpr = Divide(baseAmt, qty);
382
+ const unAmt = Multiply(qty, unpr);
383
+
384
+ return { ...item, unpr, unAmt };
385
+ }
386
+
387
+ // -------------------------------------------------------------------------
388
+ // 4b. calculateReverseFromTotalUsingDiscPerc
389
+ // -------------------------------------------------------------------------
390
+ /**
391
+ * Reverse-calculates `unpr` from a manually entered `total`, supporting the
392
+ * same `disc`/`perc` semantics as `calculateItemDiscount`.
393
+ *
394
+ * This is intentionally tolerant of existing calling conventions:
395
+ * - `item.extraDisc` can be provided to represent an additional discount
396
+ * amount that should be added on top of the row discount (e.g. overall /
397
+ * record-level discount allocation). It is ignored when `NoDisc` is true.
398
+ * - When `tax` is 0, tax stripping becomes a no-op.
399
+ *
400
+ * Returns a shallow copy of the item with `unpr`, `unAmt`, and `recDisc` set.
401
+ */
402
+ static calculateReverseFromTotalUsingDiscPerc(item: any): any {
403
+ const qty = toNum(item.qty);
404
+ const total = toNum(item.total);
405
+ const tax = toNum(item.tax);
406
+ const disc = toNum(item.disc);
407
+ const perc = toNum(item.perc);
408
+ const extraDisc = toNum(item.extraDisc);
409
+
410
+ if (qty === 0) {
411
+ return { ...item };
412
+ }
413
+
414
+ const divisor = Add(1, Divide(tax, 100));
415
+ const subtotal = Divide(total, divisor === 0 ? 1 : divisor);
416
+
417
+ if (item.NoDisc) {
418
+ const unpr = Divide(subtotal, qty);
419
+ const unAmt = Multiply(qty, unpr);
420
+ return { ...item, unpr, unAmt, recDisc: 0 };
421
+ }
422
+
423
+ // Percentage path: solve for unpr algebraically because recDisc depends on unpr
424
+ if (!isNullOrEmpty(item.perc) && perc !== 0) {
425
+ const discountRate = Divide(perc, 100);
426
+ const priceFactor = Subtract(1, discountRate);
427
+ if (priceFactor === 0) {
428
+ return { ...item };
429
+ }
430
+
431
+ const unpr = Divide(Add(subtotal, extraDisc), Multiply(qty, priceFactor));
432
+ const unAmt = Multiply(qty, unpr);
433
+ const rowDisc = Multiply(unAmt, discountRate);
434
+ const recDisc = Add(rowDisc, extraDisc);
435
+
436
+ return { ...item, unpr, unAmt, recDisc };
437
+ }
438
+
439
+ // Flat amount path
440
+ const recDisc = Add(disc, extraDisc);
441
+ const unpr = Divide(Add(subtotal, recDisc), qty);
442
+ const unAmt = Multiply(qty, unpr);
443
+
444
+ return { ...item, unpr, unAmt, recDisc };
445
+ }
446
+
447
+ // -------------------------------------------------------------------------
448
+ // 4c. calculateReverseFromTotalUsingDiscPercAndTaxes
449
+ // -------------------------------------------------------------------------
450
+ /**
451
+ * Reverse-calculates `unpr` from a manually entered `total` using a `Taxes` array.
452
+ *
453
+ * When taxes contain `Amt` rows, the reverse "strips tax" by subtracting the
454
+ * summed tax amount. If tax `Amt` is 0/missing, it falls back to stripping by
455
+ * summed tax `Rate` (same as `calculateReverseFromTotalUsingDiscPerc`).
456
+ *
457
+ * Inputs:
458
+ * - qty, total, disc, perc, extraDisc, NoDisc
459
+ * - Taxes: array of `{ Amt?, Rate? }`
460
+ */
461
+ static calculateReverseFromTotalUsingDiscPercAndTaxes(
462
+ item: any,
463
+ options?: { includeTaxInTotal?: boolean },
464
+ ): any {
465
+ const includeTaxInTotal = options?.includeTaxInTotal !== false;
466
+
467
+ const qty = toNum(item.qty);
468
+ const total = toNum(item.total);
469
+ const disc = toNum(item.disc);
470
+ const perc = toNum(item.perc);
471
+ const extraDisc = toNum(item.extraDisc);
472
+
473
+ if (qty === 0) {
474
+ return { ...item };
475
+ }
476
+
477
+ const taxSummary = TransactionCalculationEngine.getTaxesSummary(
478
+ item?.Taxes ?? item?.taxes,
479
+ );
480
+
481
+ // If total doesn't include tax, treat it as "already stripped".
482
+ const subtotal = includeTaxInTotal
483
+ ? taxSummary.taxAmount !== 0
484
+ ? Subtract(total, taxSummary.taxAmount)
485
+ : (() => {
486
+ const divisor = Add(1, Divide(taxSummary.taxRate, 100));
487
+ return Divide(total, divisor === 0 ? 1 : divisor);
488
+ })()
489
+ : total;
490
+
491
+ if (item.NoDisc) {
492
+ const unpr = Divide(subtotal, qty);
493
+ const unAmt = Multiply(qty, unpr);
494
+ return { ...item, unpr, unAmt, recDisc: 0 };
495
+ }
496
+
497
+ // Percentage path: solve for unpr algebraically because row discount depends on unpr.
498
+ if (!isNullOrEmpty(item.perc) && perc !== 0) {
499
+ const discountRate = Divide(perc, 100);
500
+ const priceFactor = Subtract(1, discountRate);
501
+ if (priceFactor === 0) {
502
+ return { ...item };
503
+ }
504
+
505
+ const unpr = Divide(Add(subtotal, extraDisc), Multiply(qty, priceFactor));
506
+ const unAmt = Multiply(qty, unpr);
507
+ const rowDisc = Multiply(unAmt, discountRate);
508
+ const recDisc = Add(rowDisc, extraDisc);
509
+
510
+ return { ...item, unpr, unAmt, recDisc };
511
+ }
512
+
513
+ const recDisc = Add(disc, extraDisc);
514
+ const unpr = Divide(Add(subtotal, recDisc), qty);
515
+ const unAmt = Multiply(qty, unpr);
516
+
517
+ return { ...item, unpr, unAmt, recDisc };
518
+ }
519
+
520
+ // -------------------------------------------------------------------------
521
+ // 5. applyPDiscountToItems
522
+ // -------------------------------------------------------------------------
523
+ /**
524
+ * Applies an item-only discount (`PDisc` / `PPerc`) to an array of items.
525
+ *
526
+ * Rules:
527
+ * - Items with `NoDisc = true` are skipped.
528
+ * - If `PPerc` is provided: each item's share = its netAmt * (PPerc / 100)
529
+ * - Otherwise: `PDisc` is distributed proportionally by each item's `unAmt`.
530
+ *
531
+ * The `recDisc` on each item is **added to** any existing item-level discount
532
+ * so that both record-level and overall PDisc coexist correctly.
533
+ *
534
+ * Returns a new array of updated item objects.
535
+ */
536
+ static applyPDiscountToItems(items: any[], PDisc: any, PPerc: any): any[] {
537
+ if (!Array.isArray(items) || items.length === 0) {
538
+ return items ?? [];
539
+ }
540
+
541
+ const discountable = items.filter((i) => !i.NoDisc);
542
+
543
+ // Percentage path
544
+ if (!isNullOrEmpty(PPerc) && toNum(PPerc) !== 0) {
545
+ const pperc = toNum(PPerc);
546
+ return items.map((item) => {
547
+ if (item.NoDisc) return { ...item };
548
+ const netAmt = Subtract(toNum(item.unAmt), toNum(item.recDisc));
549
+ const share = Divide(Multiply(netAmt, pperc), 100);
550
+ return { ...item, recDisc: Add(toNum(item.recDisc), share) };
551
+ });
552
+ }
553
+
554
+ // Flat-amount proportional path
555
+ const pdisc = toNum(PDisc);
556
+ if (pdisc === 0 || discountable.length === 0) {
557
+ return items.map((i) => ({ ...i }));
558
+ }
559
+
560
+ const shares = distributeProportionally(discountable, pdisc, 'unAmt');
561
+ let shareIdx = 0;
562
+
563
+ return items.map((item) => {
564
+ if (item.NoDisc) return { ...item };
565
+ const share = shares[shareIdx++] ?? 0;
566
+ return { ...item, recDisc: Add(toNum(item.recDisc), share) };
567
+ });
568
+ }
569
+
570
+ // -------------------------------------------------------------------------
571
+ // 6. applyLDiscountToOps
572
+ // -------------------------------------------------------------------------
573
+ /**
574
+ * Applies an ops-only discount (`LDisc` / `LPerc`) to an array of ops.
575
+ *
576
+ * Rules:
577
+ * - If `LPerc` is provided: each op's share = its `amount` * (LPerc / 100)
578
+ * - Otherwise: `LDisc` is distributed proportionally by each op's `amount`.
579
+ *
580
+ * Returns a new array of updated ops objects.
581
+ */
582
+ static applyLDiscountToOps(ops: any[], LDisc: any, LPerc: any): any[] {
583
+ if (!Array.isArray(ops) || ops.length === 0) {
584
+ return ops ?? [];
585
+ }
586
+
587
+ // Percentage path
588
+ if (!isNullOrEmpty(LPerc) && toNum(LPerc) !== 0) {
589
+ const lperc = toNum(LPerc);
590
+ return ops.map((op) => {
591
+ const share = Divide(Multiply(toNum(op.amount), lperc), 100);
592
+ return { ...op, recDisc: Add(toNum(op.recDisc), share) };
593
+ });
594
+ }
595
+
596
+ // Flat-amount proportional path
597
+ const ldisc = toNum(LDisc);
598
+ if (ldisc === 0) {
599
+ return ops.map((o) => ({ ...o }));
600
+ }
601
+
602
+ const shares = distributeProportionally(ops, ldisc, 'amount');
603
+ return ops.map((op, i) => ({
604
+ ...op,
605
+ recDisc: Add(toNum(op.recDisc), shares[i] ?? 0),
606
+ }));
607
+ }
608
+
609
+ // -------------------------------------------------------------------------
610
+ // 7. applyCommonDiscount
611
+ // -------------------------------------------------------------------------
612
+ /**
613
+ * Applies a cross-line discount (`Disc` / `Prec`) proportionally across
614
+ * both items and ops.
615
+ *
616
+ * Rules:
617
+ * - Items with `NoDisc = true` are excluded from the base but returned unchanged.
618
+ * - If `Prec` is provided: share = row's base * (Prec / 100)
619
+ * - Otherwise: `Disc` is distributed proportionally across the combined base
620
+ * (items by `unAmt`, ops by `amount`).
621
+ *
622
+ * Returns `{ items, ops }` with updated `recDisc` values.
623
+ */
624
+ static applyCommonDiscount(
625
+ items: any[],
626
+ ops: any[],
627
+ Disc: any,
628
+ Prec: any,
629
+ ): { items: any[]; ops: any[] } {
630
+ const safeItems = Array.isArray(items) ? items : [];
631
+ const safeOps = Array.isArray(ops) ? ops : [];
632
+
633
+ const discountableItems = safeItems.filter((i) => !i.NoDisc);
634
+
635
+ // Percentage path
636
+ if (!isNullOrEmpty(Prec) && toNum(Prec) !== 0) {
637
+ const prec = toNum(Prec);
638
+
639
+ const updatedItems = safeItems.map((item) => {
640
+ if (item.NoDisc) return { ...item };
641
+ // Apply common % discount on the net base after any prior discounts
642
+ // (item-level discount + PDisc), so discounts stack sequentially.
643
+ const base = Math.max(
644
+ 0,
645
+ Subtract(toNum(item.unAmt), toNum(item.recDisc)),
646
+ );
647
+ const share = Divide(Multiply(base, prec), 100);
648
+ console.log('Disc share', { id: item.id, base, share });
649
+ return { ...item, recDisc: Add(toNum(item.recDisc), share) };
650
+ });
651
+
652
+ const updatedOps = safeOps.map((op) => {
653
+ // Apply common % discount on the net base after LDisc.
654
+ const base = Math.max(
655
+ 0,
656
+ Subtract(toNum(op.amount), toNum(op.recDisc)),
657
+ );
658
+ const share = Divide(Multiply(base, prec), 100);
659
+ return { ...op, recDisc: Add(toNum(op.recDisc), share) };
660
+ });
661
+
662
+ return { items: updatedItems, ops: updatedOps };
663
+ }
664
+
665
+ // Flat-amount proportional path
666
+ const disc = toNum(Disc);
667
+ if (disc === 0) {
668
+ return {
669
+ items: safeItems.map((i) => ({ ...i })),
670
+ ops: safeOps.map((o) => ({ ...o })),
671
+ };
672
+ }
673
+
674
+ // Build a unified list for proportional distribution (no-disc items excluded)
675
+ const rows: any[] = [
676
+ ...discountableItems.map((i) => ({
677
+ _key: 'item',
678
+ _ref: i,
679
+ base: toNum(i.unAmt),
680
+ })),
681
+ ...safeOps.map((o) => ({ _key: 'op', _ref: o, base: toNum(o.amount) })),
682
+ ];
683
+
684
+ const totalBase = rows.reduce((sum, r) => Add(sum, r.base), 0);
685
+ if (totalBase === 0) {
686
+ return {
687
+ items: safeItems.map((i) => ({ ...i })),
688
+ ops: safeOps.map((o) => ({ ...o })),
689
+ };
690
+ }
691
+
692
+ // Assign shares
693
+ let allocated = 0;
694
+ rows.forEach((row, index) => {
695
+ let share: number;
696
+ if (index === rows.length - 1) {
697
+ share = Subtract(disc, allocated);
698
+ } else {
699
+ share = Multiply(Divide(row.base, totalBase), disc);
700
+ allocated = Add(allocated, share);
701
+ }
702
+ row._share = share;
703
+ });
704
+
705
+ // Build result maps
706
+ const itemShareMap = new Map<any, number>();
707
+ const opShareMap = new Map<any, number>();
708
+
709
+ rows.forEach((row) => {
710
+ if (row._key === 'item') itemShareMap.set(row._ref, row._share);
711
+ else opShareMap.set(row._ref, row._share);
712
+ });
713
+
714
+ const updatedItems = safeItems.map((item) => {
715
+ if (item.NoDisc) return { ...item };
716
+ const share = itemShareMap.get(item) ?? 0;
717
+ return { ...item, recDisc: Add(toNum(item.recDisc), share) };
718
+ });
719
+
720
+ const updatedOps = safeOps.map((op) => {
721
+ const share = opShareMap.get(op) ?? 0;
722
+ return { ...op, recDisc: Add(toNum(op.recDisc), share) };
723
+ });
724
+
725
+ return { items: updatedItems, ops: updatedOps };
726
+ }
727
+
728
+ // -------------------------------------------------------------------------
729
+ // 8. distributeRecordDiscount
730
+ // -------------------------------------------------------------------------
731
+ /**
732
+ * Orchestration method that applies all transaction-level discounts in the
733
+ * correct order:
734
+ *
735
+ * 1. applyPDiscountToItems (PDisc / PPerc)
736
+ * 2. applyLDiscountToOps (LDisc / LPerc)
737
+ * 3. applyCommonDiscount (Disc / Prec)
738
+ *
739
+ * The `recDisc` on each line starts fresh from its item-level discount
740
+ * (already computed by `calculateItemDiscount`) before overall discounts
741
+ * are stacked on top.
742
+ *
743
+ * Returns `{ items, ops }`.
744
+ */
745
+ static distributeRecordDiscount(
746
+ items: any[],
747
+ ops: any[],
748
+ transaction: any,
749
+ ): { items: any[]; ops: any[] } {
750
+ const { PDisc, PPerc, LDisc, LPerc, Disc, Prec } = transaction ?? {};
751
+
752
+ let updatedItems: any[] = Array.isArray(items)
753
+ ? items.map((i) => ({ ...i }))
754
+ : [];
755
+ let updatedOps: any[] = Array.isArray(ops)
756
+ ? ops.map((o) => ({ ...o }))
757
+ : [];
758
+
759
+ // Step 1 – items-only discount
760
+ updatedItems = TransactionCalculationEngine.applyPDiscountToItems(
761
+ updatedItems,
762
+ PDisc,
763
+ PPerc,
764
+ );
765
+
766
+ // Step 2 – ops-only discount
767
+ updatedOps = TransactionCalculationEngine.applyLDiscountToOps(
768
+ updatedOps,
769
+ LDisc,
770
+ LPerc,
771
+ );
772
+
773
+ // Step 3 – common discount across both
774
+ const result = TransactionCalculationEngine.applyCommonDiscount(
775
+ updatedItems,
776
+ updatedOps,
777
+ Disc,
778
+ Prec,
779
+ );
780
+
781
+ return { items: result.items, ops: result.ops };
782
+ }
783
+
784
+ // -------------------------------------------------------------------------
785
+ // 9. recalculateTransaction
786
+ // -------------------------------------------------------------------------
787
+ /**
788
+ * Master recalculation method.
789
+ *
790
+ * Call whenever any value in the transaction changes:
791
+ * qty | unpr | disc | perc | tax | PDisc | PPerc | LDisc | LPerc | Disc | Prec
792
+ * or when items / ops are added or removed.
793
+ *
794
+ * Processing pipeline:
795
+ * For each item:
796
+ * a. calculateItemAmount → unAmt
797
+ * b. calculateItemDiscount → item-level recDisc
798
+ * → distributeRecordDiscount → stacks overall discounts onto recDisc
799
+ * For each item:
800
+ * c. calculateItemForwardTotal → subtotal, taxAmount, total
801
+ * For each op:
802
+ * d. recalculate op total → amount − recDisc
803
+ * → computeTransactionTotals → subTotal, taxTotal, grandTotal
804
+ *
805
+ * Returns a new transaction object (shallow-copied) with updated
806
+ * `Items`, `Ops`, and summary totals (`SubTotal`, `TaxTotal`, `Total`).
807
+ *
808
+ * The original transaction object is never mutated.
809
+ *
810
+ * ⚠️ When writing results back to Reactive Form controls always use
811
+ * `{ emitEvent: false }` to prevent triggering another recalculation.
812
+ */
813
+ static recalculateTransaction(transaction: any): any {
814
+ if (!transaction) return transaction;
815
+
816
+ const rawItems: any[] = Array.isArray(transaction.Items)
817
+ ? transaction.Items
818
+ : [];
819
+ const rawOps: any[] = Array.isArray(transaction.Ops) ? transaction.Ops : [];
820
+
821
+ // ── Step A & B: item-level amounts and item-level discounts ──────────────
822
+ let items = rawItems
823
+ .map((item) => TransactionCalculationEngine.calculateItemAmount(item))
824
+ .map((item) => TransactionCalculationEngine.calculateItemDiscount(item));
825
+
826
+ // Ops: ensure `amount` exists before distribution (carry existing value)
827
+ let ops = rawOps.map((op) => ({ ...op }));
828
+
829
+ // ── Step C: distribute transaction-level discounts ────────────────────────
830
+ const distributed = TransactionCalculationEngine.distributeRecordDiscount(
831
+ items,
832
+ ops,
833
+ transaction,
834
+ );
835
+ items = distributed.items;
836
+ ops = distributed.ops;
837
+
838
+ // ── Step D: forward totals per item ──────────────────────────────────────
839
+ items = items.map((item) =>
840
+ TransactionCalculationEngine.calculateItemForwardTotal(item),
841
+ );
842
+
843
+ // ── Step E: op totals (amount already known; subtract recDisc) ───────────
844
+ ops = ops.map((op) => ({
845
+ ...op,
846
+ total: Subtract(toNum(op.amount), toNum(op.recDisc)),
847
+ }));
848
+
849
+ // ── Step F: transaction-level summary ────────────────────────────────────
850
+ const totals = TransactionCalculationEngine.computeTransactionTotals(
851
+ items,
852
+ ops,
853
+ );
854
+
855
+ return {
856
+ ...transaction,
857
+ Items: items,
858
+ Ops: ops,
859
+ SubTotal: totals.subTotal,
860
+ TaxTotal: totals.taxTotal,
861
+ Total: totals.grandTotal,
862
+ };
863
+ }
864
+
865
+ // -------------------------------------------------------------------------
866
+ // computeTransactionTotals (utility – exposed for standalone use)
867
+ // -------------------------------------------------------------------------
868
+ /**
869
+ * Computes summary totals from already-calculated items and ops.
870
+ *
871
+ * subTotal = Σ item.subtotal + Σ op.total
872
+ * taxTotal = Σ item.taxAmount
873
+ * grandTotal = subTotal + taxTotal
874
+ *
875
+ * Returns `{ subTotal, taxTotal, grandTotal }`.
876
+ */
877
+ static computeTransactionTotals(
878
+ items: any[],
879
+ ops: any[],
880
+ ): { subTotal: number; taxTotal: number; grandTotal: number } {
881
+ const safeItems = Array.isArray(items) ? items : [];
882
+ const safeOps = Array.isArray(ops) ? ops : [];
883
+
884
+ const itemSubTotal = safeItems.reduce(
885
+ (sum, i) => Add(sum, toNum(i.subtotal)),
886
+ 0,
887
+ );
888
+ const taxTotal = safeItems.reduce(
889
+ (sum, i) => Add(sum, toNum(i.taxAmount)),
890
+ 0,
891
+ );
892
+ const opsTotal = safeOps.reduce((sum, o) => Add(sum, toNum(o.total)), 0);
893
+
894
+ const subTotal = Add(itemSubTotal, opsTotal);
895
+ const grandTotal = Add(subTotal, taxTotal);
896
+
897
+ return {
898
+ subTotal,
899
+ taxTotal,
900
+ grandTotal,
901
+ };
902
+ }
903
+ }