softseti-sale-calculator-library 3.7.5 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softseti-sale-calculator-library",
3
- "version": "3.7.5",
3
+ "version": "4.0.0",
4
4
  "description": "Sales calculation engine by Softseti",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -28,5 +28,8 @@
28
28
  "jest-environment-jsdom": "^29.7.0",
29
29
  "jest-localstorage-mock": "^2.4.26",
30
30
  "ts-jest": "^29.4.0"
31
+ },
32
+ "dependencies": {
33
+ "@gorules/zen-engine": "^0.51.5"
31
34
  }
32
35
  }
@@ -4,7 +4,7 @@ const {
4
4
  calculatePreviousRefundsTotal,
5
5
  calculateMaxRefundableAmount,
6
6
  findProductInSale
7
- } = require('./helpers');
7
+ } = require('../utils/helpers');
8
8
 
9
9
  class RefundCalculator {
10
10
  static calculateRefund(sale, returnItems, options, currentMemoId) {
@@ -0,0 +1,73 @@
1
+ // src/rules/RuleEngine.js
2
+ const { ZenEngine } = require('@gorules/zen-engine');
3
+
4
+ class RuleEngine {
5
+ constructor() {
6
+ this.engine = new ZenEngine();
7
+ this.ruleCache = new Map();
8
+ }
9
+
10
+ /**
11
+ * Evaluates a business rule with given context
12
+ * @param {Object} ruleContent - GoRules JSON rule content
13
+ * @param {Object} context - Execution context
14
+ * @returns {Promise<Object>} Rule evaluation result
15
+ */
16
+ async evaluateRule(ruleContent, context) {
17
+ try {
18
+
19
+ const decision = this.engine.createDecision(ruleContent);
20
+ const result = await decision.safeEvaluate(context);
21
+
22
+ return result;
23
+
24
+ } catch (error) {
25
+ console.error('Rule evaluation error:', error);
26
+ throw new Error(`Rule evaluation failed: ${error.message}`);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Executes multiple rules for a trigger event
32
+ * @param {Array} rules - Business rules to execute
33
+ * @param {Object} context - Execution context
34
+ * @returns {Promise<Array>} Array of rule execution results
35
+ */
36
+ async executeRules(rules, context) {
37
+ const results = [];
38
+
39
+ for (const rule of rules) {
40
+ try {
41
+ const result = await this.evaluateRule(rule.rule_content, context);
42
+ results.push(
43
+ result
44
+ );
45
+ } catch (error) {
46
+
47
+ results.push({
48
+ ruleId: rule.id,
49
+ success: false,
50
+ error: error.message
51
+ });
52
+ }
53
+ }
54
+
55
+ return results;
56
+ }
57
+
58
+ /**
59
+ * Validates rule structure
60
+ * @param {Object} ruleContent - Rule content to validate
61
+ * @returns {boolean} True if rule is valid
62
+ */
63
+ validateRule(ruleContent) {
64
+ if (!ruleContent || typeof ruleContent !== 'object') {
65
+ return false;
66
+ }
67
+
68
+ const requiredProperties = ['nodes', 'edges', 'contentType'];
69
+ return requiredProperties.every(prop => ruleContent.hasOwnProperty(prop));
70
+ }
71
+ }
72
+
73
+ module.exports = RuleEngine;
@@ -1,3 +1,9 @@
1
+ const RuleEngine = require('./rule-engine');
2
+ const RuleManager = require('../rules/RuleManager');
3
+
4
+ let ruleEngine = null;
5
+ let ruleManager = null;
6
+
1
7
  let products = {};
2
8
  let productTaxes = {};
3
9
  let taxes = {};
@@ -6,10 +12,13 @@ let settings = {};
6
12
  let exchangeRates = {};
7
13
  let currencyConverterRates = {};
8
14
  let currencies = {};
15
+ let businessRules = {};
9
16
  let decimalPlaces = 2;
10
17
  let roundingFactor = 100;
11
18
  let useRounding = false;
12
19
 
20
+ let preprocessedSale = {};
21
+
13
22
  /**
14
23
  * Sales Calculation Engine (V1)
15
24
  * @author softseti
@@ -20,10 +29,6 @@ let useRounding = false;
20
29
  * - Deal/discount allocations
21
30
  */
22
31
 
23
- // ==============================================
24
- // INITIALIZATION
25
- // ==============================================
26
-
27
32
  /**
28
33
  * Initializes the calculator with indexed data
29
34
  * @param {Object} params - Data collections:
@@ -44,11 +49,41 @@ function init(params) {
44
49
  exchangeRates = params.exchange_rates || {};
45
50
  currencyConverterRates = params.currency_converter_rates || {};
46
51
  currencies = params.currencies || {};
52
+ businessRules = params.businessRules || {};
47
53
 
48
54
  decimalPlaces = params.decimal_places !== undefined ? params.decimal_places : 2;
49
55
  roundingFactor = Math.pow(10, decimalPlaces);
50
56
 
51
57
  useRounding = params.use_rounding !== undefined ? params.use_rounding : false;
58
+
59
+ // Start engine
60
+ initializeRuleSystem(businessRules);
61
+
62
+ }
63
+
64
+ // ==============================================
65
+ // Rule engine
66
+ // ==============================================
67
+
68
+ function initializeRuleSystem(businessRules) {
69
+ try {
70
+ ruleEngine = new RuleEngine();
71
+ ruleManager = new RuleManager();
72
+
73
+ if (businessRules && Object.keys(businessRules).length > 0) {
74
+
75
+ ruleManager.loadRules(businessRules);
76
+ console.log('Sistema de reglas inicializado correctamente');
77
+
78
+ } else {
79
+ console.warn('No se encontraron reglas de negocio para cargar');
80
+ }
81
+ } catch (error) {
82
+ console.error('Error al inicializar el sistema de reglas:', error);
83
+ // Fallback: continuar sin reglas
84
+ ruleEngine = null;
85
+ ruleManager = null;
86
+ }
52
87
  }
53
88
 
54
89
  // ==============================================
@@ -71,18 +106,20 @@ function validateSaleStructure(sale) {
71
106
  // CORE CALCULATION ENGINE (V2.1 SCOPE)
72
107
  // ==============================================
73
108
 
74
- function calcTotal(sale) {
109
+ async function calcTotal(sale) {
75
110
  validateSaleStructure(sale);
76
111
 
77
- return roundCurrency(
78
- sale.dwSaleProducts.reduce((total, product) => {
79
- const productSum = calcDwSaleProductSum(product, sale);
80
- const discount = calcDwSaleProductDiscount(product, sale);
81
- const netAmount = roundCurrency(productSum - discount);
82
- const taxes = getApplicableProductTaxes(product, netAmount, sale);
83
- return total + netAmount + taxes;
84
- }, 0)
85
- );
112
+ let total = 0;
113
+
114
+ for (const product of sale.dwSaleProducts) {
115
+ const productSum = calcDwSaleProductSum(product, sale);
116
+ const discount = calcDwSaleProductDiscount(product, sale);
117
+ const netAmount = roundCurrency(productSum - discount);
118
+ const taxes = await getApplicableProductTaxes(product, netAmount, sale);
119
+ total += netAmount + taxes;
120
+ }
121
+
122
+ return roundCurrency(total);
86
123
  }
87
124
 
88
125
  function calcSubtotal(sale) {
@@ -102,7 +139,7 @@ function calcSubtotal(sale) {
102
139
  * @param {Object} params
103
140
  * - sale:
104
141
  */
105
- function preprocessSale(sale) {
142
+ async function preprocessSale(sale) {
106
143
 
107
144
  const saleCurrency = currencies[sale.currency_id];
108
145
 
@@ -125,36 +162,40 @@ function preprocessSale(sale) {
125
162
  }
126
163
 
127
164
  if (sale.payments) {
128
- sale.payments = sale.payments.map(payment => {
165
+ const processedPayments = [];
166
+ for (const payment of sale.payments) {
129
167
  const originalCurrency = currencies[payment.currency_id];
130
168
 
131
169
  // If payment already has calculated fields, use them
132
170
  if (payment.amount && payment.original_amount) {
133
- return {
171
+ processedPayments.push({
134
172
  ...payment,
135
173
  original_currency_id: payment.currency_id,
136
174
  original_currency_iso: originalCurrency?.iso,
137
175
  currency_id: sale.currency_id,
138
176
  currency_iso: sale.currency_iso,
139
- change_amount: Math.max(0, payment.gived_amount - payment.amount)
140
- };
141
- }
142
-
143
- // Otherwise calculate from basic payment data
144
- return {
145
- ...payment,
146
- original_currency_id: payment.currency_id,
147
- original_currency_iso: originalCurrency?.iso,
148
- currency_id: sale.currency_id,
149
- currency_iso: sale.currency_iso,
150
- original_gived_amount: payment.gived_amount || payment.amount,
151
- ...calculatePaymentDetails({
177
+ change_amount: 0
178
+ });
179
+ } else {
180
+ // Otherwise calculate from basic payment data
181
+ const details = await calculatePaymentDetails({
152
182
  ...payment,
153
183
  original_gived_amount: payment.gived_amount || payment.amount,
154
184
  original_currency_iso: originalCurrency?.iso
155
- }, sale)
156
- };
157
- });
185
+ }, sale);
186
+
187
+ processedPayments.push({
188
+ ...payment,
189
+ original_currency_id: payment.currency_id,
190
+ original_currency_iso: originalCurrency?.iso,
191
+ currency_id: sale.currency_id,
192
+ currency_iso: sale.currency_iso,
193
+ original_gived_amount: payment.gived_amount || payment.amount,
194
+ ...details
195
+ });
196
+ }
197
+ }
198
+ sale.payments = processedPayments;
158
199
  }
159
200
 
160
201
  if (sale.dwSaleProducts) {
@@ -183,6 +224,8 @@ function preprocessSale(sale) {
183
224
  }));
184
225
  });
185
226
  }
227
+ // Set data for preprocessedSale
228
+ preprocessedSale = sale;
186
229
 
187
230
  return sale;
188
231
  }
@@ -270,18 +313,93 @@ function sumProductQuantity(products, productId) {
270
313
  .reduce((sum, p) => sum + p.quantity, 0);
271
314
  }
272
315
 
273
- function getApplicableProductTaxes(saleProduct, productSum, sale) {
316
+ async function getApplicableProductTaxes(saleProduct, productSum, sale) {
274
317
  // GET Taxes for specific products
275
318
  const specificTaxes = getProductSpecificTaxes(saleProduct, sale);
276
319
  // GET General Taxes
277
320
  const generalTaxes = getGeneralTaxes(sale);
278
321
 
279
- const totalTax = [...specificTaxes, ...generalTaxes].reduce(
280
- (total, tax) => {
281
- const taxAmount = productSum * (tax.rate / 100);
282
- return total + roundCurrency(taxAmount);
283
- }, 0
284
- );
322
+ // Combination for taxes
323
+ const taxes = [...specificTaxes, ...generalTaxes];
324
+
325
+ // Get rules for taxes
326
+ const convertedRuleTaxes = ruleManager.getRulesForTrigger('calculate_taxes') || [];
327
+
328
+ // If no rules or engine, calculate normally
329
+ if (convertedRuleTaxes.length === 0 || !ruleEngine) {
330
+ const totalTax = taxes.reduce(
331
+ (total, tax) => {
332
+ const taxAmount = productSum * (tax.rate / 100);
333
+ return total + roundCurrency(taxAmount);
334
+ }, 0
335
+ );
336
+ return roundCurrency(totalTax);
337
+ }
338
+
339
+ // Process each tax with rules
340
+ let totalTax = 0;
341
+
342
+ for (const tax of taxes) {
343
+ let adjustedRate = tax.rate;
344
+ let taxAmount = 0;
345
+
346
+ // Create context for each individual tax
347
+ const context = {
348
+ sale: {
349
+ ...preprocessedSale,
350
+ subtotal: preprocessedSale.subtotal || calcSubtotal(sale),
351
+ customers: preprocessedSale.customers || sale.customers || []
352
+ },
353
+ tax: {
354
+ ...tax,
355
+ tax_id: tax.id,
356
+ dw_product_id: tax.product_id || null,
357
+ sale_id: null,
358
+ amount: 0,
359
+ branch_id: sale.branch_id,
360
+ company_id: sale.company_id
361
+ }
362
+ };
363
+
364
+ try {
365
+ // Execute rules for this specific tax
366
+ const ruleResults = await ruleEngine.executeRules(convertedRuleTaxes, context);
367
+
368
+ // Find successful rule result
369
+ const appliedRule = ruleResults.find(result => result.success && result.data);
370
+
371
+ if (appliedRule && appliedRule.data) {
372
+ const resultData = appliedRule.data;
373
+
374
+ if (resultData.result && resultData.result.tax) {
375
+ const taxResult = resultData.result.tax;
376
+
377
+ if (taxResult.rate !== undefined) {
378
+ adjustedRate = parseFloat(taxResult.rate);
379
+ }
380
+
381
+ if (taxResult.amount !== undefined) {
382
+ taxAmount = parseFloat(taxResult.amount);
383
+ }
384
+ }
385
+ else if (resultData['tax.rate'] !== undefined) {
386
+ adjustedRate = parseFloat(resultData['tax.rate']);
387
+ } else if (resultData.rate !== undefined) {
388
+ adjustedRate = parseFloat(resultData.rate);
389
+ }
390
+ }
391
+ } catch (error) {
392
+ console.error(`Error applying rules for tax ${tax.abbreviation}:`, error);
393
+ }
394
+
395
+ // Calculate tax amount
396
+ if (taxAmount > 0) {
397
+ totalTax += roundCurrency(taxAmount);
398
+ } else {
399
+ taxAmount = productSum * (adjustedRate / 100);
400
+ totalTax += roundCurrency(taxAmount);
401
+ }
402
+ }
285
403
 
286
404
  return roundCurrency(totalTax);
287
405
  }
@@ -402,8 +520,8 @@ function applyExchangeStrategy(amount, exchangeRate) {
402
520
  * @param {Object} sale - Sale object containing payments and calculation context
403
521
  * @returns {number} Positive change amount if overpaid, otherwise 0
404
522
  */
405
- function calcChange(sale) {
406
- const total = calcTotal(sale);
523
+ async function calcChange(sale) {
524
+ const total = await calcTotal(sale);
407
525
  const paymentSum = sumGivedAmountPayments(sale);
408
526
  const change = paymentSum - total;
409
527
  return roundCurrency(change > 0 ? change : 0);
@@ -414,9 +532,9 @@ function calcChange(sale) {
414
532
  * @param {Object} sale - Sale object containing payments and calculation context
415
533
  * @returns {number} Remaining debt amount (0 if fully paid)
416
534
  */
417
- function calcDebt(sale) {
418
- const total = calcTotal(sale);
419
- const paymentSum = sumAmountPayments(sale);
535
+ async function calcDebt(sale) {
536
+ const total = await calcTotal(sale);
537
+ const paymentSum = await sumAmountPayments(sale);
420
538
  const debt = total - paymentSum;
421
539
  return roundCurrency(debt > 0 ? debt : 0);
422
540
  }
@@ -426,10 +544,15 @@ function calcDebt(sale) {
426
544
  * @param {Object} sale - Sale object containing payments array
427
545
  * @returns {number} Total effective payment amount in sale currency
428
546
  */
429
- function sumAmountPayments(sale) {
430
- return (sale.payments || []).reduce((sum, payment) => {
431
- return sum + calcPaymentAmount(payment, sale);
432
- }, 0);
547
+ async function sumAmountPayments(sale) {
548
+ const payments = sale.payments || [];
549
+ let sum = 0;
550
+
551
+ for (const payment of payments) {
552
+ sum += await calcPaymentAmount(payment, sale);
553
+ }
554
+
555
+ return sum;
433
556
  }
434
557
 
435
558
  /**
@@ -449,8 +572,8 @@ function sumGivedAmountPayments(sale) {
449
572
  * @param {Object} sale - Parent sale object for context
450
573
  * @returns {number} Effective amount applied to sale total in sale currency
451
574
  */
452
- function calcPaymentAmount(payment, sale) {
453
- const total = calcTotal(sale);
575
+ async function calcPaymentAmount(payment, sale) {
576
+ const total = await calcTotal(sale);
454
577
  const payments = sale.payments || [];
455
578
 
456
579
  // Find current payment index
@@ -495,8 +618,8 @@ function calcPaymentAmount(payment, sale) {
495
618
  * @param {Object} sale
496
619
  * @returns {number} Amount in payment's original currency
497
620
  */
498
- function calcOriginalPaymentAmount(payment, sale) {
499
- const effectiveAmount = calcPaymentAmount(payment, sale);
621
+ async function calcOriginalPaymentAmount(payment, sale) {
622
+ const effectiveAmount = await calcPaymentAmount(payment, sale);
500
623
 
501
624
  // Convert FROM sale currency TO payment's original currency
502
625
  return exchange(
@@ -540,9 +663,9 @@ function calcGivedPaymentAmount(payment, sale) {
540
663
  * - original_currency_iso: Payment currency ISO code
541
664
  * - exchange_rate: Calculated exchange rate between currencies
542
665
  */
543
- function calculatePaymentDetails(payment, sale) {
666
+ async function calculatePaymentDetails(payment, sale) {
544
667
  // Calculate the actual amount that can be applied to the sale (in sale currency)
545
- const amount = calcPaymentAmount(payment, sale);
668
+ const amount = await calcPaymentAmount(payment, sale);
546
669
 
547
670
  // Calculate the given amount in sale currency (full conversion without debt cap)
548
671
  const gived_amount = exchange(
@@ -568,7 +691,7 @@ function calculatePaymentDetails(payment, sale) {
568
691
  original_amount, // Applied amount in payment's original currency
569
692
  original_gived_amount: payment.original_gived_amount, // Original given amount unchanged
570
693
  exchange_rate,
571
- change_amount: roundCurrency(payment.gived_amount - payment.amount) // Change in sale currency
694
+ change_amount: roundCurrency(gived_amount - amount) // Change in sale currency
572
695
  };
573
696
  }
574
697
 
@@ -620,40 +743,21 @@ function appliedWholesaleLevels(sale) {
620
743
  return result;
621
744
  }
622
745
 
623
- function getSeparatedTaxes(sale) {
624
- const result = getApplicableDwTaxes(sale);
625
-
626
- return {
627
- sale_taxes: result.taxes.map(tax => ({
628
- tax_id: tax.id,
629
- abbreviation: tax.abbreviation,
630
- rate: tax.rate,
631
- amount: tax.amount,
632
- product_id: tax.product_id // null for general taxes
633
- })),
634
- tax_calculation_batches: result.tax_batches.map(batch => ({
635
- product_id: batch.product_id,
636
- product_index: batch.product_index,
637
- sum: batch.sum,
638
- amount: batch.amount,
639
- rate: batch.rate,
640
- tax_type: batch.tax_type
641
- }))
642
- };
643
- }
644
-
645
746
  /**
646
747
  * Calculates all applicable taxes for the sale and returns them in API format
647
748
  * @param {Object} sale - Sale data object
648
749
  * @returns {Array} Array of tax objects with id, abbreviation, and amount
649
750
  */
650
- function getApplicableDwTaxes(sale) {
751
+ async function getApplicableDwTaxes(sale) {
651
752
  validateSaleStructure(sale);
652
753
 
653
754
  const taxMap = {};
654
755
  const taxBatches = [];
655
756
 
656
- sale.dwSaleProducts.forEach((product, productIndex) => {
757
+ // Get rules for taxes
758
+ const convertedRuleTaxes = ruleManager.getRulesForTrigger('calculate_taxes') || [];
759
+
760
+ for (const [productIndex, product] of sale.dwSaleProducts.entries()) {
657
761
  const productSum = calcDwSaleProductSum(product, sale);
658
762
  const discount = calcDwSaleProductDiscount(product, sale);
659
763
  const netAmount = roundCurrency(productSum - discount);
@@ -661,10 +765,69 @@ function getApplicableDwTaxes(sale) {
661
765
  // Get applicable taxes for this product
662
766
  const specificTaxes = getProductSpecificTaxes(product, sale);
663
767
  const generalTaxes = getGeneralTaxes(sale);
664
- const allTaxes = [...specificTaxes, ...generalTaxes];
768
+ let allTaxes = [...specificTaxes, ...generalTaxes];
769
+
770
+ // Process each tax with rules
771
+ for (const tax of allTaxes) {
772
+ let adjustedRate = tax.rate;
773
+ let taxAmount = 0;
774
+
775
+ if (convertedRuleTaxes.length > 0 && ruleEngine) {
776
+ try {
777
+ // Context
778
+ const context = {
779
+ sale: {
780
+ ...preprocessedSale,
781
+ subtotal: preprocessedSale.subtotal || calcSubtotal(sale),
782
+ customers: preprocessedSale.customers || sale.customers || []
783
+ },
784
+ tax: {
785
+ ...tax,
786
+ tax_id: tax.id,
787
+ dw_product_id: tax.product_id || null,
788
+ sale_id: null,
789
+ amount: 0,
790
+ branch_id: sale.branch_id,
791
+ company_id: sale.company_id
792
+ }
793
+ };
794
+
795
+ const ruleResults = await ruleEngine.executeRules(convertedRuleTaxes, context);
796
+ const appliedRule = ruleResults.find(result => result.success && result.data);
797
+
798
+ if (appliedRule && appliedRule.data) {
799
+ // Result
800
+ const resultData = appliedRule.data;
801
+
802
+ if (resultData.result && resultData.result.tax) {
803
+ const taxResult = resultData.result.tax;
804
+
805
+ if (taxResult.rate !== undefined) {
806
+ adjustedRate = parseFloat(taxResult.rate);
807
+ }
808
+
809
+ if (taxResult.amount !== undefined) {
810
+ taxAmount = parseFloat(taxResult.amount);
811
+ }
812
+ }
813
+
814
+ else if (resultData['tax.rate'] !== undefined) {
815
+ adjustedRate = parseFloat(resultData['tax.rate']);
816
+ } else if (resultData.rate !== undefined) {
817
+ adjustedRate = parseFloat(resultData.rate);
818
+ }
819
+ }
820
+ } catch (error) {
821
+ console.error(`Error applying rules for tax ${tax.abbreviation}:`, error);
822
+ }
823
+ }
665
824
 
666
- allTaxes.forEach(tax => {
667
- const taxAmount = roundCurrency(netAmount * (tax.rate / 100));
825
+ if (taxAmount <= 0) {
826
+ taxAmount = netAmount * (adjustedRate / 100);
827
+ }
828
+
829
+ taxAmount = roundCurrency(taxAmount);
830
+
668
831
  const taxKey = getTaxProductIndex(tax, tax.product_id);
669
832
 
670
833
  const taxBatch = {
@@ -672,41 +835,40 @@ function getApplicableDwTaxes(sale) {
672
835
  product_index: productIndex,
673
836
  sum: netAmount,
674
837
  amount: taxAmount,
675
- rate: tax.rate,
676
- tax_type: tax.product_id ? 'specific' : 'general'
838
+ rate: adjustedRate, // ¡¡Usar adjustedRate, no tax.rate!!
839
+ original_rate: tax.rate,
840
+ tax_type: tax.product_id ? 'specific' : 'general',
841
+ adjusted_by_rule: adjustedRate !== tax.rate
677
842
  };
678
843
 
679
844
  taxBatches.push(taxBatch);
680
845
 
681
- if (taxMap[taxKey]) {
682
- taxMap[taxKey].amount += taxAmount;
683
- taxMap[taxKey].batches.push(taxBatch);
846
+ // Usar adjustedRate en el mapa de impuestos
847
+ const mapTaxKey = `${tax.id}-${adjustedRate}-${tax.abbreviation}`;
848
+
849
+ if (taxMap[mapTaxKey]) {
850
+ taxMap[mapTaxKey].amount += taxAmount;
851
+ taxMap[mapTaxKey].batches.push(taxBatch);
684
852
  } else {
685
- taxMap[taxKey] = {
853
+ taxMap[mapTaxKey] = {
686
854
  id: tax.id,
687
855
  abbreviation: tax.abbreviation,
688
- rate: tax.rate,
856
+ rate: adjustedRate, // ¡¡IMPORTANTE: Usar adjustedRate!!
857
+ original_rate: tax.rate,
689
858
  amount: taxAmount,
690
859
  product_id: tax.product_id || null,
691
- batches: [taxBatch]
860
+ batches: [taxBatch],
861
+ adjusted_by_rule: adjustedRate !== tax.rate,
862
+ rule_applied: convertedRuleTaxes.length > 0 ? 'f8147b8a-ad51-46e8-884d-c3374efc9b00' : null
692
863
  };
693
864
  }
694
- });
695
- });
865
+ }
866
+ }
696
867
 
697
868
  return Object.values(taxMap).map(tax => ({
698
869
  ...tax,
699
870
  amount: roundCurrency(tax.amount)
700
871
  })).filter(tax => tax.amount > 0);
701
-
702
- /*
703
- return {
704
- taxes: Object.values(taxMap).map(tax => ({
705
- ...tax,
706
- amount: roundCurrency(tax.amount)
707
- })).filter(tax => tax.amount > 0),
708
- tax_batches: taxBatches
709
- }; */
710
872
  }
711
873
 
712
874
 
@@ -724,12 +886,12 @@ function getApplicableDwTaxes(sale) {
724
886
  * - original_subtotal: Optional - subtotal in original currency
725
887
  * - original_total: Optional - total in original currency
726
888
  */
727
- function calculateSaleTotals(sale) {
889
+ async function calculateSaleTotals(sale) {
728
890
  // Calculate base totals in sale currency
729
891
  const subtotal = calcSubtotal(sale);
730
- const total = calcTotal(sale);
731
- const change = calcChange(sale);
732
- const debt = calcDebt(sale);
892
+ const total = await calcTotal(sale);
893
+ const change = await calcChange(sale);
894
+ const debt = await calcDebt(sale);
733
895
 
734
896
  const result = {
735
897
  subtotal, // Sum of products before taxes
@@ -893,24 +1055,24 @@ function roundCurrency(value, overrideRounding) {
893
1055
 
894
1056
  module.exports = {
895
1057
  init,
896
- preprocessSale,
897
- calcTotal,
1058
+ preprocessSale, // Async
1059
+ calcTotal, // Async
898
1060
  calcSubtotal,
899
- calcDebt,
900
- calcChange,
901
- calcOriginalPaymentAmount,
1061
+ calcDebt, // Async
1062
+ calcChange, // Async
1063
+ calcOriginalPaymentAmount, // Async
902
1064
  calcGivedPaymentAmount,
903
1065
  calcTaxesByAbbreviation,
904
- calculateSaleTotals,
905
- calculatePaymentDetails,
1066
+ calculateSaleTotals, // Async
1067
+ calculatePaymentDetails, // Async
906
1068
  appliedWholesaleLevels,
907
- getApplicableDwTaxes,
1069
+ getApplicableDwTaxes, // Async
908
1070
  getConsolidatedTaxes,
909
1071
  calcDwSaleProductPrice,
910
1072
  _internals: {
911
1073
  calcDwSaleProductSum,
912
1074
  calcDwSaleProductDiscount,
913
- getApplicableProductTaxes,
1075
+ getApplicableProductTaxes, // Async
914
1076
  exchange
915
1077
  }
916
1078
  };
package/src/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * @module softseti-sale-calculator-library
4
4
  */
5
5
 
6
- import { PaymentCalculation, RefundCalculation, RefundOptions, Sale, SaleItem, SaleTotals, WholesaleLevel } from "./interfaces";
6
+ import { PaymentCalculation, RefundCalculation, RefundOptions, Sale, SaleItem, SaleTotals, WholesaleLevel } from "./models/interfaces";
7
7
 
8
8
  declare module 'softseti-sale-calculator-library' {
9
9
  /**
@@ -15,9 +15,9 @@ declare module 'softseti-sale-calculator-library' {
15
15
  /**
16
16
  * Preprocesses sale data for calculations
17
17
  * @param {Sale} sale - Sale object to preprocess
18
- * @returns {Sale} Preprocessed sale object
18
+ * @returns {Promise<Sale>} Preprocessed sale object
19
19
  */
20
- export function preprocessSale(sale: any): any;
20
+ export function preprocessSale(sale: any): Promise<any>;
21
21
 
22
22
  /**
23
23
  * Calculates subtotal for a sale
@@ -29,23 +29,23 @@ declare module 'softseti-sale-calculator-library' {
29
29
  /**
30
30
  * Calculates total amount for a sale
31
31
  * @param {Sale} sale - Sale object
32
- * @returns {number} Total amount
32
+ * @returns {Promise<number>} Total amount
33
33
  */
34
- export function calcTotal(sale: any): number;
34
+ export function calcTotal(sale: any): Promise<number>;
35
35
 
36
36
  /**
37
37
  * Calculates remaining debt for a sale
38
38
  * @param {Sale} sale - Sale object
39
- * @returns {number} Debt amount
39
+ * @returns {Promise<number>} Debt amount
40
40
  */
41
- export function calcDebt(sale: any): number;
41
+ export function calcDebt(sale: any): Promise<number>;
42
42
 
43
43
  /**
44
44
  * Calculates change amount for a sale
45
45
  * @param {Sale} sale - Sale object
46
- * @returns {number} Change amount
46
+ * @returns {Promise<number>} Change amount
47
47
  */
48
- export function calcChange(sale: any): number;
48
+ export function calcChange(sale: any): Promise<number>;
49
49
 
50
50
  /**
51
51
  * Calculates taxes by tax abbreviation
@@ -59,9 +59,9 @@ declare module 'softseti-sale-calculator-library' {
59
59
  * Calculates original payment amount
60
60
  * @param {object} payment - Payment object
61
61
  * @param {Sale} sale - Sale object
62
- * @returns {number} Original payment amount
62
+ * @returns {Promise<number>} Original payment amount
63
63
  */
64
- export function calcOriginalPaymentAmount(payment: any, sale: Sale): number;
64
+ export function calcOriginalPaymentAmount(payment: any, sale: Sale): Promise<number>;
65
65
 
66
66
  /**
67
67
  * Calculates given payment amount
@@ -75,9 +75,9 @@ declare module 'softseti-sale-calculator-library' {
75
75
  * Calculates detailed payment information
76
76
  * @param {object} payment - Payment object
77
77
  * @param {Sale} sale - Sale object
78
- * @returns {PaymentCalculation} Payment details
78
+ * @returns {Promise<PaymentCalculation>} Payment details
79
79
  */
80
- export function calculatePaymentDetails(payment: any, sale: Sale): PaymentCalculation;
80
+ export function calculatePaymentDetails(payment: any, sale: Sale): Promise<PaymentCalculation>;
81
81
 
82
82
 
83
83
  export function calcDwSaleProductPrice(saleProduct: any, sale: any): number;
@@ -91,9 +91,9 @@ declare module 'softseti-sale-calculator-library' {
91
91
  /**
92
92
  * Calculates all applicable taxes for the sale and returns them in API format
93
93
  * @param {Object} sale - Sale object
94
- * @returns {Array} Array of tax objects with id, abbreviation, and amount
94
+ * @returns {Promise<Array>} Array of tax objects with id, abbreviation, and amount
95
95
  */
96
- export function getApplicableDwTaxes(sale: any): any[];
96
+ export function getApplicableDwTaxes(sale: any): Promise<any[]>;
97
97
 
98
98
 
99
99
  /**
@@ -106,9 +106,9 @@ declare module 'softseti-sale-calculator-library' {
106
106
  /**
107
107
  * Calculates comprehensive sale totals
108
108
  * @param {Sale} sale - Sale object
109
- * @returns {SaleTotals} Sale totals
109
+ * @returns {Promise<SaleTotals>} Sale totals
110
110
  */
111
- export function calculateSaleTotals(sale: any): SaleTotals;
111
+ export function calculateSaleTotals(sale: any): Promise<SaleTotals>;
112
112
 
113
113
  /**
114
114
  * Internal calculation methods (advanced use only)
@@ -130,7 +130,7 @@ declare module 'softseti-sale-calculator-library' {
130
130
  * Gets applicable product taxes
131
131
  * @internal
132
132
  */
133
- getApplicableProductTaxes: (saleProduct: any, productSum: number, sale: Sale) => number;
133
+ getApplicableProductTaxes: (saleProduct: any, productSum: number, sale: Sale) => Promise<number>;
134
134
 
135
135
  /**
136
136
  * Currency exchange calculation
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
- const SaleCalculator = require('./SaleCalculator');
2
- const RefundCalculator = require('./refund-calculator');
1
+ const SaleCalculator = require('./core/sale-calculator');
2
+ const RefundCalculator = require('./core/refund-calculator');
3
3
 
4
4
  module.exports = {
5
5
  ...SaleCalculator,
@@ -0,0 +1,53 @@
1
+ // src/models/BusinessRuleModel.js
2
+
3
+ /**
4
+ * Business Rule Model for GoRules integration
5
+ * @class BusinessRuleModel
6
+ */
7
+ class BusinessRuleModel {
8
+ constructor(data = {}) {
9
+ this.id = data.id || '';
10
+ this.name = data.name || '';
11
+ this.description = data.description || '';
12
+ this.entity_type = data.entity_type || '';
13
+ this.trigger_event = data.trigger_event || '';
14
+ this.type_rule = data.type_rule || '';
15
+ this.company_id = data.company_id || 0;
16
+ this.branch_id = data.branch_id || 0;
17
+ this.execution_order = data.execution_order || 1;
18
+ this.cumulative = data.cumulative || false;
19
+ this.valid_from = data.valid_from || null;
20
+ this.valid_until = data.valid_until || null;
21
+ this.rule_content = data.rule_content || null;
22
+ }
23
+
24
+ /**
25
+ * Check if rule is currently active
26
+ * @returns {boolean}
27
+ */
28
+ isActive() {
29
+ const now = new Date();
30
+
31
+ if (this.valid_from && new Date(this.valid_from) > now) {
32
+ return false;
33
+ }
34
+
35
+ if (this.valid_until && new Date(this.valid_until) < now) {
36
+ return false;
37
+ }
38
+
39
+ return true;
40
+ }
41
+
42
+ /**
43
+ * Validate rule structure
44
+ * @returns {boolean}
45
+ */
46
+ isValid() {
47
+ return this.rule_content &&
48
+ this.rule_content.nodes &&
49
+ this.rule_content.edges;
50
+ }
51
+ }
52
+
53
+ module.exports = BusinessRuleModel;
@@ -0,0 +1,75 @@
1
+ // src/rules/RuleManager.js
2
+ const BusinessRuleModel = require('../models/BusinessRuleModel');
3
+
4
+ /**
5
+ * Rule Manager for handling business rule execution
6
+ * @class RuleManager
7
+ */
8
+ class RuleManager {
9
+ constructor() {
10
+ this.rulesByTrigger = new Map();
11
+ }
12
+
13
+ /**
14
+ * Load rules organized by trigger event
15
+ * @param {Object} rules - Rules indexed by trigger event
16
+ */
17
+ loadRules(rules) {
18
+ this.rulesByTrigger.clear();
19
+ Object.entries(rules).forEach(([trigger, ruleList]) => {
20
+ const ruleModels = ruleList.map(rule => new BusinessRuleModel(rule));
21
+ this.rulesByTrigger.set(trigger, ruleModels);
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Get rules for a specific trigger event
27
+ * @param {string} triggerEvent - Trigger event name
28
+ * @returns {Array<BusinessRuleModel>}
29
+ */
30
+ getRulesForTrigger(triggerEvent) {
31
+ const rules = this.rulesByTrigger.get(triggerEvent) || [];
32
+ return rules.filter(rule => rule.isActive() && rule.isValid());
33
+ }
34
+
35
+ /**
36
+ * Get all trigger events with rules
37
+ * @returns {Array<string>}
38
+ */
39
+ getAvailableTriggers() {
40
+ return Array.from(this.rulesByTrigger.keys());
41
+ }
42
+
43
+ /**
44
+ * Clear all loaded rules
45
+ */
46
+ clearRules() {
47
+ this.rulesByTrigger.clear();
48
+ }
49
+
50
+ /**
51
+ * Get statistics about loaded rules
52
+ * @returns {Object}
53
+ */
54
+ getStats() {
55
+ const stats = {
56
+ totalRules: 0,
57
+ byTrigger: {},
58
+ activeRules: 0
59
+ };
60
+
61
+ this.rulesByTrigger.forEach((rules, trigger) => {
62
+ const activeRules = rules.filter(rule => rule.isActive());
63
+ stats.byTrigger[trigger] = {
64
+ total: rules.length,
65
+ active: activeRules.length
66
+ };
67
+ stats.totalRules += rules.length;
68
+ stats.activeRules += activeRules.length;
69
+ });
70
+
71
+ return stats;
72
+ }
73
+ }
74
+
75
+ module.exports = RuleManager;
File without changes
File without changes