Merge pull request #47155 from sagarvora/fix-dr-cr-mismatch
fix: get total without rounding off tax amounts for distributing discount
This commit is contained in:
@@ -2705,13 +2705,13 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
|
||||
To test if after applying discount on grand total,
|
||||
the grand total is calculated correctly without any rounding errors
|
||||
"""
|
||||
invoice = make_purchase_invoice(qty=2, rate=100, do_not_save=True, do_not_submit=True)
|
||||
invoice = make_purchase_invoice(qty=3, rate=100, do_not_save=True, do_not_submit=True)
|
||||
invoice.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"qty": 1,
|
||||
"rate": 21.39,
|
||||
"qty": 3,
|
||||
"rate": 50.3,
|
||||
},
|
||||
)
|
||||
invoice.append(
|
||||
@@ -2720,18 +2720,19 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"description": "VAT",
|
||||
"rate": 15.5,
|
||||
"rate": 15,
|
||||
},
|
||||
)
|
||||
|
||||
# the grand total here will be 255.71
|
||||
# the grand total here will be 518.54
|
||||
invoice.disable_rounded_total = 1
|
||||
# apply discount on grand total to adjust the grand total to 255
|
||||
invoice.discount_amount = 0.71
|
||||
# apply discount on grand total to adjust the grand total to 518
|
||||
invoice.discount_amount = 0.54
|
||||
|
||||
invoice.save()
|
||||
|
||||
# check if grand total is 496 and not something like 254.99 due to rounding errors
|
||||
self.assertEqual(invoice.grand_total, 255)
|
||||
# check if grand total is 518 and not something like 517.99 due to rounding errors
|
||||
self.assertEqual(invoice.grand_total, 518)
|
||||
|
||||
def test_apply_discount_on_grand_total_with_previous_row_total_tax(self):
|
||||
"""
|
||||
|
||||
@@ -21,8 +21,6 @@ from erpnext.deprecation_dumpster import deprecated
|
||||
from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template, get_item_tax_map
|
||||
from erpnext.utilities.regional import temporary_flag
|
||||
|
||||
logger = frappe.logger(__name__)
|
||||
|
||||
ItemWiseTaxDetail = frappe._dict
|
||||
|
||||
|
||||
@@ -384,22 +382,22 @@ class calculate_taxes_and_totals:
|
||||
self._calculate()
|
||||
|
||||
def calculate_taxes(self):
|
||||
self.grand_total_diff = 0
|
||||
doc = self.doc
|
||||
if not doc.get("taxes"):
|
||||
return
|
||||
|
||||
# maintain actual tax rate based on idx
|
||||
actual_tax_dict = dict(
|
||||
[
|
||||
[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||
for tax in self.doc.get("taxes")
|
||||
for tax in doc.taxes
|
||||
if tax.charge_type == "Actual"
|
||||
]
|
||||
)
|
||||
|
||||
logger.debug(f"{self.doc} ...")
|
||||
for n, item in enumerate(self._items):
|
||||
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
|
||||
logger.debug(f" Item {n}: {item.item_code}" + (f" - {item_tax_map}" if item_tax_map else ""))
|
||||
for i, tax in enumerate(self.doc.get("taxes")):
|
||||
for i, tax in enumerate(doc.taxes):
|
||||
# tax_amount represents the amount of tax for the current step
|
||||
current_net_amount, current_tax_amount = self.get_current_tax_and_net_amount(
|
||||
item, tax, item_tax_map
|
||||
@@ -438,37 +436,42 @@ class calculate_taxes_and_totals:
|
||||
tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
|
||||
else:
|
||||
tax.grand_total_for_current_item = flt(
|
||||
self.doc.get("taxes")[i - 1].grand_total_for_current_item + current_tax_amount
|
||||
doc.taxes[i - 1].grand_total_for_current_item + current_tax_amount
|
||||
)
|
||||
|
||||
# set precision in the last item iteration
|
||||
if n == len(self._items) - 1:
|
||||
self.round_off_totals(tax)
|
||||
self._set_in_company_currency(
|
||||
tax, ["tax_amount", "tax_amount_after_discount_amount", "net_amount"]
|
||||
discount_amount_applied = self.discount_amount_applied
|
||||
if doc.apply_discount_on == "Grand Total" and (
|
||||
discount_amount_applied or doc.discount_amount or doc.additional_discount_percentage
|
||||
):
|
||||
tax_amount_precision = doc.taxes[0].precision("tax_amount")
|
||||
|
||||
for i, tax in enumerate(doc.taxes):
|
||||
if discount_amount_applied:
|
||||
tax.tax_amount_after_discount_amount = flt(
|
||||
tax.tax_amount_after_discount_amount, tax_amount_precision
|
||||
)
|
||||
|
||||
self.round_off_base_values(tax)
|
||||
self.set_cumulative_total(i, tax)
|
||||
self.set_cumulative_total(i, tax)
|
||||
|
||||
self._set_in_company_currency(tax, ["total"])
|
||||
|
||||
# adjust Discount Amount loss in last tax iteration
|
||||
if (
|
||||
i == (len(self.doc.get("taxes")) - 1)
|
||||
and self.discount_amount_applied
|
||||
and self.doc.discount_amount
|
||||
and self.doc.apply_discount_on == "Grand Total"
|
||||
):
|
||||
self.grand_total_diff = flt(
|
||||
self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
|
||||
self.doc.precision("rounding_adjustment"),
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f" net_amount: {current_net_amount:<20} tax_amount: {current_tax_amount:<20} - {tax.description}"
|
||||
if not discount_amount_applied:
|
||||
self.grand_total_for_distributing_discount = doc.taxes[-1].total
|
||||
else:
|
||||
self.grand_total_diff = flt(
|
||||
self.grand_total_for_distributing_discount - doc.discount_amount - doc.taxes[-1].total,
|
||||
doc.precision("grand_total"),
|
||||
)
|
||||
|
||||
for i, tax in enumerate(doc.taxes):
|
||||
self.round_off_totals(tax)
|
||||
self._set_in_company_currency(
|
||||
tax, ["tax_amount", "tax_amount_after_discount_amount", "net_amount"]
|
||||
)
|
||||
|
||||
self.round_off_base_values(tax)
|
||||
self.set_cumulative_total(i, tax)
|
||||
|
||||
self._set_in_company_currency(tax, ["total"])
|
||||
|
||||
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
|
||||
# if just for valuation, do not add the tax amount in total
|
||||
# if tax/charges is for deduction, multiply by -1
|
||||
@@ -612,16 +615,20 @@ class calculate_taxes_and_totals:
|
||||
|
||||
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
|
||||
self.grand_total_diff = diff
|
||||
else:
|
||||
self.grand_total_diff = 0
|
||||
|
||||
def calculate_totals(self):
|
||||
grand_total_diff = getattr(self, "grand_total_diff", 0)
|
||||
|
||||
if self.doc.get("taxes"):
|
||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + self.grand_total_diff
|
||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + grand_total_diff
|
||||
else:
|
||||
self.doc.grand_total = flt(self.doc.net_total)
|
||||
|
||||
if self.doc.get("taxes"):
|
||||
self.doc.total_taxes_and_charges = flt(
|
||||
self.doc.grand_total - self.doc.net_total - self.grand_total_diff,
|
||||
self.doc.grand_total - self.doc.net_total - grand_total_diff,
|
||||
self.doc.precision("total_taxes_and_charges"),
|
||||
)
|
||||
else:
|
||||
@@ -766,7 +773,8 @@ class calculate_taxes_and_totals:
|
||||
self.doc.base_discount_amount = 0
|
||||
|
||||
def get_total_for_discount_amount(self):
|
||||
if self.doc.apply_discount_on == "Net Total":
|
||||
doc = self.doc
|
||||
if doc.apply_discount_on == "Net Total" or not doc.get("taxes"):
|
||||
return self.doc.net_total
|
||||
|
||||
total_actual_tax = 0
|
||||
@@ -786,7 +794,7 @@ class calculate_taxes_and_totals:
|
||||
"cumulative_tax_amount": total_actual_tax,
|
||||
}
|
||||
|
||||
for tax in self.doc.get("taxes"):
|
||||
for tax in doc.taxes:
|
||||
if tax.charge_type in ["Actual", "On Item Quantity"]:
|
||||
update_actual_tax_dict(tax, tax.tax_amount)
|
||||
continue
|
||||
@@ -805,7 +813,7 @@ class calculate_taxes_and_totals:
|
||||
)
|
||||
update_actual_tax_dict(tax, base_tax_amount * tax.rate / 100)
|
||||
|
||||
return self.doc.grand_total - total_actual_tax
|
||||
return getattr(self, "grand_total_for_distributing_discount", doc.grand_total) - total_actual_tax
|
||||
|
||||
def calculate_total_advance(self):
|
||||
if not self.doc.docstatus.is_cancelled():
|
||||
|
||||
@@ -343,12 +343,14 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
calculate_taxes() {
|
||||
const doc = this.frm.doc;
|
||||
if (!doc.taxes?.length) return;
|
||||
|
||||
var me = this;
|
||||
this.grand_total_diff = 0;
|
||||
var actual_tax_dict = {};
|
||||
|
||||
// maintain actual tax rate based on idx
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
$.each(doc.taxes, function(i, tax) {
|
||||
if (tax.charge_type == "Actual") {
|
||||
actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax));
|
||||
}
|
||||
@@ -356,7 +358,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
$.each(this.frm._items || [], function(n, item) {
|
||||
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
||||
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
||||
$.each(doc.taxes, function(i, tax) {
|
||||
// tax_amount represents the amount of tax for the current step
|
||||
var current_tax_amount = me.get_current_tax_amount(item, tax, item_tax_map);
|
||||
if (frappe.flags.round_row_wise_tax) {
|
||||
@@ -401,29 +403,40 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
tax.grand_total_for_current_item =
|
||||
flt(me.frm.doc["taxes"][i-1].grand_total_for_current_item + current_tax_amount);
|
||||
}
|
||||
|
||||
// set precision in the last item iteration
|
||||
if (n == me.frm._items.length - 1) {
|
||||
me.round_off_totals(tax);
|
||||
me.set_in_company_currency(tax,
|
||||
["tax_amount", "tax_amount_after_discount_amount"]);
|
||||
|
||||
me.round_off_base_values(tax);
|
||||
|
||||
// in tax.total, accumulate grand total for each item
|
||||
me.set_cumulative_total(i, tax);
|
||||
|
||||
me.set_in_company_currency(tax, ["total"]);
|
||||
|
||||
// adjust Discount Amount loss in last tax iteration
|
||||
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied
|
||||
&& me.frm.doc.apply_discount_on == "Grand Total" && me.frm.doc.discount_amount) {
|
||||
me.grand_total_diff = flt(me.frm.doc.grand_total -
|
||||
flt(me.frm.doc.discount_amount) - tax.total, precision("rounding_adjustment"));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const discount_amount_applied = this.discount_amount_applied;
|
||||
if (doc.apply_discount_on === "Grand Total" && (discount_amount_applied || doc.discount_amount || doc.additional_discount_percentage)) {
|
||||
const tax_amount_precision = precision("tax_amount", doc.taxes[0]);
|
||||
|
||||
for (const [i, tax] of doc.taxes.entries()) {
|
||||
if (discount_amount_applied)
|
||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, tax_amount_precision);
|
||||
|
||||
this.set_cumulative_total(i, tax);
|
||||
}
|
||||
|
||||
if (!this.discount_amount_applied) {
|
||||
this.grand_total_for_distributing_discount = doc.taxes[doc.taxes.length - 1].total;
|
||||
} else {
|
||||
this.grand_total_diff = flt(
|
||||
this.grand_total_for_distributing_discount - doc.discount_amount - doc.taxes[doc.taxes.length - 1].total, precision("grand_total"));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [i, tax] of doc.taxes.entries()) {
|
||||
me.round_off_totals(tax);
|
||||
me.set_in_company_currency(tax,
|
||||
["tax_amount", "tax_amount_after_discount_amount"]);
|
||||
|
||||
me.round_off_base_values(tax);
|
||||
|
||||
// in tax.total, accumulate grand total for each tax
|
||||
me.set_cumulative_total(i, tax);
|
||||
|
||||
me.set_in_company_currency(tax, ["total"]);
|
||||
}
|
||||
}
|
||||
|
||||
set_cumulative_total(row_idx, tax) {
|
||||
@@ -586,10 +599,12 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
calculate_totals() {
|
||||
// Changing sequence can cause rounding_adjustmentng issue and on-screen discrepency
|
||||
var me = this;
|
||||
var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0;
|
||||
const me = this;
|
||||
const tax_count = this.frm.doc.taxes?.length;
|
||||
const grand_total_diff = this.grand_total_diff || 0;
|
||||
|
||||
this.frm.doc.grand_total = flt(tax_count
|
||||
? this.frm.doc["taxes"][tax_count - 1].total + this.grand_total_diff
|
||||
? this.frm.doc["taxes"][tax_count - 1].total + grand_total_diff
|
||||
: this.frm.doc.net_total);
|
||||
|
||||
if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
|
||||
@@ -621,7 +636,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
|
||||
- this.grand_total_diff, precision("total_taxes_and_charges"));
|
||||
- grand_total_diff, precision("total_taxes_and_charges"));
|
||||
|
||||
this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges"]);
|
||||
|
||||
@@ -744,8 +759,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
get_total_for_discount_amount() {
|
||||
if(this.frm.doc.apply_discount_on == "Net Total")
|
||||
return this.frm.doc.net_total;
|
||||
const doc = this.frm.doc;
|
||||
|
||||
if (doc.apply_discount_on == "Net Total" || !doc.taxes?.length)
|
||||
return doc.net_total;
|
||||
|
||||
let total_actual_tax = 0.0;
|
||||
let actual_taxes_dict = {};
|
||||
@@ -760,7 +777,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
};
|
||||
}
|
||||
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
doc.taxes.forEach(tax => {
|
||||
if (["Actual", "On Item Quantity"].includes(tax.charge_type)) {
|
||||
update_actual_taxes_dict(tax, tax.tax_amount);
|
||||
return;
|
||||
@@ -775,7 +792,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
update_actual_taxes_dict(tax, base_tax_amount * tax.rate / 100);
|
||||
});
|
||||
|
||||
return this.frm.doc.grand_total - total_actual_tax;
|
||||
return (this.grand_total_for_distributing_discount || doc.grand_total) - total_actual_tax;
|
||||
}
|
||||
|
||||
calculate_total_advance(update_paid_amount) {
|
||||
|
||||
Reference in New Issue
Block a user