fix: improved rounding adjustment when applying discount (backport #46720)

* fix: improved rounding adjustment when applying discount (#46720)

* fix: rounding adjustment in apply_discount_amount taxes_and_totals

* refactor: minor changes

* fix: set the rounding difference while calculating tax total in the last tax row and add test case

* fix: failing test case

* fix: made changes in get_total_for_discount_amount in taxes_and_totals

* fix: failing test cases

* fix: changes as per review

* refactor: remove unnecessary use of flt

* refactor: improve logic

* refactor: minor change

* refactor: minor changes

* fix: add a test case for applying discount with previous row total in taxes

* fix: failing test case

* refactor: flatter code, remove `flt` usage for accuracy

---------

Co-authored-by: Vishakh Desai <78500008+vishakhdesai@users.noreply.github.com>
Co-authored-by: Sagar Vora <sagar@resilient.tech>
This commit is contained in:
mergify[bot]
2025-03-31 15:54:42 +05:30
committed by GitHub
parent 37adb9187f
commit 7b864bece8
3 changed files with 162 additions and 57 deletions

View File

@@ -343,7 +343,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
calculate_taxes() {
var me = this;
this.frm.doc.rounding_adjustment = 0;
var actual_tax_dict = {};
// maintain actual tax rate based on idx
@@ -605,7 +604,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
this.frm.doc.total_taxes_and_charges = flt(this.frm.doc.grand_total - this.frm.doc.net_total
- flt(this.frm.doc.rounding_adjustment), precision("total_taxes_and_charges"));
- flt(this.frm.doc.grand_total_diff), precision("total_taxes_and_charges"));
this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges", "rounding_adjustment"]);
@@ -627,6 +626,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if (cint(disable_rounded_total)) {
this.frm.doc.rounded_total = 0;
this.frm.doc.base_rounded_total = 0;
this.frm.doc.rounding_adjustment = 0;
return;
}
@@ -695,22 +695,26 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
return;
}
var total_for_discount_amount = this.get_total_for_discount_amount();
var net_total = 0;
const total_for_discount_amount = this.get_total_for_discount_amount();
let net_total = 0;
let expected_net_total = 0;
// calculate item amount after Discount Amount
if (total_for_discount_amount) {
$.each(this.frm._items || [], function(i, item) {
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
item.net_amount = flt(item.net_amount - distributed_amount, precision("net_amount", item));
const adjusted_net_amount = item.net_amount - distributed_amount;
expected_net_total += adjusted_net_amount
item.net_amount = flt(adjusted_net_amount, precision("net_amount", item));
net_total += item.net_amount;
// discount amount rounding loss adjustment if no taxes
if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total"))
&& i == (me.frm._items || []).length - 1) {
var discount_amount_loss = flt(me.frm.doc.net_total - net_total
- me.frm.doc.discount_amount, precision("net_total"));
item.net_amount = flt(item.net_amount + discount_amount_loss,
precision("net_amount", item));
// discount amount rounding adjustment
// assignment to rounding_difference is intentional
const rounding_difference = flt(expected_net_total - net_total, precision("net_total"));
if (rounding_difference) {
item.net_amount = flt(item.net_amount + rounding_difference, precision("net_amount", item));
net_total += rounding_difference;
}
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
me.set_in_company_currency(item, ["net_rate", "net_amount"]);
@@ -723,29 +727,38 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
get_total_for_discount_amount() {
if(this.frm.doc.apply_discount_on == "Net Total") {
if(this.frm.doc.apply_discount_on == "Net Total")
return this.frm.doc.net_total;
} else {
var total_actual_tax = 0.0;
var actual_taxes_dict = {};
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (["Actual", "On Item Quantity"].includes(tax.charge_type)) {
var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
actual_taxes_dict[tax.idx] = tax_amount;
} else if (actual_taxes_dict[tax.row_id] !== null) {
var actual_tax_amount = flt(actual_taxes_dict[tax.row_id]) * flt(tax.rate) / 100;
actual_taxes_dict[tax.idx] = actual_tax_amount;
}
});
let total_actual_tax = 0.0;
let actual_taxes_dict = {};
$.each(actual_taxes_dict, function(key, value) {
if (value) total_actual_tax += value;
});
function update_actual_taxes_dict(tax, tax_amount) {
if (tax.add_deduct_tax == "Deduct") tax_amount *= -1;
if (tax.category != "Valuation") total_actual_tax += tax_amount;
return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
actual_taxes_dict[tax.idx] = {
tax_amount: tax_amount,
cumulative_total: total_actual_tax
};
}
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
if (["Actual", "On Item Quantity"].includes(tax.charge_type)) {
update_actual_taxes_dict(tax, tax.tax_amount);
return;
}
const base_row = actual_taxes_dict[tax.row_id];
if (!base_row) return;
// if charge type is 'On Previous Row Amount', calculate tax on previous row amount
// else (On Previous Row Total) calculate tax on cumulative total
const base_tax_amount = tax.charge_type == "On Previous Row Amount" ? base_row["tax_amount"]: base_row["cumulative_total"];
update_actual_taxes_dict(tax, base_tax_amount * tax.rate / 100);
});
return this.frm.doc.grand_total - total_actual_tax;
}
calculate_total_advance(update_paid_amount) {