softseti-sale-calculator-library 4.0.0 → 4.1.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 +1 -1
- package/src/core/CalculationEngine.js +218 -0
- package/src/core/CurrencyEngine.js +104 -0
- package/src/core/PaymentEngine.js +174 -0
- package/src/core/RuleEngineWrapper.js +110 -0
- package/src/core/TaxEngine.js +429 -0
- package/src/core/sale-calculator.js +219 -1033
- package/src/usecases/CalculateProductDiscount.js +41 -0
- package/src/usecases/CalculateProductPrice.js +48 -0
- package/src/usecases/CalculateProductSum.js +38 -0
- package/src/usecases/CalculateSubtotal.js +33 -0
- package/src/usecases/CalculateTotal.js +36 -0
- package/src/usecases/CalculateWholesale.js +119 -0
- package/src/utils/CurrencyUtils.js +76 -0
- package/src/utils/RoundingUtils.js +52 -0
- package/src/utils/TaxUtils.js +51 -0
- package/src/utils/ValidationUtils.js +55 -0
- package/src/utils/WholesaleUtils.js +69 -0
package/package.json
CHANGED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculation Engine
|
|
3
|
+
* Handles core sale calculations including subtotal, total, and wholesale pricing
|
|
4
|
+
*/
|
|
5
|
+
const RoundingUtils = require('../utils/RoundingUtils');
|
|
6
|
+
const ValidationUtils = require('../utils/ValidationUtils');
|
|
7
|
+
|
|
8
|
+
class CalculationEngine {
|
|
9
|
+
/**
|
|
10
|
+
* @param {SalesCalculator} calculator - Parent calculator instance
|
|
11
|
+
*/
|
|
12
|
+
constructor(calculator) {
|
|
13
|
+
this.calculator = calculator;
|
|
14
|
+
|
|
15
|
+
// Initialize ALL use cases
|
|
16
|
+
this.calculateSubtotal = new (require('../usecases/CalculateSubtotal'))(calculator);
|
|
17
|
+
this.calculateTotal = new (require('../usecases/CalculateTotal'))(calculator);
|
|
18
|
+
this.calculateProductPrice = new (require('../usecases/CalculateProductPrice'))(calculator);
|
|
19
|
+
this.calculateProductDiscount = new (require('../usecases/CalculateProductDiscount'))(calculator);
|
|
20
|
+
this.calculateProductSum = new (require('../usecases/CalculateProductSum'))(calculator);
|
|
21
|
+
this.calculateWholesale = new (require('../usecases/CalculateWholesale'))(calculator);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Preprocess sale data
|
|
26
|
+
* @param {Object} sale - Sale object
|
|
27
|
+
* @returns {Promise<Object>} Preprocessed sale
|
|
28
|
+
*/
|
|
29
|
+
async preprocessSale(sale) {
|
|
30
|
+
ValidationUtils.validateSaleStructure(sale, this.calculator.data.products);
|
|
31
|
+
|
|
32
|
+
const saleCurrency = this.calculator.data.currencies[sale.currency_id];
|
|
33
|
+
|
|
34
|
+
// Add currency ISO if not provided
|
|
35
|
+
if (!sale.currency_iso && saleCurrency?.iso) {
|
|
36
|
+
sale.currency_iso = saleCurrency.iso;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Set default currency if none specified
|
|
40
|
+
if (!sale.currency_id && !sale.currency_iso) {
|
|
41
|
+
sale.currency_id = 88; // Default currency ID
|
|
42
|
+
sale.currency_iso = 'MXN'; // Default currency
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Process deals
|
|
46
|
+
if (sale.dwSaleDeals) {
|
|
47
|
+
sale.dwSaleDeals = sale.dwSaleDeals.map(deal => ({
|
|
48
|
+
...deal,
|
|
49
|
+
sum: deal.amount ? deal.amount * -1 : 0
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Process payments
|
|
54
|
+
if (sale.payments) {
|
|
55
|
+
const processedPayments = [];
|
|
56
|
+
for (const payment of sale.payments) {
|
|
57
|
+
const originalCurrency = this.calculator.data.currencies[payment.currency_id];
|
|
58
|
+
|
|
59
|
+
if (payment.amount && payment.original_amount) {
|
|
60
|
+
processedPayments.push({
|
|
61
|
+
...payment,
|
|
62
|
+
original_currency_id: payment.currency_id,
|
|
63
|
+
original_currency_iso: originalCurrency?.iso,
|
|
64
|
+
currency_id: sale.currency_id,
|
|
65
|
+
currency_iso: sale.currency_iso,
|
|
66
|
+
change_amount: 0
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
const details = await this.calculator.paymentEngine.calculatePaymentDetails({
|
|
70
|
+
...payment,
|
|
71
|
+
original_gived_amount: payment.gived_amount || payment.amount,
|
|
72
|
+
original_currency_iso: originalCurrency?.iso
|
|
73
|
+
}, sale);
|
|
74
|
+
|
|
75
|
+
processedPayments.push({
|
|
76
|
+
...payment,
|
|
77
|
+
original_currency_id: payment.currency_id,
|
|
78
|
+
original_currency_iso: originalCurrency?.iso,
|
|
79
|
+
currency_id: sale.currency_id,
|
|
80
|
+
currency_iso: sale.currency_iso,
|
|
81
|
+
original_gived_amount: payment.gived_amount || payment.amount,
|
|
82
|
+
...details
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
sale.payments = processedPayments;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Process products
|
|
90
|
+
if (sale.dwSaleProducts) {
|
|
91
|
+
sale.dwSaleProducts = sale.dwSaleProducts.map(product => {
|
|
92
|
+
const productData = this.calculator.data.products[product.product_id];
|
|
93
|
+
const productCurrency = this.calculator.data.currencies[productData?.currency_id];
|
|
94
|
+
|
|
95
|
+
// Calculate actual price (including wholesale)
|
|
96
|
+
const actualPrice = this.calcDwSaleProductPrice(product, sale);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
...product,
|
|
100
|
+
price: actualPrice,
|
|
101
|
+
public_price: actualPrice,
|
|
102
|
+
currency_iso: productCurrency?.iso || sale.currency_iso,
|
|
103
|
+
currency_id: productData?.currency_id || sale.currency_id
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Process wholesale levels
|
|
109
|
+
if (this.calculator.data.wholesaleLevels) {
|
|
110
|
+
Object.keys(this.calculator.data.wholesaleLevels).forEach(productId => {
|
|
111
|
+
this.calculator.data.wholesaleLevels[productId] =
|
|
112
|
+
this.calculator.data.wholesaleLevels[productId].map(level => ({
|
|
113
|
+
...level,
|
|
114
|
+
price: RoundingUtils.roundCurrency(level.price, this.calculator.config)
|
|
115
|
+
}));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Store preprocessed sale
|
|
120
|
+
this.calculator.preprocessedSale = sale;
|
|
121
|
+
|
|
122
|
+
return sale;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Calculate total sale amount
|
|
127
|
+
* @param {Object} sale - Sale object
|
|
128
|
+
* @returns {Promise<number>} Total amount
|
|
129
|
+
*/
|
|
130
|
+
async calcTotal(sale) {
|
|
131
|
+
return this.calculateTotal.calculate(sale);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Calculate subtotal
|
|
136
|
+
* @param {Object} sale - Sale object
|
|
137
|
+
* @returns {number} Subtotal amount
|
|
138
|
+
*/
|
|
139
|
+
calcSubtotal(sale) {
|
|
140
|
+
return this.calculateSubtotal.calculate(sale);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Calculate product discount
|
|
145
|
+
* @param {Object} product - Product object
|
|
146
|
+
* @param {Object} sale - Sale object
|
|
147
|
+
* @returns {number} Discount amount
|
|
148
|
+
*/
|
|
149
|
+
calcDwSaleProductDiscount(product, sale) {
|
|
150
|
+
return this.calculateProductDiscount.calculate(product, sale);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Calculate product sum
|
|
155
|
+
* @param {Object} saleProduct - Sale product object
|
|
156
|
+
* @param {Object} sale - Sale object
|
|
157
|
+
* @returns {number} Product sum
|
|
158
|
+
*/
|
|
159
|
+
calcDwSaleProductSum(saleProduct, sale) {
|
|
160
|
+
return this.calculateProductSum.calculate(saleProduct, sale);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Calculate product price
|
|
165
|
+
* @param {Object} saleProduct - Sale product object
|
|
166
|
+
* @param {Object} sale - Sale object
|
|
167
|
+
* @returns {number} Product price
|
|
168
|
+
*/
|
|
169
|
+
calcDwSaleProductPrice(saleProduct, sale) {
|
|
170
|
+
return this.calculateProductPrice.calculate(saleProduct, sale);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get applied wholesale levels
|
|
175
|
+
* @param {Object} sale - Sale object
|
|
176
|
+
* @returns {Array} Applied wholesale levels
|
|
177
|
+
*/
|
|
178
|
+
appliedWholesaleLevels(sale) {
|
|
179
|
+
return this.calculateWholesale.getAppliedLevels(sale);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Calculate sale totals
|
|
184
|
+
* @param {Object} sale - Sale object
|
|
185
|
+
* @returns {Promise<Object>} Sale totals object
|
|
186
|
+
*/
|
|
187
|
+
async calculateSaleTotals(sale) {
|
|
188
|
+
const subtotal = this.calcSubtotal(sale);
|
|
189
|
+
const total = await this.calcTotal(sale);
|
|
190
|
+
const change = await this.calculator.paymentEngine.calcChange(sale);
|
|
191
|
+
const debt = await this.calculator.paymentEngine.calcDebt(sale);
|
|
192
|
+
|
|
193
|
+
const result = {
|
|
194
|
+
subtotal,
|
|
195
|
+
total,
|
|
196
|
+
change,
|
|
197
|
+
debt
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Add original currency totals if different
|
|
201
|
+
if (sale.original_currency_iso && sale.original_currency_iso !== sale.currency_iso) {
|
|
202
|
+
result.original_subtotal = this.calculator.currencyEngine.exchange(
|
|
203
|
+
subtotal,
|
|
204
|
+
sale.currency_iso,
|
|
205
|
+
sale.original_currency_iso
|
|
206
|
+
);
|
|
207
|
+
result.original_total = this.calculator.currencyEngine.exchange(
|
|
208
|
+
total,
|
|
209
|
+
sale.currency_iso,
|
|
210
|
+
sale.original_currency_iso
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = CalculationEngine;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency Engine
|
|
3
|
+
* Handles currency exchange and conversion
|
|
4
|
+
*/
|
|
5
|
+
const RoundingUtils = require('../utils/RoundingUtils');
|
|
6
|
+
|
|
7
|
+
class CurrencyEngine {
|
|
8
|
+
/**
|
|
9
|
+
* @param {SalesCalculator} calculator - Parent calculator instance
|
|
10
|
+
*/
|
|
11
|
+
constructor(calculator) {
|
|
12
|
+
this.calculator = calculator;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert amount between currencies
|
|
17
|
+
* @param {number} amount - Amount to convert
|
|
18
|
+
* @param {string} fromCurrency - ISO code (e.g. 'MXN')
|
|
19
|
+
* @param {string} toCurrency - ISO code (e.g. 'USD')
|
|
20
|
+
* @returns {number} Converted amount
|
|
21
|
+
*/
|
|
22
|
+
exchange(amount, fromCurrency, toCurrency) {
|
|
23
|
+
if (fromCurrency === toCurrency) {
|
|
24
|
+
return RoundingUtils.roundCurrency(amount, this.calculator.config);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Try exchange rate first
|
|
28
|
+
const exchangeRateModel = this.findExchangeRate(fromCurrency, toCurrency);
|
|
29
|
+
if (exchangeRateModel) {
|
|
30
|
+
return RoundingUtils.roundCurrency(
|
|
31
|
+
this.applyExchangeStrategy(amount, exchangeRateModel),
|
|
32
|
+
this.calculator.config
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Fallback to currency converter rate
|
|
37
|
+
const currencyConverterRateModel = this.findCurrencyConverterRate(fromCurrency, toCurrency);
|
|
38
|
+
if (!currencyConverterRateModel) {
|
|
39
|
+
throw new Error(`No exchange rate found for ${fromCurrency}->${toCurrency}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return RoundingUtils.roundCurrency(
|
|
43
|
+
amount * currencyConverterRateModel.rate,
|
|
44
|
+
this.calculator.config
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find exchange rate
|
|
50
|
+
* @param {string} from - From currency ISO
|
|
51
|
+
* @param {string} to - To currency ISO
|
|
52
|
+
* @returns {Object|null} Exchange rate model or null
|
|
53
|
+
*/
|
|
54
|
+
findExchangeRate(from, to) {
|
|
55
|
+
return Object.values(this.calculator.data.exchangeRates).find(rate =>
|
|
56
|
+
rate.from_currency_iso === from &&
|
|
57
|
+
rate.to_currency_iso === to
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Find currency converter rate
|
|
63
|
+
* @param {string} from - From currency ISO
|
|
64
|
+
* @param {string} to - To currency ISO
|
|
65
|
+
* @returns {Object|null} Currency converter rate or null
|
|
66
|
+
*/
|
|
67
|
+
findCurrencyConverterRate(from, to) {
|
|
68
|
+
return Object.values(this.calculator.data.currencyConverterRates).find(rate =>
|
|
69
|
+
rate.from_currency_iso === from &&
|
|
70
|
+
rate.to_currency_iso === to
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Apply exchange strategy
|
|
76
|
+
* @param {number} amount - Amount to convert
|
|
77
|
+
* @param {Object} exchangeRate - Exchange rate model
|
|
78
|
+
* @returns {number} Converted amount
|
|
79
|
+
*/
|
|
80
|
+
applyExchangeStrategy(amount, exchangeRate) {
|
|
81
|
+
return (amount * exchangeRate.to_currency_value) / exchangeRate.from_currency_value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get currency by ID
|
|
86
|
+
* @param {number} currencyId - Currency ID
|
|
87
|
+
* @returns {Object|null} Currency object or null
|
|
88
|
+
*/
|
|
89
|
+
getCurrencyById(currencyId) {
|
|
90
|
+
return this.calculator.data.currencies[currencyId] || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get currency ISO by ID
|
|
95
|
+
* @param {number} currencyId - Currency ID
|
|
96
|
+
* @returns {string|null} Currency ISO or null
|
|
97
|
+
*/
|
|
98
|
+
getCurrencyIso(currencyId) {
|
|
99
|
+
const currency = this.getCurrencyById(currencyId);
|
|
100
|
+
return currency ? currency.iso : null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = CurrencyEngine;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Engine
|
|
3
|
+
* Handles payment calculations, currency conversions, and debt management
|
|
4
|
+
*/
|
|
5
|
+
const RoundingUtils = require('../utils/RoundingUtils');
|
|
6
|
+
|
|
7
|
+
class PaymentEngine {
|
|
8
|
+
/**
|
|
9
|
+
* @param {SalesCalculator} calculator - Parent calculator instance
|
|
10
|
+
*/
|
|
11
|
+
constructor(calculator) {
|
|
12
|
+
this.calculator = calculator;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Calculate change amount
|
|
17
|
+
* @param {Object} sale - Sale object
|
|
18
|
+
* @returns {Promise<number>} Change amount
|
|
19
|
+
*/
|
|
20
|
+
async calcChange(sale) {
|
|
21
|
+
const total = await this.calculator.calculationEngine.calcTotal(sale);
|
|
22
|
+
const paymentSum = this.sumGivedAmountPayments(sale);
|
|
23
|
+
const change = paymentSum - total;
|
|
24
|
+
return RoundingUtils.roundCurrency(change > 0 ? change : 0, this.calculator.config);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Calculate remaining debt
|
|
29
|
+
* @param {Object} sale - Sale object
|
|
30
|
+
* @returns {Promise<number>} Debt amount
|
|
31
|
+
*/
|
|
32
|
+
async calcDebt(sale) {
|
|
33
|
+
const total = await this.calculator.calculationEngine.calcTotal(sale);
|
|
34
|
+
const paymentSum = await this.sumAmountPayments(sale);
|
|
35
|
+
const debt = total - paymentSum;
|
|
36
|
+
return RoundingUtils.roundCurrency(debt > 0 ? debt : 0, this.calculator.config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sum effective payment amounts
|
|
41
|
+
* @param {Object} sale - Sale object
|
|
42
|
+
* @returns {Promise<number>} Total effective payment amount
|
|
43
|
+
*/
|
|
44
|
+
async sumAmountPayments(sale) {
|
|
45
|
+
const payments = sale.payments || [];
|
|
46
|
+
let sum = 0;
|
|
47
|
+
|
|
48
|
+
for (const payment of payments) {
|
|
49
|
+
sum += await this.calcPaymentAmount(payment, sale);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return RoundingUtils.roundCurrency(sum, this.calculator.config);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sum raw payment amounts
|
|
57
|
+
* @param {Object} sale - Sale object
|
|
58
|
+
* @returns {number} Total given payment amount
|
|
59
|
+
*/
|
|
60
|
+
sumGivedAmountPayments(sale) {
|
|
61
|
+
return RoundingUtils.roundCurrency((sale.payments || []).reduce((sum, payment) => {
|
|
62
|
+
return sum + this.calcGivedPaymentAmount(payment, sale);
|
|
63
|
+
}, 0), this.calculator.config);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Calculate effective payment amount
|
|
68
|
+
* @param {Object} payment - Payment object
|
|
69
|
+
* @param {Object} sale - Sale object
|
|
70
|
+
* @returns {Promise<number>} Effective amount in sale currency
|
|
71
|
+
*/
|
|
72
|
+
async calcPaymentAmount(payment, sale) {
|
|
73
|
+
const total = await this.calculator.calculationEngine.calcTotal(sale);
|
|
74
|
+
const payments = sale.payments || [];
|
|
75
|
+
|
|
76
|
+
const index = payments.findIndex(p =>
|
|
77
|
+
p.original_gived_amount === payment.original_gived_amount &&
|
|
78
|
+
p.currency_id === payment.currency_id &&
|
|
79
|
+
p.payment_type_id === payment.payment_type_id
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (index === -1) return 0;
|
|
83
|
+
|
|
84
|
+
let accumulated = 0;
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < index; i++) {
|
|
87
|
+
const prevPayment = payments[i];
|
|
88
|
+
const amount = this.calculator.currencyEngine.exchange(
|
|
89
|
+
prevPayment.original_gived_amount,
|
|
90
|
+
prevPayment.original_currency_iso,
|
|
91
|
+
sale.currency_iso
|
|
92
|
+
);
|
|
93
|
+
const remaining = total - accumulated;
|
|
94
|
+
accumulated += Math.min(amount, remaining);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const currentAmount = this.calculator.currencyEngine.exchange(
|
|
98
|
+
payment.original_gived_amount,
|
|
99
|
+
payment.original_currency_iso,
|
|
100
|
+
sale.currency_iso
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const remaining = total - accumulated;
|
|
104
|
+
const appliedAmount = Math.min(currentAmount, Math.max(remaining, 0));
|
|
105
|
+
|
|
106
|
+
return RoundingUtils.roundCurrency(appliedAmount, this.calculator.config);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate original payment amount
|
|
111
|
+
* @param {Object} payment - Payment object
|
|
112
|
+
* @param {Object} sale - Sale object
|
|
113
|
+
* @returns {Promise<number>} Amount in payment's original currency
|
|
114
|
+
*/
|
|
115
|
+
async calcOriginalPaymentAmount(payment, sale) {
|
|
116
|
+
const effectiveAmount = await this.calcPaymentAmount(payment, sale);
|
|
117
|
+
|
|
118
|
+
return this.calculator.currencyEngine.exchange(
|
|
119
|
+
effectiveAmount,
|
|
120
|
+
sale.currency_iso,
|
|
121
|
+
payment.original_currency_iso
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Calculate given payment amount
|
|
127
|
+
* @param {Object} payment - Payment object
|
|
128
|
+
* @param {Object} sale - Sale object
|
|
129
|
+
* @returns {number} Converted amount without debt adjustment
|
|
130
|
+
*/
|
|
131
|
+
calcGivedPaymentAmount(payment, sale) {
|
|
132
|
+
return RoundingUtils.roundCurrency(this.calculator.currencyEngine.exchange(
|
|
133
|
+
payment.original_gived_amount,
|
|
134
|
+
payment.original_currency_iso,
|
|
135
|
+
sale.currency_iso
|
|
136
|
+
), this.calculator.config);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Calculate complete payment details
|
|
141
|
+
* @param {Object} payment - Payment object
|
|
142
|
+
* @param {Object} sale - Sale object
|
|
143
|
+
* @returns {Promise<Object>} Payment details
|
|
144
|
+
*/
|
|
145
|
+
async calculatePaymentDetails(payment, sale) {
|
|
146
|
+
const amount = await this.calcPaymentAmount(payment, sale);
|
|
147
|
+
|
|
148
|
+
const gived_amount = this.calculator.currencyEngine.exchange(
|
|
149
|
+
payment.original_gived_amount,
|
|
150
|
+
payment.original_currency_iso,
|
|
151
|
+
sale.currency_iso
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const original_amount = this.calculator.currencyEngine.exchange(
|
|
155
|
+
amount,
|
|
156
|
+
sale.currency_iso,
|
|
157
|
+
payment.original_currency_iso
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const exchange_rate = gived_amount / payment.original_gived_amount;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
...payment,
|
|
164
|
+
amount: RoundingUtils.roundCurrency(amount, this.calculator.config),
|
|
165
|
+
gived_amount: RoundingUtils.roundCurrency(gived_amount, this.calculator.config),
|
|
166
|
+
original_amount: RoundingUtils.roundCurrency(original_amount, this.calculator.config),
|
|
167
|
+
original_gived_amount: payment.original_gived_amount,
|
|
168
|
+
exchange_rate: RoundingUtils.roundCurrency(exchange_rate, this.calculator.config),
|
|
169
|
+
change_amount: RoundingUtils.roundCurrency(gived_amount - amount, this.calculator.config)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = PaymentEngine;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Engine Wrapper
|
|
3
|
+
* Provides abstraction for rule engine with fallback support
|
|
4
|
+
*/
|
|
5
|
+
class RuleEngineWrapper {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.engine = null;
|
|
8
|
+
this.ruleCache = new Map();
|
|
9
|
+
this.initializeEngine();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize engine with graceful fallback
|
|
14
|
+
*/
|
|
15
|
+
initializeEngine() {
|
|
16
|
+
try {
|
|
17
|
+
// Try to load ZenEngine if available
|
|
18
|
+
const { ZenEngine } = require('@gorules/zen-engine');
|
|
19
|
+
this.engine = new ZenEngine();
|
|
20
|
+
console.log('ZenEngine loaded successfully');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.warn('ZenEngine not available, using fallback mode:', error.message);
|
|
23
|
+
this.engine = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if rule engine is available
|
|
29
|
+
* @returns {boolean} True if engine is available
|
|
30
|
+
*/
|
|
31
|
+
isAvailable() {
|
|
32
|
+
return this.engine !== null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Evaluate a business rule
|
|
37
|
+
* @param {Object} ruleContent - Rule content
|
|
38
|
+
* @param {Object} context - Execution context
|
|
39
|
+
* @returns {Promise<Object>} Evaluation result
|
|
40
|
+
*/
|
|
41
|
+
async evaluateRule(ruleContent, context) {
|
|
42
|
+
if (!this.isAvailable()) {
|
|
43
|
+
return this.fallbackEvaluation(ruleContent, context);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const decision = this.engine.createDecision(ruleContent);
|
|
48
|
+
return await decision.safeEvaluate(context);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Rule evaluation error, using fallback:', error);
|
|
51
|
+
return this.fallbackEvaluation(ruleContent, context);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Execute multiple rules
|
|
57
|
+
* @param {Array} rules - Rules to execute
|
|
58
|
+
* @param {Object} context - Execution context
|
|
59
|
+
* @returns {Promise<Array>} Execution results
|
|
60
|
+
*/
|
|
61
|
+
async executeRules(rules, context) {
|
|
62
|
+
const results = [];
|
|
63
|
+
|
|
64
|
+
for (const rule of rules) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await this.evaluateRule(rule.rule_content, context);
|
|
67
|
+
results.push(result);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
results.push({
|
|
70
|
+
ruleId: rule.id,
|
|
71
|
+
success: false,
|
|
72
|
+
error: error.message
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Fallback evaluation when engine is not available
|
|
82
|
+
* @param {Object} ruleContent - Rule content
|
|
83
|
+
* @param {Object} context - Execution context
|
|
84
|
+
* @returns {Object} Default result
|
|
85
|
+
*/
|
|
86
|
+
fallbackEvaluation(ruleContent, context) {
|
|
87
|
+
// Return default result when engine is not available
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
data: {},
|
|
91
|
+
message: 'Rule engine not available, using fallback'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validate rule structure
|
|
97
|
+
* @param {Object} ruleContent - Rule content
|
|
98
|
+
* @returns {boolean} True if rule is valid
|
|
99
|
+
*/
|
|
100
|
+
validateRule(ruleContent) {
|
|
101
|
+
if (!ruleContent || typeof ruleContent !== 'object') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const requiredProperties = ['nodes', 'edges', 'contentType'];
|
|
106
|
+
return requiredProperties.every(prop => ruleContent.hasOwnProperty(prop));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = RuleEngineWrapper;
|