diff --git a/accounts/doctype/sales_invoice/test_sales_invoice.py b/accounts/doctype/sales_invoice/test_sales_invoice.py index 46a3f26d48..86308905b5 100644 --- a/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -192,7 +192,57 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(si.doc.grand_total, 1500) self.assertEquals(si.doc.grand_total_export, 1500) - + + def test_flat_discount_gl_entry(self): + si = webnotes.bean(copy=test_records[3]) + si.doc.flat_discount = 104.95 + si.doclist.append({ + "doctype": "Sales Taxes and Charges", + "parentfield": "other_charges", + "charge_type": "On Previous Row Amount", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 10, + "row_id": 8, + "idx": 9 + }) + si.insert() + si.submit() + + gl_entries = webnotes.conn.sql("""select account, debit, credit + from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s + order by account asc""", si.doc.name, as_dict=1) + + self.assertTrue(gl_entries) + + expected_values = sorted([ + [si.doc.debit_to, 1500, 0.0], + [test_records[3][1]["income_account"], 0.0, 1163.45], + [test_records[3][3]["account_head"], 0.0, 130.31], + [test_records[3][4]["account_head"], 0.0, 2.61], + [test_records[3][5]["account_head"], 0.0, 1.31], + [test_records[3][6]["account_head"], 0.0, 25.96], + [test_records[3][7]["account_head"], 0.0, 145.43], + [test_records[3][8]["account_head"], 0.0, 116.35], + [test_records[3][9]["account_head"], 0.0, 100], + [test_records[3][10]["account_head"], 168.54, 0.0], + ["_Test Account Service Tax - _TC", 16.88, 0.0], + ]) + + for i, gle in enumerate(gl_entries): + self.assertEquals(expected_values[i][0], gle.account) + self.assertEquals(expected_values[i][1], gle.debit) + self.assertEquals(expected_values[i][2], gle.credit) + + # cancel + si.cancel() + + gle = webnotes.conn.sql("""select * from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s""", si.doc.name) + + self.assertFalse(gle) + def test_inclusive_rate_validations(self): si = webnotes.bean(copy=test_records[2]) for i, tax in enumerate(si.doclist.get({"parentfield": "other_charges"})): @@ -418,7 +468,6 @@ class TestSalesInvoice(unittest.TestCase): from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s order by account asc, debit asc""", si.doc.name, as_dict=1) self.assertTrue(gl_entries) - # print gl_entries stock_in_hand = webnotes.conn.get_value("Account", {"master_name": "_Test Warehouse - _TC"}) diff --git a/buying/doctype/purchase_common/purchase_common.js b/buying/doctype/purchase_common/purchase_common.js index 9661f6edaf..92d127b267 100644 --- a/buying/doctype/purchase_common/purchase_common.js +++ b/buying/doctype/purchase_common/purchase_common.js @@ -360,6 +360,14 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ }); } } + + if(this.frm.tax_doclist.length) { + if(!wn.meta.get_docfield(this.frm.tax_doclist[0].doctype, "tax_amount_after_flat_discount", this.frm.doctype)) { + $.each(this.frm.tax_doclist, function(i, tax) { + delete tax["tax_amount_after_flat_discount"]; + }); + } + } }, calculate_outstanding_amount: function() { diff --git a/controllers/accounts_controller.py b/controllers/accounts_controller.py index 41d4859357..bcb50da44c 100644 --- a/controllers/accounts_controller.py +++ b/controllers/accounts_controller.py @@ -227,11 +227,11 @@ class AccountsController(TransactionBase): "charge_type": tax.charge_type, }, raise_exception=True) elif tax.charge_type == "On Previous Row Amount" and \ - not cint(self.tax_doclist[tax.row_id - 1].included_in_print_rate): + not cint(self.tax_doclist[cint(tax.row_id) - 1].included_in_print_rate): # referred row should also be inclusive _on_previous_row_error(tax.row_id) elif tax.charge_type == "On Previous Row Total" and \ - not all([cint(t.included_in_print_rate) for t in self.tax_doclist[:tax.row_id - 1]]): + not all([cint(t.included_in_print_rate) for t in self.tax_doclist[:cint(tax.row_id) - 1]]): # all rows about the reffered tax should be inclusive _on_previous_row_error("1 - %d" % (tax.row_id,)) @@ -249,7 +249,7 @@ class AccountsController(TransactionBase): # Adjust divisional loss to the last item if tax.charge_type == "Actual": - actual_tax_dict[tax.idx] -= current_tax_amount; + actual_tax_dict[tax.idx] -= current_tax_amount if n == len(self.item_doclist) - 1: current_tax_amount += actual_tax_dict[tax.idx] @@ -283,24 +283,29 @@ class AccountsController(TransactionBase): tax.grand_total_for_current_item = \ flt(self.tax_doclist[i-1].grand_total_for_current_item + current_tax_amount, self.precision("total", tax)) - # in tax.total, accumulate grand total of each item tax.total += tax.grand_total_for_current_item # set precision in the last item iteration if n == len(self.item_doclist) - 1: - tax.total = flt(tax.total, self.precision("total", tax)) - tax.tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax)) - tax.tax_amount_after_flat_discount = flt(tax.tax_amount_after_flat_discount, - self.precision("tax_amount", tax)) + self.round_off_totals(tax) - # adjust discount loss in last tax iteration + # adjust flat discount loss in last tax iteration if i == (len(self.tax_doclist) - 1) and self.flat_discount_applied: - flat_discount_loss = self.doc.grand_total - self.doc.flat_discount - tax.total - tax.tax_amount_after_flat_discount = flt(tax.tax_amount_after_flat_discount + - flat_discount_loss, self.precision("tax_amount", tax)) - tax.total = flt(tax.total + flat_discount_loss, self.precision("total", tax)) + self.adjust_flat_discount_loss(tax) + + def round_off_totals(self, tax): + tax.total = flt(tax.total, self.precision("total", tax)) + tax.tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax)) + tax.tax_amount_after_flat_discount = flt(tax.tax_amount_after_flat_discount, + self.precision("tax_amount", tax)) + + def adjust_flat_discount_loss(self, tax): + flat_discount_loss = self.doc.grand_total - self.doc.flat_discount - tax.total + tax.tax_amount_after_flat_discount = flt(tax.tax_amount_after_flat_discount + + flat_discount_loss, self.precision("tax_amount", tax)) + tax.total = flt(tax.total + flat_discount_loss, self.precision("total", tax)) def get_current_tax_amount(self, item, tax, item_tax_map): tax_rate = self._get_tax_rate(tax, item_tax_map) diff --git a/public/js/transaction.js b/public/js/transaction.js index f06fc6b735..542a1c1411 100644 --- a/public/js/transaction.js +++ b/public/js/transaction.js @@ -516,7 +516,7 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ this.validate_conversion_rate(); this.frm.item_doclist = this.get_item_doclist(); this.frm.tax_doclist = this.get_tax_doclist(); - + this.calculate_item_values(); this.initialize_taxes(); this.determine_exclusive_rate && this.determine_exclusive_rate(); @@ -533,12 +533,11 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ $.each(this.frm.tax_doclist, function(i, tax) { tax.item_wise_tax_detail = {}; - tax_fields = ["total", "tax_amount_for_current_item", "grand_total_for_current_item", + tax_fields = ["total", "tax_amount_after_flat_discount", + "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"] - if (me.flat_discount_applied) - tax_fields.push("tax_amount_after_flat_discount"); - else + if (!me.flat_discount_applied) tax_fields.push("tax_amount"); $.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0 }); @@ -551,34 +550,41 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ calculate_taxes: function() { var me = this; + var actual_tax_dict = {}; + // maintain actual tax rate based on idx + $.each(this.frm.tax_doclist, function(i, tax) { + if (tax.charge_type == "Actual") { + actual_tax_dict[tax.idx] = flt(tax.rate); + } + }); + $.each(this.frm.item_doclist, function(n, item) { var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); - + $.each(me.frm.tax_doclist, 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); - me.set_item_tax_amount && me.set_item_tax_amount(item, tax, current_tax_amount); - - // case when net total is 0 but there is an actual type charge - // in this case add the actual amount to tax.tax_amount - // and tax.grand_total_for_current_item for the first such iteration - if(tax.charge_type == "Actual" && - !(current_tax_amount || me.frm.doc.net_total || tax.tax_amount)) { - var zero_net_total_adjustment = flt(tax.rate, precision("tax_amount", tax)); - current_tax_amount += zero_net_total_adjustment; + // Adjust divisional loss to the last item + if (tax.charge_type == "Actual") { + actual_tax_dict[tax.idx] -= current_tax_amount; + if (n == me.frm.item_doclist.length - 1) { + current_tax_amount += actual_tax_dict[tax.idx] } - + } + + me.set_item_tax_amount && me.set_item_tax_amount(item, tax, current_tax_amount); + // store tax_amount for current item as it will be used for // charge type = 'On Previous Row Amount' tax.tax_amount_for_current_item = current_tax_amount; // accumulate tax amount into tax.tax_amount - if (me.flat_discount_applied) - tax.tax_amount_after_flat_discount += current_tax_amount; - else + if (!me.flat_discount_applied) tax.tax_amount += current_tax_amount; + + tax.tax_amount_after_flat_discount += current_tax_amount; // for buying if(tax.category) { @@ -603,9 +609,32 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ // in tax.total, accumulate grand total for each item tax.total += tax.grand_total_for_current_item; + + // set precision in the last item iteration + if (n == me.frm.item_doclist.length - 1) { + me.round_off_totals(tax); + + // adjust flat discount loss in last tax iteration + if ((i == me.frm.tax_doclist.length - 1) && me.flat_discount_applied) + me.adjust_flat_discount_loss(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_after_flat_discount = flt(tax.tax_amount_after_flat_discount, + precision("tax_amount", tax)); + }, + + adjust_flat_discount_loss: function(tax) { + var flat_discount_loss = this.frm.doc.grand_total - this.frm.doc.flat_discount - tax.total; + tax.tax_amount_after_flat_discount = flt(tax.tax_amount_after_flat_discount + + flat_discount_loss, precision("tax_amount", tax)); + tax.total = flt(tax.total + flat_discount_loss, precision("total", tax)); + }, get_current_tax_amount: function(item, tax, item_tax_map) { var tax_rate = this._get_tax_rate(tax, item_tax_map); diff --git a/selling/sales_common.js b/selling/sales_common.js index d8fdb706b0..91ec01dfcf 100644 --- a/selling/sales_common.js +++ b/selling/sales_common.js @@ -353,7 +353,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ item.amount = flt( (item.export_amount * me.frm.doc.conversion_rate) / (1 + cumulated_tax_fraction), precision("amount", item)); - + item.basic_rate = flt(item.amount / item.qty, precision("basic_rate", item)); if(item.adj_rate == 100) { @@ -393,20 +393,19 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ calculate_net_total: function() { var me = this; - this.frm.doc.net_total = this.frm.doc.net_total_export = 0.0; + $.each(this.frm.item_doclist, function(i, item) { me.frm.doc.net_total += item.amount; me.frm.doc.net_total_export += item.export_amount; }); - + wn.model.round_floats_in(this.frm.doc, ["net_total", "net_total_export"]); }, calculate_totals: function() { var me = this; var tax_count = this.frm.tax_doclist.length; - this.total_tax_excluding_actual = 0.0; this.frm.doc.grand_total = flt( tax_count ? this.frm.tax_doclist[tax_count - 1].total : this.frm.doc.net_total, @@ -416,40 +415,54 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ this.frm.doc.other_charges_total = flt(this.frm.doc.grand_total - this.frm.doc.net_total, precision("other_charges_total")); - this.frm.doc.other_charges_total_export = flt( - this.frm.doc.grand_total_export - this.frm.doc.net_total_export + this.frm.doc.flat_discount, + this.frm.doc.other_charges_total_export = flt(this.frm.doc.grand_total_export - + this.frm.doc.net_total_export + this.frm.doc.flat_discount, precision("other_charges_total_export")); this.frm.doc.rounded_total = Math.round(this.frm.doc.grand_total); this.frm.doc.rounded_total_export = Math.round(this.frm.doc.grand_total_export); - - // calculate total amount for flat discount - $.each(this.frm.tax_doclist, function(i, tax) { - if (tax.charge_type != "Actual") { - me.total_tax_excluding_actual += flt(tax.tax_amount, precision("tax_amount", tax)); - } - }); - - this.total_amount_for_flat_discount = flt(this.frm.doc.net_total + - this.total_tax_excluding_actual, precision("grand_total")); }, apply_flat_discount: function() { var me = this; var distributed_amount = 0.0; - if (this.frm.doc.flat_discount && this.total_amount_for_flat_discount) { + if (this.frm.doc.flat_discount) { + var total_amount_for_flat_discount = this.get_flat_discountable_amount(); // calculate item amount after flat discount $.each(this.frm.item_doclist, function(i, item) { - distributed_amount = flt(me.frm.doc.flat_discount * item.amount / me.total_amount_for_flat_discount, - precision("amount", item)); - item.amount -= distributed_amount; + distributed_amount = me.frm.doc.flat_discount * item.amount / total_amount_for_flat_discount; + item.amount = flt(item.amount - distributed_amount, precision("amount", item)); }); this.flat_discount_applied = true; this.calculate_taxes_and_totals(); } }, + + get_flat_discountable_amount: function() { + var me = this; + var total_actual_tax = 0.0; + var actual_taxes_dict = {}; + + $.each(this.frm.tax_doclist, function(i, tax) { + if (tax.charge_type == "Actual") + actual_taxes_dict[tax.idx] = tax.tax_amount; + else if (actual_taxes_dict[tax.row_id] !== null) { + actual_tax_amount = flt(actual_taxes_dict[tax.row_id]) * flt(tax.rate) / 100; + actual_taxes_dict[tax.idx] = actual_tax_amount; + } + }); + + $.each(actual_taxes_dict, function(key, value) { + if (value) + total_actual_tax += value; + }); + + total_amount_for_flat_discount = flt(this.frm.doc.grand_total - total_actual_tax, + precision("grand_total")); + return total_amount_for_flat_discount; + }, calculate_outstanding_amount: function() { // NOTE: diff --git a/stock/doctype/item/item.js b/stock/doctype/item/item.js index c9aa75e7b7..6a1a7ef1c2 100644 --- a/stock/doctype/item/item.js +++ b/stock/doctype/item/item.js @@ -97,7 +97,8 @@ cur_frm.fields_dict['default_sales_cost_center'].get_query = function(doc) { cur_frm.fields_dict['item_tax'].grid.get_field("tax_type").get_query = function(doc, cdt, cdn) { return{ filters:[ - ['Account', 'account_type', 'in', 'Tax, Chargeable'], + ['Account', 'account_type', 'in', + 'Tax, Chargeable, Income Account, Expense Account'], ['Account', 'docstatus', '!=', 2] ] } diff --git a/stock/doctype/item/item.py b/stock/doctype/item/item.py index b5884b4111..0c4d5dd596 100644 --- a/stock/doctype/item/item.py +++ b/stock/doctype/item/item.py @@ -154,8 +154,8 @@ class DocType(DocListController, WebsiteGenerator): if d.tax_type: account_type = webnotes.conn.get_value("Account", d.tax_type, "account_type") - if account_type not in ['Tax', 'Chargeable']: - msgprint("'%s' is not Tax / Chargeable Account" % d.tax_type, raise_exception=1) + if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']: + msgprint("'%s' is not Tax / Chargeable / Income / Expense Account" % d.tax_type, raise_exception=1) else: if d.tax_type in check_list: msgprint("Rate is entered twice for: '%s'" % d.tax_type, raise_exception=1)