softseti-sale-calculator-library 3.7.1 → 3.7.2
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/SaleCalculator.js +79 -44
package/package.json
CHANGED
package/src/SaleCalculator.js
CHANGED
|
@@ -8,7 +8,7 @@ let currencyConverterRates = {};
|
|
|
8
8
|
let currencies = {};
|
|
9
9
|
let decimalPlaces = 2;
|
|
10
10
|
let roundingFactor = 100;
|
|
11
|
-
let useRounding = false;
|
|
11
|
+
let useRounding = false;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Sales Calculation Engine (V1)
|
|
@@ -48,7 +48,7 @@ function init(params) {
|
|
|
48
48
|
decimalPlaces = params.decimal_places !== undefined ? params.decimal_places : 2;
|
|
49
49
|
roundingFactor = Math.pow(10, decimalPlaces);
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
useRounding = params.use_rounding !== undefined ? params.use_rounding : false;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// ==============================================
|
|
@@ -127,17 +127,30 @@ function preprocessSale(sale) {
|
|
|
127
127
|
if (sale.payments) {
|
|
128
128
|
sale.payments = sale.payments.map(payment => {
|
|
129
129
|
const originalCurrency = currencies[payment.currency_id];
|
|
130
|
+
|
|
131
|
+
// If payment already has calculated fields, use them
|
|
132
|
+
if (payment.amount && payment.original_amount) {
|
|
133
|
+
return {
|
|
134
|
+
...payment,
|
|
135
|
+
original_currency_id: payment.currency_id,
|
|
136
|
+
original_currency_iso: originalCurrency?.iso,
|
|
137
|
+
currency_id: sale.currency_id,
|
|
138
|
+
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
|
|
130
144
|
return {
|
|
131
145
|
...payment,
|
|
132
146
|
original_currency_id: payment.currency_id,
|
|
133
147
|
original_currency_iso: originalCurrency?.iso,
|
|
134
148
|
currency_id: sale.currency_id,
|
|
135
|
-
currency_iso:
|
|
136
|
-
original_gived_amount: payment.gived_amount,
|
|
137
|
-
|
|
149
|
+
currency_iso: sale.currency_iso,
|
|
150
|
+
original_gived_amount: payment.gived_amount || payment.amount,
|
|
151
|
+
...calculatePaymentDetails({
|
|
138
152
|
...payment,
|
|
139
|
-
original_gived_amount: payment.gived_amount,
|
|
140
|
-
currency_iso: currencies[sale.currency_id]?.iso,
|
|
153
|
+
original_gived_amount: payment.gived_amount || payment.amount,
|
|
141
154
|
original_currency_iso: originalCurrency?.iso
|
|
142
155
|
}, sale)
|
|
143
156
|
};
|
|
@@ -148,7 +161,7 @@ function preprocessSale(sale) {
|
|
|
148
161
|
sale.dwSaleProducts = sale.dwSaleProducts.map(product => {
|
|
149
162
|
const productData = products[product.product_id];
|
|
150
163
|
const productCurrency = currencies[productData?.currency_id];
|
|
151
|
-
|
|
164
|
+
|
|
152
165
|
// Calculate the actual price that will be used (including wholesale)
|
|
153
166
|
const actualPrice = roundCurrency(calcDwSaleProductPrice(product, sale));
|
|
154
167
|
|
|
@@ -204,7 +217,7 @@ function calcDwSaleProductSum(saleProduct, sale) {
|
|
|
204
217
|
//return roundCurrency((saleProduct.quantity * price) / 2); original logic to calc based on legacy
|
|
205
218
|
// Calc sum for products in relation with price product
|
|
206
219
|
//return roundCurrency(saleProduct.quantity * price);
|
|
207
|
-
|
|
220
|
+
return roundCurrency((saleProduct.quantity * price) / baseQuantity);
|
|
208
221
|
}
|
|
209
222
|
|
|
210
223
|
function calcDwSaleProductPrice(saleProduct, sale) {
|
|
@@ -262,14 +275,14 @@ function getApplicableProductTaxes(saleProduct, productSum, sale) {
|
|
|
262
275
|
const specificTaxes = getProductSpecificTaxes(saleProduct, sale);
|
|
263
276
|
// GET General Taxes
|
|
264
277
|
const generalTaxes = getGeneralTaxes(sale);
|
|
265
|
-
|
|
278
|
+
|
|
266
279
|
const totalTax = [...specificTaxes, ...generalTaxes].reduce(
|
|
267
280
|
(total, tax) => {
|
|
268
281
|
const taxAmount = productSum * (tax.rate / 100);
|
|
269
|
-
return total + roundCurrency(taxAmount);
|
|
282
|
+
return total + roundCurrency(taxAmount);
|
|
270
283
|
}, 0
|
|
271
284
|
);
|
|
272
|
-
|
|
285
|
+
|
|
273
286
|
return roundCurrency(totalTax);
|
|
274
287
|
}
|
|
275
288
|
|
|
@@ -345,7 +358,7 @@ function exchange(amount, fromCurrency, toCurrency) {
|
|
|
345
358
|
const exchangeRateModel = findExchangeRate(fromCurrency, toCurrency);
|
|
346
359
|
|
|
347
360
|
if (exchangeRateModel) {
|
|
348
|
-
return roundCurrency(applyExchangeStrategy(amount, exchangeRateModel));
|
|
361
|
+
return roundCurrency(applyExchangeStrategy(amount, exchangeRateModel));
|
|
349
362
|
}
|
|
350
363
|
|
|
351
364
|
// GET CURRENCY CONVERTER RATE WHEN NOT EXIST THE EXANGE RATE
|
|
@@ -356,7 +369,7 @@ function exchange(amount, fromCurrency, toCurrency) {
|
|
|
356
369
|
}
|
|
357
370
|
|
|
358
371
|
// Exchange With CURRENCY CONVERTER RATE
|
|
359
|
-
return roundCurrency(amount * currencyConverterRateModel.rate);
|
|
372
|
+
return roundCurrency(amount * currencyConverterRateModel.rate);
|
|
360
373
|
}
|
|
361
374
|
|
|
362
375
|
// Get exchange rate
|
|
@@ -440,6 +453,7 @@ function calcPaymentAmount(payment, sale) {
|
|
|
440
453
|
const total = calcTotal(sale);
|
|
441
454
|
const payments = sale.payments || [];
|
|
442
455
|
|
|
456
|
+
// Find current payment index
|
|
443
457
|
const index = payments.findIndex(p =>
|
|
444
458
|
p.original_gived_amount === payment.original_gived_amount &&
|
|
445
459
|
p.currency_id === payment.currency_id &&
|
|
@@ -450,6 +464,7 @@ function calcPaymentAmount(payment, sale) {
|
|
|
450
464
|
|
|
451
465
|
let accumulated = 0;
|
|
452
466
|
|
|
467
|
+
// Calculate how much previous payments have covered
|
|
453
468
|
for (let i = 0; i < index; i++) {
|
|
454
469
|
const prevPayment = payments[i];
|
|
455
470
|
const amount = exchange(
|
|
@@ -458,9 +473,10 @@ function calcPaymentAmount(payment, sale) {
|
|
|
458
473
|
sale.currency_iso
|
|
459
474
|
);
|
|
460
475
|
const remaining = total - accumulated;
|
|
461
|
-
accumulated +=
|
|
476
|
+
accumulated += Math.min(amount, remaining);
|
|
462
477
|
}
|
|
463
478
|
|
|
479
|
+
// Calculate current payment amount that can be applied
|
|
464
480
|
const currentAmount = exchange(
|
|
465
481
|
payment.original_gived_amount,
|
|
466
482
|
payment.original_currency_iso,
|
|
@@ -468,21 +484,25 @@ function calcPaymentAmount(payment, sale) {
|
|
|
468
484
|
);
|
|
469
485
|
|
|
470
486
|
const remaining = total - accumulated;
|
|
471
|
-
|
|
487
|
+
const appliedAmount = Math.min(currentAmount, Math.max(remaining, 0));
|
|
488
|
+
|
|
489
|
+
return roundCurrency(appliedAmount);
|
|
472
490
|
}
|
|
473
491
|
|
|
474
492
|
/**
|
|
475
|
-
*
|
|
493
|
+
* Calculates the original amount applied to sale (in payment's original currency)
|
|
476
494
|
* @param {Object} payment
|
|
477
495
|
* @param {Object} sale
|
|
478
496
|
* @returns {number} Amount in payment's original currency
|
|
479
497
|
*/
|
|
480
498
|
function calcOriginalPaymentAmount(payment, sale) {
|
|
481
499
|
const effectiveAmount = calcPaymentAmount(payment, sale);
|
|
500
|
+
|
|
501
|
+
// Convert FROM sale currency TO payment's original currency
|
|
482
502
|
return exchange(
|
|
483
503
|
effectiveAmount,
|
|
484
|
-
|
|
485
|
-
|
|
504
|
+
sale.currency_iso, // From sale currency
|
|
505
|
+
payment.original_currency_iso // To payment's original currency
|
|
486
506
|
);
|
|
487
507
|
}
|
|
488
508
|
|
|
@@ -520,20 +540,35 @@ function calcGivedPaymentAmount(payment, sale) {
|
|
|
520
540
|
* - original_currency_iso: Payment currency ISO code
|
|
521
541
|
* - exchange_rate: Calculated exchange rate between currencies
|
|
522
542
|
*/
|
|
523
|
-
function calculatePaymentDetails(payment,
|
|
524
|
-
// Calculate
|
|
525
|
-
const
|
|
526
|
-
|
|
543
|
+
function calculatePaymentDetails(payment, sale) {
|
|
544
|
+
// Calculate the actual amount that can be applied to the sale (in sale currency)
|
|
545
|
+
const amount = calcPaymentAmount(payment, sale);
|
|
546
|
+
|
|
547
|
+
// Calculate the given amount in sale currency (full conversion without debt cap)
|
|
548
|
+
const gived_amount = exchange(
|
|
549
|
+
payment.original_gived_amount,
|
|
550
|
+
payment.original_currency_iso,
|
|
551
|
+
sale.currency_iso
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
// Calculate the original amount applied (in payment's original currency)
|
|
555
|
+
const original_amount = exchange(
|
|
556
|
+
amount, // This is in sale currency
|
|
557
|
+
sale.currency_iso,
|
|
558
|
+
payment.original_currency_iso
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
// Calculate exchange rate (sale currency per 1 unit of payment currency)
|
|
527
562
|
const exchange_rate = gived_amount / payment.original_gived_amount;
|
|
528
|
-
const original_amount = calcOriginalPaymentAmount(payment, enhancedSale);
|
|
529
563
|
|
|
530
564
|
return {
|
|
531
565
|
...payment,
|
|
532
|
-
amount,
|
|
533
|
-
gived_amount,
|
|
534
|
-
original_amount,
|
|
535
|
-
original_gived_amount: payment.original_gived_amount,
|
|
566
|
+
amount, // Applied amount in sale currency
|
|
567
|
+
gived_amount, // Full given amount in sale currency
|
|
568
|
+
original_amount, // Applied amount in payment's original currency
|
|
569
|
+
original_gived_amount: payment.original_gived_amount, // Original given amount unchanged
|
|
536
570
|
exchange_rate,
|
|
571
|
+
change_amount: Math.max(0, gived_amount - amount) // Change in sale currency
|
|
537
572
|
};
|
|
538
573
|
}
|
|
539
574
|
|
|
@@ -587,7 +622,7 @@ function appliedWholesaleLevels(sale) {
|
|
|
587
622
|
|
|
588
623
|
function getSeparatedTaxes(sale) {
|
|
589
624
|
const result = getApplicableDwTaxes(sale);
|
|
590
|
-
|
|
625
|
+
|
|
591
626
|
return {
|
|
592
627
|
sale_taxes: result.taxes.map(tax => ({
|
|
593
628
|
tax_id: tax.id,
|
|
@@ -616,7 +651,7 @@ function getApplicableDwTaxes(sale) {
|
|
|
616
651
|
validateSaleStructure(sale);
|
|
617
652
|
|
|
618
653
|
const taxMap = {};
|
|
619
|
-
const taxBatches = [];
|
|
654
|
+
const taxBatches = [];
|
|
620
655
|
|
|
621
656
|
sale.dwSaleProducts.forEach((product, productIndex) => {
|
|
622
657
|
const productSum = calcDwSaleProductSum(product, sale);
|
|
@@ -631,7 +666,7 @@ function getApplicableDwTaxes(sale) {
|
|
|
631
666
|
allTaxes.forEach(tax => {
|
|
632
667
|
const taxAmount = roundCurrency(netAmount * (tax.rate / 100));
|
|
633
668
|
const taxKey = getTaxProductIndex(tax, tax.product_id);
|
|
634
|
-
|
|
669
|
+
|
|
635
670
|
const taxBatch = {
|
|
636
671
|
product_id: product.product_id,
|
|
637
672
|
product_index: productIndex,
|
|
@@ -660,9 +695,9 @@ function getApplicableDwTaxes(sale) {
|
|
|
660
695
|
});
|
|
661
696
|
|
|
662
697
|
return Object.values(taxMap).map(tax => ({
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
698
|
+
...tax,
|
|
699
|
+
amount: roundCurrency(tax.amount)
|
|
700
|
+
})).filter(tax => tax.amount > 0);
|
|
666
701
|
|
|
667
702
|
/*
|
|
668
703
|
return {
|
|
@@ -754,23 +789,23 @@ function calcTaxesByAbbreviation(sale, abbreviation) {
|
|
|
754
789
|
*/
|
|
755
790
|
function getConsolidatedTaxes(sale) {
|
|
756
791
|
validateSaleStructure(sale);
|
|
757
|
-
|
|
792
|
+
|
|
758
793
|
const taxMap = {};
|
|
759
|
-
|
|
794
|
+
|
|
760
795
|
sale.dwSaleProducts.forEach((product, productIndex) => {
|
|
761
796
|
const productSum = calcDwSaleProductSum(product, sale);
|
|
762
797
|
const discount = calcDwSaleProductDiscount(product, sale);
|
|
763
798
|
const netAmount = roundCurrency(productSum - discount);
|
|
764
|
-
|
|
799
|
+
|
|
765
800
|
// Get applicable taxes for this product
|
|
766
801
|
const specificTaxes = getProductSpecificTaxes(product, sale);
|
|
767
802
|
const generalTaxes = getGeneralTaxes(sale);
|
|
768
803
|
const allTaxes = [...specificTaxes, ...generalTaxes];
|
|
769
|
-
|
|
804
|
+
|
|
770
805
|
allTaxes.forEach(tax => {
|
|
771
806
|
const taxAmount = roundCurrency(netAmount * (tax.rate / 100));
|
|
772
807
|
const taxKey = `${tax.id}-${tax.abbreviation}-${tax.rate}`;
|
|
773
|
-
|
|
808
|
+
|
|
774
809
|
if (taxMap[taxKey]) {
|
|
775
810
|
// Merge with existing tax
|
|
776
811
|
taxMap[taxKey].amount += taxAmount;
|
|
@@ -789,7 +824,7 @@ function getConsolidatedTaxes(sale) {
|
|
|
789
824
|
}
|
|
790
825
|
});
|
|
791
826
|
});
|
|
792
|
-
|
|
827
|
+
|
|
793
828
|
// Return unified taxes sorted by amount (descending)
|
|
794
829
|
return Object.values(taxMap)
|
|
795
830
|
.map(tax => ({
|
|
@@ -835,15 +870,15 @@ function roundCurrency(value, overrideRounding) {
|
|
|
835
870
|
console.warn('roundCurrency: Invalid input', value);
|
|
836
871
|
return 0;
|
|
837
872
|
}
|
|
838
|
-
|
|
873
|
+
|
|
839
874
|
// Handle zero and very small values
|
|
840
875
|
if (Math.abs(value) < Number.EPSILON) {
|
|
841
876
|
return 0;
|
|
842
877
|
}
|
|
843
|
-
|
|
878
|
+
|
|
844
879
|
const shouldRound = overrideRounding !== undefined ? overrideRounding : useRounding;
|
|
845
880
|
let result;
|
|
846
|
-
|
|
881
|
+
|
|
847
882
|
if (shouldRound) {
|
|
848
883
|
// Rounding behavior
|
|
849
884
|
result = Math.round(value * roundingFactor) / roundingFactor;
|
|
@@ -851,7 +886,7 @@ function roundCurrency(value, overrideRounding) {
|
|
|
851
886
|
// Truncation behavior (default)
|
|
852
887
|
result = Math.trunc(value * roundingFactor) / roundingFactor;
|
|
853
888
|
}
|
|
854
|
-
|
|
889
|
+
|
|
855
890
|
// Additional precision handling
|
|
856
891
|
return parseFloat(result.toFixed(decimalPlaces));
|
|
857
892
|
}
|