Rounding issue in tax calculation (#10135)

* Adjust rounding loss of discount in the last item row. Fixes #8952

* Fixed rounding issue in tax calculation. Fixes #8953, #8952, #8893, #6954, #8910

* Rounding related fixes for purchase cycle
This commit is contained in:
Nabin Hait 2017-07-31 18:07:45 +05:30 committed by Makarand Bauskar
parent 1fb4abc322
commit cd95134267
3 changed files with 206 additions and 124 deletions

View File

@ -167,15 +167,15 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account S&H Education Cess - _TC": [1.5, 1619.5, 0.03, 32.39], "_Test Account S&H Education Cess - _TC": [1.5, 1619.5, 0.03, 32.39],
"_Test Account CST - _TC": [32.5, 1652, 0.65, 33.04], "_Test Account CST - _TC": [32.5, 1652, 0.65, 33.04],
"_Test Account VAT - _TC": [156.5, 1808.5, 3.13, 36.17], "_Test Account VAT - _TC": [156.5, 1808.5, 3.13, 36.17],
"_Test Account Discount - _TC": [-180.5, 1628, -3.61, 32.56] "_Test Account Discount - _TC": [-181.0, 1627.5, -3.62, 32.55]
} }
for d in si.get("taxes"): for d in si.get("taxes"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.base_grand_total, 1628) self.assertEquals(si.base_grand_total, 1627.5)
self.assertEquals(si.grand_total, 32.56) self.assertEquals(si.grand_total, 32.55)
def test_sales_invoice_with_discount_and_inclusive_tax(self): def test_sales_invoice_with_discount_and_inclusive_tax(self):
si = create_sales_invoice(qty=100, rate=50, do_not_save=True) si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
@ -235,21 +235,29 @@ class TestSalesInvoice(unittest.TestCase):
"item_code": "_Test Item Home Desktop 100", "item_code": "_Test Item Home Desktop 100",
"price_list_rate": 62.5, "price_list_rate": 62.5,
"discount_percentage": 0, "discount_percentage": 0,
"rate": 62.5, "amount": 625, "rate": 62.5,
"amount": 625,
"base_price_list_rate": 62.5, "base_price_list_rate": 62.5,
"base_rate": 62.5, "base_amount": 625, "base_rate": 62.5,
"net_rate": 46.54, "net_amount": 465.37, "base_amount": 625,
"base_net_rate": 46.54, "base_net_amount": 465.37 "net_rate": 46.54,
"net_amount": 465.37,
"base_net_rate": 46.54,
"base_net_amount": 465.37
}, },
{ {
"item_code": "_Test Item Home Desktop 200", "item_code": "_Test Item Home Desktop 200",
"price_list_rate": 190.66, "price_list_rate": 190.66,
"discount_percentage": 0, "discount_percentage": 0,
"rate": 190.66, "amount": 953.3, "rate": 190.66,
"amount": 953.3,
"base_price_list_rate": 190.66, "base_price_list_rate": 190.66,
"base_rate": 190.66, "base_amount": 953.3, "base_rate": 190.66,
"net_rate": 139.62, "net_amount": 698.08, "base_amount": 953.3,
"base_net_rate": 139.62, "base_net_amount": 698.08 "net_rate": 139.62,
"net_amount": 698.08,
"base_net_rate": 139.62,
"base_net_amount": 698.08
} }
] ]
@ -270,13 +278,13 @@ class TestSalesInvoice(unittest.TestCase):
"keys": ["tax_amount", "tax_amount_after_discount_amount", "total"], "keys": ["tax_amount", "tax_amount_after_discount_amount", "total"],
"_Test Account Excise Duty - _TC": [140, 130.31, 1293.76], "_Test Account Excise Duty - _TC": [140, 130.31, 1293.76],
"_Test Account Education Cess - _TC": [2.8, 2.61, 1296.37], "_Test Account Education Cess - _TC": [2.8, 2.61, 1296.37],
"_Test Account S&H Education Cess - _TC": [1.4, 1.31, 1297.68], "_Test Account S&H Education Cess - _TC": [1.4, 1.30, 1297.67],
"_Test Account CST - _TC": [27.88, 25.96, 1323.64], "_Test Account CST - _TC": [27.88, 25.95, 1323.62],
"_Test Account VAT - _TC": [156.25, 145.43, 1469.07], "_Test Account VAT - _TC": [156.25, 145.43, 1469.05],
"_Test Account Customs Duty - _TC": [125, 116.35, 1585.42], "_Test Account Customs Duty - _TC": [125, 116.35, 1585.40],
"_Test Account Shipping Charges - _TC": [100, 100, 1685.42], "_Test Account Shipping Charges - _TC": [100, 100, 1685.40],
"_Test Account Discount - _TC": [-180.33, -168.54, 1516.88], "_Test Account Discount - _TC": [-180.33, -168.54, 1516.86],
"_Test Account Service Tax - _TC": [-18.03, -16.88, 1500] "_Test Account Service Tax - _TC": [-18.03, -16.86, 1500]
} }
for d in si.get("taxes"): for d in si.get("taxes"):
@ -312,13 +320,13 @@ class TestSalesInvoice(unittest.TestCase):
[test_records[3]["items"][0]["income_account"], 0.0, 1163.45], [test_records[3]["items"][0]["income_account"], 0.0, 1163.45],
[test_records[3]["taxes"][0]["account_head"], 0.0, 130.31], [test_records[3]["taxes"][0]["account_head"], 0.0, 130.31],
[test_records[3]["taxes"][1]["account_head"], 0.0, 2.61], [test_records[3]["taxes"][1]["account_head"], 0.0, 2.61],
[test_records[3]["taxes"][2]["account_head"], 0.0, 1.31], [test_records[3]["taxes"][2]["account_head"], 0.0, 1.30],
[test_records[3]["taxes"][3]["account_head"], 0.0, 25.96], [test_records[3]["taxes"][3]["account_head"], 0.0, 25.95],
[test_records[3]["taxes"][4]["account_head"], 0.0, 145.43], [test_records[3]["taxes"][4]["account_head"], 0.0, 145.43],
[test_records[3]["taxes"][5]["account_head"], 0.0, 116.35], [test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
[test_records[3]["taxes"][6]["account_head"], 0.0, 100], [test_records[3]["taxes"][6]["account_head"], 0.0, 100],
[test_records[3]["taxes"][7]["account_head"], 168.54, 0.0], [test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
["_Test Account Service Tax - _TC", 16.88, 0.0], ["_Test Account Service Tax - _TC", 16.86, 0.0]
]) ])
for gle in gl_entries: for gle in gl_entries:
@ -334,6 +342,61 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(gle) self.assertFalse(gle)
def test_tax_calculation_with_multiple_items(self):
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
item_row = si.get("items")[0]
for qty in (54, 288, 144, 430):
item_row_copy = copy.deepcopy(item_row)
item_row_copy.qty = qty
si.append("items", item_row_copy)
si.append("taxes", {
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 19
})
si.insert()
self.assertEquals(si.net_total, 4600)
self.assertEquals(si.get("taxes")[0].tax_amount, 874.0)
self.assertEquals(si.get("taxes")[0].total, 5474.0)
self.assertEquals(si.grand_total, 5474.0)
def test_tax_calculation_with_multiple_items_and_discount(self):
si = create_sales_invoice(qty=1, rate=75, do_not_save=True)
item_row = si.get("items")[0]
for rate in (500, 200, 100, 50, 50):
item_row_copy = copy.deepcopy(item_row)
item_row_copy.price_list_rate = rate
item_row_copy.rate = rate
si.append("items", item_row_copy)
si.apply_discount_on = "Net Total"
si.discount_amount = 75.0
si.append("taxes", {
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 24
})
si.insert()
self.assertEquals(si.total, 975)
self.assertEquals(si.net_total, 900)
self.assertEquals(si.get("taxes")[0].tax_amount, 216.0)
self.assertEquals(si.get("taxes")[0].total, 1116.0)
self.assertEquals(si.grand_total, 1116.0)
def test_inclusive_rate_validations(self): def test_inclusive_rate_validations(self):
si = frappe.copy_doc(test_records[2]) si = frappe.copy_doc(test_records[2])
for i, tax in enumerate(si.get("taxes")): for i, tax in enumerate(si.get("taxes")):
@ -416,21 +479,29 @@ class TestSalesInvoice(unittest.TestCase):
"item_code": "_Test Item Home Desktop 100", "item_code": "_Test Item Home Desktop 100",
"price_list_rate": 55.56, "price_list_rate": 55.56,
"discount_percentage": 10, "discount_percentage": 10,
"rate": 50, "amount": 500, "rate": 50,
"amount": 500,
"base_price_list_rate": 2778, "base_price_list_rate": 2778,
"base_rate": 2500, "base_amount": 25000, "base_rate": 2500,
"net_rate": 40, "net_amount": 399.98, "base_amount": 25000,
"base_net_rate": 2000, "base_net_amount": 19999 "net_rate": 40,
"net_amount": 399.98,
"base_net_rate": 2000,
"base_net_amount": 19999
}, },
{ {
"item_code": "_Test Item Home Desktop 200", "item_code": "_Test Item Home Desktop 200",
"price_list_rate": 187.5, "price_list_rate": 187.5,
"discount_percentage": 20, "discount_percentage": 20,
"rate": 150, "amount": 750, "rate": 150,
"amount": 750,
"base_price_list_rate": 9375, "base_price_list_rate": 9375,
"base_rate": 7500, "base_amount": 37500, "base_rate": 7500,
"net_rate": 118.01, "net_amount": 590.05, "base_amount": 37500,
"base_net_rate": 5900.5, "base_net_amount": 29502.5 "net_rate": 118.01,
"net_amount": 590.05,
"base_net_rate": 5900.5,
"base_net_amount": 29502.5
} }
] ]
@ -450,22 +521,22 @@ class TestSalesInvoice(unittest.TestCase):
# check tax calculation # check tax calculation
expected_values = { expected_values = {
"keys": ["base_tax_amount", "base_total", "tax_amount", "total"], "keys": ["base_tax_amount", "base_total", "tax_amount", "total"],
"_Test Account Excise Duty - _TC": [5540.5, 55042, 110.81, 1100.84], "_Test Account Excise Duty - _TC": [5540.0, 55041.5, 110.80, 1100.83],
"_Test Account Education Cess - _TC": [111, 55153, 2.22, 1103.06], "_Test Account Education Cess - _TC": [111, 55152.5, 2.22, 1103.05],
"_Test Account S&H Education Cess - _TC": [55.5, 55208.5, 1.11, 1104.17], "_Test Account S&H Education Cess - _TC": [55.5, 55208.0, 1.11, 1104.16],
"_Test Account CST - _TC": [1104, 56312.5, 22.08, 1126.25], "_Test Account CST - _TC": [1104, 56312.0, 22.08, 1126.24],
"_Test Account VAT - _TC": [6188, 62500.5, 123.76, 1250.01], "_Test Account VAT - _TC": [6187.5, 62499.5, 123.75, 1249.99],
"_Test Account Customs Duty - _TC": [4950.5, 67451, 99.01, 1349.02], "_Test Account Customs Duty - _TC": [4950.0, 67449.5, 99.0, 1348.99],
"_Test Account Shipping Charges - _TC": [ 100, 67551, 2, 1351.02], "_Test Account Shipping Charges - _TC": [ 100, 67549.5, 2, 1350.99],
"_Test Account Discount - _TC": [ -6755, 60796, -135.10, 1215.92] "_Test Account Discount - _TC": [ -6755, 60794.5, -135.10, 1215.89]
} }
for d in si.get("taxes"): for d in si.get("taxes"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.base_grand_total, 60796) self.assertEquals(si.base_grand_total, 60794.5)
self.assertEquals(si.grand_total, 1215.92) self.assertEquals(si.grand_total, 1215.89)
def test_outstanding(self): def test_outstanding(self):
w = self.make() w = self.make()
@ -910,19 +981,20 @@ class TestSalesInvoice(unittest.TestCase):
"_Test Account Excise Duty - _TC": [70, 70, 70], "_Test Account Excise Duty - _TC": [70, 70, 70],
"_Test Account Education Cess - _TC": [1.4, 1.4, 1.4], "_Test Account Education Cess - _TC": [1.4, 1.4, 1.4],
"_Test Account S&H Education Cess - _TC": [.7, 0.7, 0.7], "_Test Account S&H Education Cess - _TC": [.7, 0.7, 0.7],
"_Test Account CST - _TC": [17.2, 17.2, 17.2], "_Test Account CST - _TC": [17.19, 17.19, 17.19],
"_Test Account VAT - _TC": [78.13, 78.13, 78.13], "_Test Account VAT - _TC": [78.13, 78.13, 78.13],
"_Test Account Discount - _TC": [-95.49, -95.49, -95.49] "_Test Account Discount - _TC": [-95.49, -95.49, -95.49]
} }
for d in si.get("taxes"): for d in si.get("taxes"):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.account_head][i]) if expected_values.get(d.account_head):
self.assertEquals(d.get(k), expected_values[d.account_head][i])
self.assertEquals(si.total_taxes_and_charges, 234.44) self.assertEquals(si.total_taxes_and_charges, 234.43)
self.assertEquals(si.base_grand_total, 859.44) self.assertEquals(si.base_grand_total, 859.43)
self.assertEquals(si.grand_total, 859.44) self.assertEquals(si.grand_total, 859.43)
def test_multi_currency_gle(self): def test_multi_currency_gle(self):
set_perpetual_inventory(0) set_perpetual_inventory(0)

View File

@ -179,7 +179,6 @@ class calculate_taxes_and_totals(object):
for n, item in enumerate(self.doc.get("items")): for n, item in enumerate(self.doc.get("items")):
item_tax_map = self._load_item_tax_rate(item.item_tax_rate) item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
for i, tax in enumerate(self.doc.get("taxes")): for i, tax in enumerate(self.doc.get("taxes")):
# tax_amount represents the amount of tax for the current step # tax_amount represents the amount of tax for the current step
current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map) current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
@ -202,36 +201,45 @@ class calculate_taxes_and_totals(object):
# set tax after discount # set tax after discount
tax.tax_amount_after_discount_amount += current_tax_amount tax.tax_amount_after_discount_amount += current_tax_amount
if getattr(tax, "category", None): current_tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(current_tax_amount, tax)
# if just for valuation, do not add the tax amount in total
# hence, setting it as 0 for further steps
current_tax_amount = 0.0 if (tax.category == "Valuation") \
else current_tax_amount
current_tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
# Calculate tax.total viz. grand total till that step
# note: grand_total_for_current_item contains the contribution of # note: grand_total_for_current_item contains the contribution of
# item's amount, previously applied tax and the current tax on that item # item's amount, previously applied tax and the current tax on that item
if i==0: if i==0:
tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount, tax.precision("total")) tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
else: else:
tax.grand_total_for_current_item = \ tax.grand_total_for_current_item = \
flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount, tax.precision("total")) flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
# in tax.total, accumulate grand total of each item
tax.total += tax.grand_total_for_current_item
# set precision in the last item iteration # set precision in the last item iteration
if n == len(self.doc.get("items")) - 1: if n == len(self.doc.get("items")) - 1:
self.round_off_totals(tax) self.round_off_totals(tax)
self.set_cumulative_total(i, tax)
self._set_in_company_currency(tax,
["total", "tax_amount", "tax_amount_after_discount_amount"])
# adjust Discount Amount loss in last tax iteration # adjust Discount Amount loss in last tax iteration
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ 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": and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
self.adjust_discount_amount_loss(tax) self.adjust_discount_amount_loss(tax)
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
if getattr(tax, "category", None):
tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
return tax_amount
def set_cumulative_total(self, row_idx, tax):
tax_amount = tax.tax_amount_after_discount_amount
tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax_amount, tax)
if row_idx == 0:
tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
else:
tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
def get_current_tax_amount(self, item, tax, item_tax_map): def get_current_tax_amount(self, item, tax, item_tax_map):
tax_rate = self._get_tax_rate(tax, item_tax_map) tax_rate = self._get_tax_rate(tax, item_tax_map)
@ -251,8 +259,6 @@ class calculate_taxes_and_totals(object):
current_tax_amount = (tax_rate / 100.0) * \ current_tax_amount = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
current_tax_amount = flt(current_tax_amount, tax.precision("tax_amount"))
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
return current_tax_amount return current_tax_amount
@ -267,11 +273,9 @@ class calculate_taxes_and_totals(object):
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount, tax.precision("base_tax_amount"))] tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount, tax.precision("base_tax_amount"))]
def round_off_totals(self, tax): def round_off_totals(self, tax):
tax.total = flt(tax.total, tax.precision("total"))
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount")) tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount")) tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
tax.precision("tax_amount"))
self._set_in_company_currency(tax, ["total", "tax_amount", "tax_amount_after_discount_amount"])
def adjust_discount_amount_loss(self, tax): def adjust_discount_amount_loss(self, tax):
discount_amount_loss = self.doc.grand_total - flt(self.doc.discount_amount) - tax.total discount_amount_loss = self.doc.grand_total - flt(self.doc.discount_amount) - tax.total

View File

@ -5,7 +5,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
setup: function() {}, setup: function() {},
apply_pricing_rule_on_item: function(item){ apply_pricing_rule_on_item: function(item){
if(item.margin_type == "Percentage"){ if(item.margin_type == "Percentage"){
item.rate_with_margin = flt(item.price_list_rate) item.rate_with_margin = flt(item.price_list_rate)
+ flt(item.price_list_rate) * ( flt(item.margin_rate_or_amount) / 100); + flt(item.price_list_rate) * ( flt(item.margin_rate_or_amount) / 100);
} else { } else {
item.rate_with_margin = flt(item.price_list_rate) + flt(item.margin_rate_or_amount); item.rate_with_margin = flt(item.price_list_rate) + flt(item.margin_rate_or_amount);
@ -70,12 +70,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} else { } else {
frappe.throw(repl('%(conversion_rate_label)s' + frappe.throw(repl('%(conversion_rate_label)s' +
__(' is mandatory. Maybe Currency Exchange record is not created for ') + __(' is mandatory. Maybe Currency Exchange record is not created for ') +
'%(from_currency)s' + __(" to ") + '%(to_currency)s', '%(from_currency)s' + __(" to ") + '%(to_currency)s', {
{ "conversion_rate_label": conversion_rate_label,
"conversion_rate_label": conversion_rate_label, "from_currency": this.frm.doc.currency,
"from_currency": this.frm.doc.currency, "to_currency": company_currency
"to_currency": company_currency }));
}));
} }
} }
@ -101,7 +100,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
var me = this; var me = this;
$.each(fields, function(i, f) { $.each(fields, function(i, f) {
doc["base_"+f] = flt(flt(doc[f], precision(f, doc)) * me.frm.doc.conversion_rate, precision("base_" + f, doc)); doc["base_"+f] = flt(flt(doc[f], precision(f, doc)) * me.frm.doc.conversion_rate, precision("base_" + f, doc));
}) });
}, },
initialize_taxes: function() { initialize_taxes: function() {
@ -111,14 +110,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
tax.item_wise_tax_detail = {}; tax.item_wise_tax_detail = {};
var tax_fields = ["total", "tax_amount_after_discount_amount", var tax_fields = ["total", "tax_amount_after_discount_amount",
"tax_amount_for_current_item", "grand_total_for_current_item", "tax_amount_for_current_item", "grand_total_for_current_item",
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"];
if (cstr(tax.charge_type) != "Actual" && if (cstr(tax.charge_type) != "Actual" &&
!(me.discount_amount_applied && me.frm.doc.apply_discount_on=="Grand Total")) { !(me.discount_amount_applied && me.frm.doc.apply_discount_on=="Grand Total")) {
tax_fields.push("tax_amount"); tax_fields.push("tax_amount");
} }
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0 }); $.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
if (!this.discount_amount_applied && cur_frm) { if (!this.discount_amount_applied && cur_frm) {
cur_frm.cscript.validate_taxes_and_charges(tax.doctype, tax.name); cur_frm.cscript.validate_taxes_and_charges(tax.doctype, tax.name);
@ -134,7 +133,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
var has_inclusive_tax = false; var has_inclusive_tax = false;
$.each(me.frm.doc["taxes"] || [], function(i, row) { $.each(me.frm.doc["taxes"] || [], function(i, row) {
if(cint(row.included_in_print_rate)) has_inclusive_tax = true; if(cint(row.included_in_print_rate)) has_inclusive_tax = true;
}) });
if(has_inclusive_tax==false) return; if(has_inclusive_tax==false) return;
$.each(me.frm.doc["items"] || [], function(n, item) { $.each(me.frm.doc["items"] || [], function(n, item) {
@ -223,7 +222,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
$.each(this.frm.doc["items"] || [], function(n, item) { $.each(this.frm.doc["items"] || [], function(n, item) {
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
$.each(me.frm.doc["taxes"] || [], function(i, tax) { $.each(me.frm.doc["taxes"] || [], function(i, tax) {
// tax_amount represents the amount of tax for the current step // 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); var current_tax_amount = me.get_current_tax_amount(item, tax, item_tax_map);
@ -232,7 +230,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
if (tax.charge_type == "Actual") { if (tax.charge_type == "Actual") {
actual_tax_dict[tax.idx] -= current_tax_amount; actual_tax_dict[tax.idx] -= current_tax_amount;
if (n == me.frm.doc["items"].length - 1) { if (n == me.frm.doc["items"].length - 1) {
current_tax_amount += actual_tax_dict[tax.idx] current_tax_amount += actual_tax_dict[tax.idx];
} }
} }
@ -258,23 +256,25 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0; current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
} }
// Calculate tax.total viz. grand total till that step
// note: grand_total_for_current_item contains the contribution of // note: grand_total_for_current_item contains the contribution of
// item's amount, previously applied tax and the current tax on that item // item's amount, previously applied tax and the current tax on that item
if(i==0) { if(i==0) {
tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount, precision("total", tax)); tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount);
} else { } else {
tax.grand_total_for_current_item = tax.grand_total_for_current_item =
flt(me.frm.doc["taxes"][i-1].grand_total_for_current_item + current_tax_amount, precision("total", tax)); flt(me.frm.doc["taxes"][i-1].grand_total_for_current_item + current_tax_amount);
} }
// in tax.total, accumulate grand total for each item
tax.total += tax.grand_total_for_current_item;
// set precision in the last item iteration // set precision in the last item iteration
if (n == me.frm.doc["items"].length - 1) { if (n == me.frm.doc["items"].length - 1) {
me.round_off_totals(tax); me.round_off_totals(tax);
// in tax.total, accumulate grand total for each item
tax.total = me.set_cumulative_total(i, tax);
this.set_in_company_currency(tax,
["total", "tax_amount", "tax_amount_after_discount_amount"]);
// adjust Discount Amount loss in last tax iteration // adjust Discount Amount loss in last tax iteration
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied 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.frm.doc.apply_discount_on == "Grand Total" && me.frm.doc.discount_amount)
@ -284,6 +284,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
}); });
}, },
set_cumulative_total: function(row_idx, tax) {
if(row_idx==0) {
tax.total = flt(this.frm.doc.net_total + tax.tax_amount_after_discount_amount,
precision("total", tax));
} else {
tax.total = flt(this.frm.doc.get("taxes")[row_idx-1].total + tax.tax_amount_after_discount_amount,
precision("total", tax));
}
},
_load_item_tax_rate: function(item_tax_rate) { _load_item_tax_rate: function(item_tax_rate) {
return item_tax_rate ? JSON.parse(item_tax_rate) : {}; return item_tax_rate ? JSON.parse(item_tax_rate) : {};
}, },
@ -300,7 +310,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} else if(tax.charge_type == "On Net Total") { } else if(tax.charge_type == "On Net Total") {
current_tax_amount = (tax_rate / 100.0) * item.net_amount; current_tax_amount = (tax_rate / 100.0) * item.net_amount;
} else if(tax.charge_type == "On Previous Row Amount") { } else if(tax.charge_type == "On Previous Row Amount") {
current_tax_amount = (tax_rate / 100.0) * current_tax_amount = (tax_rate / 100.0) *
this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount_for_current_item; this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount_for_current_item;
@ -310,8 +319,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item; this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item;
} }
current_tax_amount = flt(current_tax_amount, precision("tax_amount", tax));
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
return current_tax_amount; return current_tax_amount;
@ -322,18 +329,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
var key = item.item_code || item.item_name; var key = item.item_code || item.item_name;
var item_wise_tax_amount = current_tax_amount * this.frm.doc.conversion_rate; var item_wise_tax_amount = current_tax_amount * this.frm.doc.conversion_rate;
if (tax.item_wise_tax_detail && tax.item_wise_tax_detail[key]) if (tax.item_wise_tax_detail && tax.item_wise_tax_detail[key])
item_wise_tax_amount += tax.item_wise_tax_detail[key][1] item_wise_tax_amount += tax.item_wise_tax_detail[key][1];
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount, precision("base_tax_amount", tax))]
tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount, precision("base_tax_amount", tax))];
}, },
round_off_totals: function(tax) { round_off_totals: function(tax) {
tax.total = flt(tax.total, precision("total", tax));
tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax)); tax.tax_amount = flt(tax.tax_amount, precision("tax_amount", tax));
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, precision("tax_amount", tax)); tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount, precision("tax_amount", tax));
this.set_in_company_currency(tax, ["total", "tax_amount", "tax_amount_after_discount_amount"]);
}, },
adjust_discount_amount_loss: function(tax) { adjust_discount_amount_loss: function(tax) {
@ -391,7 +394,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount); me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount);
} }
} }
}) });
frappe.model.round_floats_in(this.frm.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]); frappe.model.round_floats_in(this.frm.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]);
} }
@ -437,7 +440,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
if(this.frm.doc["taxes"] && this.frm.doc["taxes"].length) { if(this.frm.doc["taxes"] && this.frm.doc["taxes"].length) {
var temporary_fields = ["tax_amount_for_current_item", "grand_total_for_current_item", var temporary_fields = ["tax_amount_for_current_item", "grand_total_for_current_item",
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"];
if(!frappe.meta.get_docfield(this.frm.doc["taxes"][0].doctype, "tax_amount_after_discount_amount", this.frm.doctype)) { if(!frappe.meta.get_docfield(this.frm.doc["taxes"][0].doctype, "tax_amount_after_discount_amount", this.frm.doctype)) {
temporary_fields.push("tax_amount_after_discount_amount"); temporary_fields.push("tax_amount_after_discount_amount");
@ -473,13 +476,24 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
precision("base_discount_amount")); precision("base_discount_amount"));
var total_for_discount_amount = this.get_total_for_discount_amount(); var total_for_discount_amount = this.get_total_for_discount_amount();
var net_total = 0;
// calculate item amount after Discount Amount // calculate item amount after Discount Amount
if (total_for_discount_amount) { if (total_for_discount_amount) {
$.each(this.frm.doc["items"] || [], function(i, item) { $.each(this.frm.doc["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, precision("base_amount", item)); item.net_amount = flt(item.net_amount - distributed_amount,
item.net_rate = flt(item.net_amount / item.qty, precision("net_rate", item)); precision("base_amount", item));
net_total += item.net_amount;
// discount amount rounding loss adjustment if no taxes
if ((!(me.frm.doc.taxes || []).length || (me.frm.doc.apply_discount_on == "Net Total"))
&& i == (me.frm.doc.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));
}
item.net_rate = flt(item.net_amount / item.qty, precision("net_rate", item));
me.set_in_company_currency(item, ["net_rate", "net_amount"]); me.set_in_company_currency(item, ["net_rate", "net_amount"]);
}); });
@ -490,10 +504,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
}, },
get_total_for_discount_amount: function() { get_total_for_discount_amount: function() {
var me = this;
if(this.frm.doc.apply_discount_on == "Net Total") { if(this.frm.doc.apply_discount_on == "Net Total") {
return this.frm.doc.net_total return this.frm.doc.net_total;
} else { } else {
var total_actual_tax = 0.0; var total_actual_tax = 0.0;
var actual_taxes_dict = {}; var actual_taxes_dict = {};
@ -517,7 +529,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
calculate_total_advance: function(update_paid_amount) { calculate_total_advance: function(update_paid_amount) {
var total_allocated_amount = frappe.utils.sum($.map(this.frm.doc["advances"] || [], function(adv) { var total_allocated_amount = frappe.utils.sum($.map(this.frm.doc["advances"] || [], function(adv) {
return flt(adv.allocated_amount, precision("allocated_amount", adv)) return flt(adv.allocated_amount, precision("allocated_amount", adv));
})); }));
this.frm.doc.total_advance = flt(total_allocated_amount, precision("total_advance")); this.frm.doc.total_advance = flt(total_allocated_amount, precision("total_advance"));
@ -528,9 +540,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
// NOTE: // NOTE:
// paid_amount and write_off_amount is only for POS Invoice // paid_amount and write_off_amount is only for POS Invoice
// total_advance is only for non POS Invoice // total_advance is only for non POS Invoice
if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_return){ if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_return){
this.calculate_paid_amount() this.calculate_paid_amount();
} }
if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return; if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return;
@ -558,25 +570,21 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} }
if(this.frm.doc.doctype == "Sales Invoice"){ if(this.frm.doc.doctype == "Sales Invoice"){
this.set_default_payment(total_amount_to_pay, update_paid_amount) this.set_default_payment(total_amount_to_pay, update_paid_amount);
this.calculate_paid_amount() this.calculate_paid_amount();
} }
this.calculate_change_amount() this.calculate_change_amount();
var outstanding_amount = 0.0
var paid_amount = (this.frm.doc.party_account_currency == this.frm.doc.currency) ? var paid_amount = (this.frm.doc.party_account_currency == this.frm.doc.currency) ?
this.frm.doc.paid_amount : this.frm.doc.base_paid_amount; this.frm.doc.paid_amount : this.frm.doc.base_paid_amount;
var change_amount = (this.frm.doc.party_account_currency == this.frm.doc.currency) ?
this.frm.doc.change_amount : this.frm.doc.base_change_amount;
this.frm.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + this.frm.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) +
flt(this.frm.doc.change_amount * this.frm.doc.conversion_rate), precision("outstanding_amount")); flt(this.frm.doc.change_amount * this.frm.doc.conversion_rate), precision("outstanding_amount"));
} else if(this.frm.doc.doctype == "Purchase Invoice") { } else if(this.frm.doc.doctype == "Purchase Invoice") {
this.frm.doc.outstanding_amount = flt(total_amount_to_pay, precision("outstanding_amount")); this.frm.doc.outstanding_amount = flt(total_amount_to_pay, precision("outstanding_amount"));
} }
}, },
set_default_payment: function(total_amount_to_pay, update_paid_amount){ set_default_payment: function(total_amount_to_pay, update_paid_amount){
@ -591,7 +599,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
}else if(me.frm.doc.paid_amount){ }else if(me.frm.doc.paid_amount){
data.amount = 0.0; data.amount = 0.0;
} }
}) });
} }
}, },
@ -604,7 +612,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
data.base_amount = flt(data.amount * me.frm.doc.conversion_rate, precision("base_amount")); data.base_amount = flt(data.amount * me.frm.doc.conversion_rate, precision("base_amount"));
paid_amount += data.amount; paid_amount += data.amount;
base_paid_amount += data.base_amount; base_paid_amount += data.base_amount;
}) });
} else if(!this.frm.doc.is_return){ } else if(!this.frm.doc.is_return){
this.frm.doc.payments = []; this.frm.doc.payments = [];
} }
@ -617,30 +625,28 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.frm.doc.change_amount = 0.0; this.frm.doc.change_amount = 0.0;
this.frm.doc.base_change_amount = 0.0; this.frm.doc.base_change_amount = 0.0;
if(this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) { if(this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) {
var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type }); var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; });
if (in_list(payment_types, 'Cash')) { if (in_list(payment_types, 'Cash')) {
this.frm.doc.change_amount = flt(this.frm.doc.paid_amount - this.frm.doc.grand_total + this.frm.doc.change_amount = flt(this.frm.doc.paid_amount - this.frm.doc.grand_total +
this.frm.doc.write_off_amount, precision("change_amount")); this.frm.doc.write_off_amount, precision("change_amount"));
this.frm.doc.base_change_amount = flt(this.frm.doc.base_paid_amount - this.frm.doc.base_change_amount = flt(this.frm.doc.base_paid_amount -
this.frm.doc.base_grand_total + this.frm.doc.base_write_off_amount, this.frm.doc.base_grand_total + this.frm.doc.base_write_off_amount,
precision("base_change_amount")); precision("base_change_amount"));
} }
} }
}, },
calculate_write_off_amount: function(){ calculate_write_off_amount: function(){
if(this.frm.doc.paid_amount > this.frm.doc.grand_total){ if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
this.frm.doc.write_off_amount = flt(this.frm.doc.grand_total - this.frm.doc.paid_amount + this.frm.doc.change_amount, this.frm.doc.write_off_amount = flt(this.frm.doc.grand_total - this.frm.doc.paid_amount
precision("write_off_amount")) + this.frm.doc.change_amount, precision("write_off_amount"));
this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate, this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate,
precision("base_write_off_amount")); precision("base_write_off_amount"));
}else{ }else{
this.frm.doc.paid_amount = 0.0 this.frm.doc.paid_amount = 0.0;
} }
this.calculate_outstanding_amount(false);
this.calculate_outstanding_amount(false)
} }
}) });