Merge pull request #44040 from ruthra-kumar/manual_backport_pr_39481

fix: specify precision for net_amount (backport #39481)
This commit is contained in:
ruthra kumar
2024-11-11 17:11:11 +05:30
committed by GitHub
6 changed files with 80 additions and 49 deletions

View File

@@ -93,7 +93,7 @@ class TestPOSInvoice(unittest.TestCase):
inv.save() inv.save()
self.assertEqual(inv.net_total, 4298.25) self.assertEqual(inv.net_total, 4298.24)
self.assertEqual(inv.grand_total, 4900.00) self.assertEqual(inv.grand_total, 4900.00)
def test_tax_calculation_with_multiple_items(self): def test_tax_calculation_with_multiple_items(self):

View File

@@ -343,7 +343,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
inv.load_from_db() inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.status, "Return") self.assertEqual(consolidated_invoice.status, "Return")
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001) self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
finally: finally:
frappe.set_user("Administrator") frappe.set_user("Administrator")

View File

@@ -314,7 +314,8 @@ class TestSalesInvoice(FrappeTestCase):
si.insert() si.insert()
# with inclusive tax # with inclusive tax
self.assertEqual(si.items[0].net_amount, 3947.368421052631) self.assertEqual(si.items[0].net_amount, 3947.37)
self.assertEqual(si.net_total, si.base_net_total)
self.assertEqual(si.net_total, 3947.37) self.assertEqual(si.net_total, 3947.37)
self.assertEqual(si.grand_total, 5000) self.assertEqual(si.grand_total, 5000)
@@ -658,7 +659,7 @@ class TestSalesInvoice(FrappeTestCase):
62.5, 62.5,
625.0, 625.0,
50, 50,
499.97600115194473, 499.98,
], ],
"_Test Item Home Desktop 200": [ "_Test Item Home Desktop 200": [
190.66, 190.66,
@@ -669,7 +670,7 @@ class TestSalesInvoice(FrappeTestCase):
190.66, 190.66,
953.3, 953.3,
150, 150,
749.9968530500239, 750,
], ],
} }
@@ -682,20 +683,21 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(d.get(k), expected_values[d.item_code][i]) self.assertEqual(d.get(k), expected_values[d.item_code][i])
# check net total # check net total
self.assertEqual(si.net_total, 1249.97) self.assertEqual(si.base_net_total, si.net_total)
self.assertEqual(si.net_total, 1249.98)
self.assertEqual(si.total, 1578.3) self.assertEqual(si.total, 1578.3)
# check tax calculation # check tax calculation
expected_values = { expected_values = {
"keys": ["tax_amount", "total"], "keys": ["tax_amount", "total"],
"_Test Account Excise Duty - _TC": [140, 1389.97], "_Test Account Excise Duty - _TC": [140, 1389.98],
"_Test Account Education Cess - _TC": [2.8, 1392.77], "_Test Account Education Cess - _TC": [2.8, 1392.78],
"_Test Account S&H Education Cess - _TC": [1.4, 1394.17], "_Test Account S&H Education Cess - _TC": [1.4, 1394.18],
"_Test Account CST - _TC": [27.88, 1422.05], "_Test Account CST - _TC": [27.88, 1422.06],
"_Test Account VAT - _TC": [156.25, 1578.30], "_Test Account VAT - _TC": [156.25, 1578.31],
"_Test Account Customs Duty - _TC": [125, 1703.30], "_Test Account Customs Duty - _TC": [125, 1703.31],
"_Test Account Shipping Charges - _TC": [100, 1803.30], "_Test Account Shipping Charges - _TC": [100, 1803.31],
"_Test Account Discount - _TC": [-180.33, 1622.97], "_Test Account Discount - _TC": [-180.33, 1622.98],
} }
for d in si.get("taxes"): for d in si.get("taxes"):
@@ -731,7 +733,7 @@ class TestSalesInvoice(FrappeTestCase):
"base_rate": 2500, "base_rate": 2500,
"base_amount": 25000, "base_amount": 25000,
"net_rate": 40, "net_rate": 40,
"net_amount": 399.9808009215558, "net_amount": 399.98,
"base_net_rate": 2000, "base_net_rate": 2000,
"base_net_amount": 19999, "base_net_amount": 19999,
}, },
@@ -745,7 +747,7 @@ class TestSalesInvoice(FrappeTestCase):
"base_rate": 7500, "base_rate": 7500,
"base_amount": 37500, "base_amount": 37500,
"net_rate": 118.01, "net_rate": 118.01,
"net_amount": 590.0531205155963, "net_amount": 590.05,
"base_net_rate": 5900.5, "base_net_rate": 5900.5,
"base_net_amount": 29502.5, "base_net_amount": 29502.5,
}, },
@@ -783,8 +785,13 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(si.base_grand_total, 60795) self.assertEqual(si.base_grand_total, 60795)
self.assertEqual(si.grand_total, 1215.90) self.assertEqual(si.grand_total, 1215.90)
self.assertEqual(si.rounding_adjustment, 0.01) # no rounding adjustment as the Smallest Currency Fraction Value of USD is 0.01
self.assertEqual(si.base_rounding_adjustment, 0.50) if frappe.db.get_value("Currency", "USD", "smallest_currency_fraction_value") < 0.01:
self.assertEqual(si.rounding_adjustment, 0.10)
self.assertEqual(si.base_rounding_adjustment, 5.0)
else:
self.assertEqual(si.rounding_adjustment, 0.0)
self.assertEqual(si.base_rounding_adjustment, 0.0)
def test_outstanding(self): def test_outstanding(self):
w = self.make() w = self.make()
@@ -2172,7 +2179,7 @@ class TestSalesInvoice(FrappeTestCase):
def test_rounding_adjustment_2(self): def test_rounding_adjustment_2(self):
si = create_sales_invoice(rate=400, do_not_save=True) si = create_sales_invoice(rate=400, do_not_save=True)
for rate in [400, 600, 100]: for rate in [400.25, 600.30, 100.65]:
si.append( si.append(
"items", "items",
{ {
@@ -2198,18 +2205,19 @@ class TestSalesInvoice(FrappeTestCase):
) )
si.save() si.save()
si.submit() si.submit()
self.assertEqual(si.net_total, 1271.19) self.assertEqual(si.net_total, si.base_net_total)
self.assertEqual(si.grand_total, 1500) self.assertEqual(si.net_total, 1272.20)
self.assertEqual(si.total_taxes_and_charges, 228.82) self.assertEqual(si.grand_total, 1501.20)
self.assertEqual(si.rounding_adjustment, -0.01) self.assertEqual(si.total_taxes_and_charges, 229)
self.assertEqual(si.rounding_adjustment, -0.20)
round_off_account = frappe.get_cached_value("Company", "_Test Company", "round_off_account") round_off_account = frappe.get_cached_value("Company", "_Test Company", "round_off_account")
expected_values = { expected_values = {
"_Test Account Service Tax - _TC": [0.0, 114.41], "_Test Account Service Tax - _TC": [0.0, 114.50],
"_Test Account VAT - _TC": [0.0, 114.41], "_Test Account VAT - _TC": [0.0, 114.50],
si.debit_to: [1500, 0.0], si.debit_to: [1501, 0.0],
round_off_account: [0.01, 0.01], round_off_account: [0.20, 0.0],
"Sales - _TC": [0.0, 1271.18], "Sales - _TC": [0.0, 1272.20],
} }
gl_entries = frappe.db.sql( gl_entries = frappe.db.sql(
@@ -2267,7 +2275,8 @@ class TestSalesInvoice(FrappeTestCase):
si.save() si.save()
si.submit() si.submit()
self.assertEqual(si.net_total, 4007.16) self.assertEqual(si.net_total, si.base_net_total)
self.assertEqual(si.net_total, 4007.15)
self.assertEqual(si.grand_total, 4488.02) self.assertEqual(si.grand_total, 4488.02)
self.assertEqual(si.total_taxes_and_charges, 480.86) self.assertEqual(si.total_taxes_and_charges, 480.86)
self.assertEqual(si.rounding_adjustment, -0.02) self.assertEqual(si.rounding_adjustment, -0.02)
@@ -2280,7 +2289,7 @@ class TestSalesInvoice(FrappeTestCase):
["_Test Account Service Tax - _TC", 0.0, 240.43], ["_Test Account Service Tax - _TC", 0.0, 240.43],
["_Test Account VAT - _TC", 0.0, 240.43], ["_Test Account VAT - _TC", 0.0, 240.43],
["Sales - _TC", 0.0, 4007.15], ["Sales - _TC", 0.0, 4007.15],
[round_off_account, 0.02, 0.01], [round_off_account, 0.01, 0.0],
] ]
) )

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _, scrub from frappe import _, scrub
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
from frappe.utils.deprecations import deprecated
import erpnext import erpnext
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
@@ -74,7 +75,7 @@ class calculate_taxes_and_totals:
self.calculate_net_total() self.calculate_net_total()
self.calculate_tax_withholding_net_total() self.calculate_tax_withholding_net_total()
self.calculate_taxes() self.calculate_taxes()
self.manipulate_grand_total_for_inclusive_tax() self.adjust_grand_total_for_inclusive_tax()
self.calculate_totals() self.calculate_totals()
self._cleanup() self._cleanup()
self.calculate_total_net_weight() self.calculate_total_net_weight()
@@ -286,7 +287,7 @@ class calculate_taxes_and_totals:
): ):
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
item.net_amount = flt(amount / (1 + cumulated_tax_fraction)) item.net_amount = flt(amount / (1 + cumulated_tax_fraction), item.precision("net_amount"))
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
item.discount_percentage = flt( item.discount_percentage = flt(
item.discount_percentage, item.precision("discount_percentage") item.discount_percentage, item.precision("discount_percentage")
@@ -531,7 +532,12 @@ class calculate_taxes_and_totals:
tax.base_tax_amount = round(tax.base_tax_amount, 0) tax.base_tax_amount = round(tax.base_tax_amount, 0)
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0) tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
@deprecated
def manipulate_grand_total_for_inclusive_tax(self): def manipulate_grand_total_for_inclusive_tax(self):
# for backward compatablility - if in case used by an external application
return self.adjust_grand_total_for_inclusive_tax()
def adjust_grand_total_for_inclusive_tax(self):
# if fully inclusive taxes and diff # if fully inclusive taxes and diff
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")): if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
last_tax = self.doc.get("taxes")[-1] last_tax = self.doc.get("taxes")[-1]
@@ -553,17 +559,21 @@ class calculate_taxes_and_totals:
diff = flt(diff, self.doc.precision("rounding_adjustment")) diff = flt(diff, self.doc.precision("rounding_adjustment"))
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")): if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
self.doc.rounding_adjustment = diff self.doc.grand_total_diff = diff
else:
self.doc.grand_total_diff = 0
def calculate_totals(self): def calculate_totals(self):
if self.doc.get("taxes"): if self.doc.get("taxes"):
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(
self.doc.get("grand_total_diff")
)
else: else:
self.doc.grand_total = flt(self.doc.net_total) self.doc.grand_total = flt(self.doc.net_total)
if self.doc.get("taxes"): if self.doc.get("taxes"):
self.doc.total_taxes_and_charges = flt( self.doc.total_taxes_and_charges = flt(
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), self.doc.grand_total - self.doc.net_total - flt(self.doc.get("grand_total_diff")),
self.doc.precision("total_taxes_and_charges"), self.doc.precision("total_taxes_and_charges"),
) )
else: else:
@@ -626,8 +636,8 @@ class calculate_taxes_and_totals:
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total") self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
) )
# if print_in_rate is set, we would have already calculated rounding adjustment # rounding adjustment should always be the difference vetween grand and rounded total
self.doc.rounding_adjustment += flt( self.doc.rounding_adjustment = flt(
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment") self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
) )

View File

@@ -160,7 +160,7 @@ erpnext.accounts.taxes = {
let tax = frappe.get_doc(cdt, cdn); let tax = frappe.get_doc(cdt, cdn);
try { try {
me.validate_taxes_and_charges(cdt, cdn); me.validate_taxes_and_charges(cdt, cdn);
me.validate_inclusive_tax(tax); me.validate_inclusive_tax(tax, frm);
} catch(e) { } catch(e) {
tax.included_in_print_rate = 0; tax.included_in_print_rate = 0;
refresh_field("included_in_print_rate", tax.name, tax.parentfield); refresh_field("included_in_print_rate", tax.name, tax.parentfield);
@@ -170,7 +170,8 @@ erpnext.accounts.taxes = {
}); });
}, },
validate_inclusive_tax: function(tax) { validate_inclusive_tax: function(tax, frm) {
this.frm = this.frm || frm;
let actual_type_error = function() { let actual_type_error = function() {
var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx]) var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
frappe.throw(msg); frappe.throw(msg);
@@ -186,12 +187,12 @@ erpnext.accounts.taxes = {
if(tax.charge_type == "Actual") { if(tax.charge_type == "Actual") {
// inclusive tax cannot be of type Actual // inclusive tax cannot be of type Actual
actual_type_error(); actual_type_error();
} else if(tax.charge_type == "On Previous Row Amount" && } else if(tax.charge_type == "On Previous Row Amount" && this.frm &&
!cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate) !cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate)
) { ) {
// referred row should also be an inclusive tax // referred row should also be an inclusive tax
on_previous_row_error(tax.row_id); on_previous_row_error(tax.row_id);
} else if(tax.charge_type == "On Previous Row Total") { } else if(tax.charge_type == "On Previous Row Total" && this.frm) {
var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id), var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id),
function(t) { return cint(t.included_in_print_rate) ? null : t; }); function(t) { return cint(t.included_in_print_rate) ? null : t; });
if(taxes_not_included.length > 0) { if(taxes_not_included.length > 0) {

View File

@@ -103,7 +103,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.determine_exclusive_rate(); this.determine_exclusive_rate();
this.calculate_net_total(); this.calculate_net_total();
this.calculate_taxes(); this.calculate_taxes();
this.manipulate_grand_total_for_inclusive_tax(); this.adjust_grand_total_for_inclusive_tax();
this.calculate_totals(); this.calculate_totals();
this._cleanup(); this._cleanup();
} }
@@ -185,7 +185,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if (!this.discount_amount_applied) { if (!this.discount_amount_applied) {
erpnext.accounts.taxes.validate_taxes_and_charges(tax.doctype, tax.name); erpnext.accounts.taxes.validate_taxes_and_charges(tax.doctype, tax.name);
erpnext.accounts.taxes.validate_inclusive_tax(tax); erpnext.accounts.taxes.validate_inclusive_tax(tax, this.frm);
} }
frappe.model.round_floats_in(tax); frappe.model.round_floats_in(tax);
}); });
@@ -250,7 +250,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) { if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) {
var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty; var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty;
item.net_amount = flt(amount / (1 + cumulated_tax_fraction)); item.net_amount = flt(amount / (1 + cumulated_tax_fraction), precision("net_amount", item));
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; 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"]); me.set_in_company_currency(item, ["net_rate", "net_amount"]);
@@ -305,6 +305,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
me.frm.doc.net_total += item.net_amount; me.frm.doc.net_total += item.net_amount;
me.frm.doc.base_net_total += item.base_net_amount; me.frm.doc.base_net_total += item.base_net_amount;
}); });
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
} }
calculate_shipping_charges() { calculate_shipping_charges() {
@@ -523,7 +525,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
} }
} }
/**
* @deprecated Use adjust_grand_total_for_inclusive_tax instead.
*/
manipulate_grand_total_for_inclusive_tax() { manipulate_grand_total_for_inclusive_tax() {
// for backward compatablility - if in case used by an external application
this.adjust_grand_total_for_inclusive_tax()
}
adjust_grand_total_for_inclusive_tax() {
var me = this; var me = this;
// if fully inclusive taxes and diff // if fully inclusive taxes and diff
if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) { if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) {
@@ -550,7 +560,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
diff = flt(diff, precision("rounding_adjustment")); diff = flt(diff, precision("rounding_adjustment"));
if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) { if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) {
me.frm.doc.rounding_adjustment = diff; me.frm.doc.grand_total_diff = diff;
} else {
me.frm.doc.grand_total_diff = 0;
} }
} }
} }
@@ -561,7 +573,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
var me = this; var me = this;
var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0; var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0;
this.frm.doc.grand_total = flt(tax_count this.frm.doc.grand_total = flt(tax_count
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment) ? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff)
: this.frm.doc.net_total); : this.frm.doc.net_total);
if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) { if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
@@ -621,7 +633,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) { if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total, this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total,
this.frm.doc.currency, precision("rounded_total")); this.frm.doc.currency, precision("rounded_total"));
this.frm.doc.rounding_adjustment += flt(this.frm.doc.rounded_total - this.frm.doc.grand_total, this.frm.doc.rounding_adjustment = flt(this.frm.doc.rounded_total - this.frm.doc.grand_total,
precision("rounding_adjustment")); precision("rounding_adjustment"));
this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]); this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
@@ -689,8 +701,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if (total_for_discount_amount) { if (total_for_discount_amount) {
$.each(this.frm._items || [], function(i, item) { $.each(this.frm._items || [], function(i, item) {
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount; distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
item.net_amount = flt(item.net_amount - distributed_amount, item.net_amount = flt(item.net_amount - distributed_amount, precision("net_amount", item));
precision("base_amount", item));
net_total += item.net_amount; net_total += item.net_amount;
// discount amount rounding loss adjustment if no taxes // discount amount rounding loss adjustment if no taxes