From 63aa5fd67351e3478819d316f89f44aa0a4e73f4 Mon Sep 17 00:00:00 2001 From: Akhilesh Darjee Date: Tue, 24 Dec 2013 20:30:03 +0530 Subject: [PATCH] [flat discount] implemented flat discount on server side --- .../sales_invoice/test_sales_invoice.py | 86 +++++++++++-------- controllers/accounts_controller.py | 50 +++++++---- controllers/selling_controller.py | 42 +++++---- 3 files changed, 108 insertions(+), 70 deletions(-) diff --git a/accounts/doctype/sales_invoice/test_sales_invoice.py b/accounts/doctype/sales_invoice/test_sales_invoice.py index bc6d01ce606..46a3f26d488 100644 --- a/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -16,7 +16,7 @@ class TestSalesInvoice(unittest.TestCase): w.submit() return w - def atest_double_submission(self): + def test_double_submission(self): w = webnotes.bean(copy=test_records[0]) w.doc.docstatus = '0' w.insert() @@ -27,7 +27,7 @@ class TestSalesInvoice(unittest.TestCase): w = webnotes.bean(w2) self.assertRaises(DocstatusTransitionError, w.submit) - def atest_timestamp_change(self): + def test_timestamp_change(self): w = webnotes.bean(copy=test_records[0]) w.doc.docstatus = '0' w.insert() @@ -42,7 +42,7 @@ class TestSalesInvoice(unittest.TestCase): time.sleep(1) self.assertRaises(TimestampMismatchError, w2.save) - def atest_sales_invoice_calculation_base_currency(self): + def test_sales_invoice_calculation_base_currency(self): si = webnotes.bean(copy=test_records[2]) si.insert() @@ -86,7 +86,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(si.doc.grand_total, 1627.05) self.assertEquals(si.doc.grand_total_export, 1627.05) - def atest_sales_invoice_calculation_export_currency(self): + def test_sales_invoice_calculation_export_currency(self): si = webnotes.bean(copy=test_records[2]) si.doc.currency = "USD" si.doc.conversion_rate = 50 @@ -138,14 +138,25 @@ class TestSalesInvoice(unittest.TestCase): def test_sales_invoice_flat_discount(self): si = webnotes.bean(copy=test_records[3]) - si.doc.flat_discount = 22.98 + 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() expected_values = { "keys": ["ref_rate", "adj_rate", "export_rate", "export_amount", "base_ref_rate", "basic_rate", "amount"], - "_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 492.44], - "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 738.68], + "_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 50, 50, 465.37], + "_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 150, 150, 698.08], } # check if children are saved @@ -158,30 +169,31 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(d.fields.get(k), expected_values[d.item_code][i]) # check net total - self.assertEquals(si.doc.net_total, 1231.12) + self.assertEquals(si.doc.net_total, 1163.45) self.assertEquals(si.doc.net_total_export, 1578.3) # check tax calculation expected_values = { "keys": ["tax_amount", "tax_amount_after_flat_discount", "total"], - "_Test Account Excise Duty - _TC": [140, 137.89, 1369.01], - "_Test Account Education Cess - _TC": [2.8, 2.76, 1371.77], - "_Test Account S&H Education Cess - _TC": [1.4, 1.38, 1373.15], - "_Test Account CST - _TC": [27.88, 27.46, 1400.61], - "_Test Account VAT - _TC": [156.25, 153.89, 1554.5], - "_Test Account Customs Duty - _TC": [125, 123.11, 1677.61], - "_Test Account Shipping Charges - _TC": [100, 100, 1777.61], - "_Test Account Discount - _TC": [-180.33, -177.61, 1600] + "_Test Account Excise Duty - _TC": [140, 130.31, 1293.76], + "_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 CST - _TC": [27.88, 25.96, 1323.64], + "_Test Account VAT - _TC": [156.25, 145.43, 1469.07], + "_Test Account Customs Duty - _TC": [125, 116.35, 1585.42], + "_Test Account Shipping Charges - _TC": [100, 100, 1685.42], + "_Test Account Discount - _TC": [-180.33, -168.54, 1516.88], + "_Test Account Service Tax - _TC": [-18.03, -16.88, 1500] } for d in si.doclist.get({"parentfield": "other_charges"}): for i, k in enumerate(expected_values["keys"]): - self.assertEquals(flt(d.fields.get(k), 6), expected_values[d.account_head][i]) + self.assertEquals(d.fields.get(k), expected_values[d.account_head][i]) - self.assertEquals(si.doc.grand_total, 1600) - self.assertEquals(si.doc.grand_total_export, 1600) + self.assertEquals(si.doc.grand_total, 1500) + self.assertEquals(si.doc.grand_total_export, 1500) - def atest_inclusive_rate_validations(self): + def test_inclusive_rate_validations(self): si = webnotes.bean(copy=test_records[2]) for i, tax in enumerate(si.doclist.get({"parentfield": "other_charges"})): tax.idx = i+1 @@ -198,7 +210,7 @@ class TestSalesInvoice(unittest.TestCase): si.doclist[3].included_in_print_rate = 0 self.assertRaises(webnotes.ValidationError, si.insert) - def atest_sales_invoice_calculation_base_currency_with_tax_inclusive_price(self): + def test_sales_invoice_calculation_base_currency_with_tax_inclusive_price(self): # prepare si = webnotes.bean(copy=test_records[3]) si.insert() @@ -238,12 +250,12 @@ class TestSalesInvoice(unittest.TestCase): for d in si.doclist.get({"parentfield": "other_charges"}): for i, k in enumerate(expected_values["keys"]): - self.assertEquals(flt(d.fields.get(k), 6), expected_values[d.account_head][i]) + self.assertEquals(d.fields.get(k), expected_values[d.account_head][i]) self.assertEquals(si.doc.grand_total, 1622.98) self.assertEquals(si.doc.grand_total_export, 1622.98) - def atest_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self): + def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self): # prepare si = webnotes.bean(copy=test_records[3]) si.doc.currency = "USD" @@ -291,16 +303,16 @@ class TestSalesInvoice(unittest.TestCase): for d in si.doclist.get({"parentfield": "other_charges"}): for i, k in enumerate(expected_values["keys"]): - self.assertEquals(flt(d.fields.get(k), 6), expected_values[d.account_head][i]) + self.assertEquals(d.fields.get(k), expected_values[d.account_head][i]) self.assertEquals(si.doc.grand_total, 65205.16) self.assertEquals(si.doc.grand_total_export, 1304.1) - def atest_outstanding(self): + def test_outstanding(self): w = self.make() self.assertEquals(w.doc.outstanding_amount, w.doc.grand_total) - def atest_payment(self): + def test_payment(self): webnotes.conn.sql("""delete from `tabGL Entry`""") w = self.make() @@ -319,7 +331,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(webnotes.conn.get_value("Sales Invoice", w.doc.name, "outstanding_amount"), 561.8) - def atest_time_log_batch(self): + def test_time_log_batch(self): tlb = webnotes.bean("Time Log Batch", "_T-Time Log Batch-00001") tlb.submit() @@ -342,7 +354,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEquals(webnotes.conn.get_value("Time Log", "_T-Time Log-00001", "status"), "Batched for Billing") - def atest_sales_invoice_gl_entry_without_aii(self): + def test_sales_invoice_gl_entry_without_aii(self): self.clear_stock_account_balance() set_perpetual_inventory(0) si = webnotes.bean(copy=test_records[1]) @@ -375,7 +387,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) - def atest_pos_gl_entry_with_aii(self): + def test_pos_gl_entry_with_aii(self): self.clear_stock_account_balance() set_perpetual_inventory() @@ -435,7 +447,7 @@ class TestSalesInvoice(unittest.TestCase): set_perpetual_inventory(0) - def atest_si_gl_entry_with_aii_and_update_stock_with_warehouse_but_no_account(self): + def test_si_gl_entry_with_aii_and_update_stock_with_warehouse_but_no_account(self): self.clear_stock_account_balance() set_perpetual_inventory() webnotes.delete_doc("Account", "_Test Warehouse No Account - _TC") @@ -490,7 +502,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(gle) set_perpetual_inventory(0) - def atest_sales_invoice_gl_entry_with_aii_no_item_code(self): + def test_sales_invoice_gl_entry_with_aii_no_item_code(self): self.clear_stock_account_balance() set_perpetual_inventory() @@ -518,7 +530,7 @@ class TestSalesInvoice(unittest.TestCase): set_perpetual_inventory(0) - def atest_sales_invoice_gl_entry_with_aii_non_stock_item(self): + def test_sales_invoice_gl_entry_with_aii_non_stock_item(self): self.clear_stock_account_balance() set_perpetual_inventory() si_copy = webnotes.copy_doclist(test_records[1]) @@ -570,7 +582,7 @@ class TestSalesInvoice(unittest.TestCase): ps = webnotes.bean(copy=pos_setting_test_records[0]) ps.insert() - def atest_sales_invoice_with_advance(self): + def test_sales_invoice_with_advance(self): from accounts.doctype.journal_voucher.test_journal_voucher \ import test_records as jv_test_records @@ -605,7 +617,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertTrue(not webnotes.conn.sql("""select name from `tabJournal Voucher Detail` where against_invoice=%s""", si.doc.name)) - def atest_recurring_invoice(self): + def test_recurring_invoice(self): from webnotes.utils import now_datetime, get_first_day, get_last_day, add_to_date today = now_datetime().date() @@ -745,7 +757,7 @@ class TestSalesInvoice(unittest.TestCase): webnotes.conn.sql("delete from tabBin") webnotes.conn.sql("delete from `tabGL Entry`") - def atest_serialized(self): + def test_serialized(self): from stock.doctype.stock_entry.test_stock_entry import make_serialized_item from stock.doctype.serial_no.serial_no import get_serial_nos @@ -767,7 +779,7 @@ class TestSalesInvoice(unittest.TestCase): return si - def atest_serialized_cancel(self): + def test_serialized_cancel(self): from stock.doctype.serial_no.serial_no import get_serial_nos si = self.test_serialized() si.cancel() @@ -779,7 +791,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "delivery_document_no")) - def atest_serialize_status(self): + def test_serialize_status(self): from stock.doctype.serial_no.serial_no import SerialNoStatusError, get_serial_nos from stock.doctype.stock_entry.test_stock_entry import make_serialized_item diff --git a/controllers/accounts_controller.py b/controllers/accounts_controller.py index c76258ac276..41d48593570 100644 --- a/controllers/accounts_controller.py +++ b/controllers/accounts_controller.py @@ -148,6 +148,10 @@ class AccountsController(TransactionBase): self.doc.conversion_rate = flt(self.doc.conversion_rate) self.item_doclist = self.doclist.get({"parentfield": self.fname}) self.tax_doclist = self.doclist.get({"parentfield": self.other_fname}) + + # for buying + if not hasattr(self, "flat_discount_applied"): + self.flat_discount_applied = False self.calculate_item_values() self.initialize_taxes() @@ -232,24 +236,26 @@ class AccountsController(TransactionBase): _on_previous_row_error("1 - %d" % (tax.row_id,)) def calculate_taxes(self): - for item in self.item_doclist: + # maintain actual tax rate based on idx + actual_tax_dict = dict([[tax.idx, tax.rate] for tax in self.tax_doclist + if tax.charge_type == "Actual"]) + + for n, item in enumerate(self.item_doclist): item_tax_map = self._load_item_tax_rate(item.item_tax_rate) for i, tax in enumerate(self.tax_doclist): # tax_amount represents the amount of tax for the current step current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map) + + # Adjust divisional loss to the last item + if tax.charge_type == "Actual": + actual_tax_dict[tax.idx] -= current_tax_amount; + if n == len(self.item_doclist) - 1: + current_tax_amount += actual_tax_dict[tax.idx] if hasattr(self, "set_item_tax_amount"): self.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" and \ - not (current_tax_amount or self.doc.net_total or tax.tax_amount): - zero_net_total_adjustment = flt(tax.rate, self.precision("tax_amount", tax)) - current_tax_amount += zero_net_total_adjustment - # 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 @@ -271,17 +277,31 @@ class AccountsController(TransactionBase): # note: grand_total_for_current_item contains the contribution of # item's amount, previously applied tax and the current tax on that item if i==0: - tax.grand_total_for_current_item = flt(item.amount + - current_tax_amount, self.precision("total", tax)) - + tax.grand_total_for_current_item = flt(item.amount + current_tax_amount, + self.precision("total", tax)) else: tax.grand_total_for_current_item = \ - flt(self.tax_doclist[i-1].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)) + + # adjust 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)) + def get_current_tax_amount(self, item, tax, item_tax_map): tax_rate = self._get_tax_rate(tax, item_tax_map) current_tax_amount = 0.0 @@ -300,9 +320,9 @@ class AccountsController(TransactionBase): elif tax.charge_type == "On Previous Row Total": current_tax_amount = (tax_rate / 100.0) * \ self.tax_doclist[cint(tax.row_id) - 1].grand_total_for_current_item - + current_tax_amount = flt(current_tax_amount, self.precision("tax_amount", tax)) - + # store tax breakup for each item key = item.item_code or item.item_name if tax.item_wise_tax_detail.get(key): diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py index ede6ca9242c..2571c3c83eb 100644 --- a/controllers/selling_controller.py +++ b/controllers/selling_controller.py @@ -185,7 +185,6 @@ class SellingController(StockController): self.round_floats_in(self.doc, ["net_total", "net_total_export"]) def calculate_totals(self): - self.total_tax_excluding_actual = 0.0 self.doc.grand_total = flt(self.tax_doclist and \ self.tax_doclist[-1].total or self.doc.net_total, self.precision("grand_total")) self.doc.grand_total_export = flt(self.doc.grand_total / self.doc.conversion_rate, @@ -199,26 +198,33 @@ class SellingController(StockController): self.doc.rounded_total = _round(self.doc.grand_total) self.doc.rounded_total_export = _round(self.doc.grand_total_export) - if self.doc.flat_discount: - # calculate total tax for flat discount excluding actual - for tax in self.tax_doclist: - if tax.charge_type != "Actual": - self.total_tax_excluding_actual += tax.tax_amount - - self.total_amount_for_flat_discount = flt(self.doc.net_total + - self.total_tax_excluding_actual, self.precision("grand_total")) - def apply_flat_discount(self): - distributed_amount = 0.0 + if self.doc.flat_discount: + total_amount_for_flat_discount = self.get_flat_discountable_amount() - if self.doc.flat_discount and self.total_amount_for_flat_discount: - # calculate item amount after flat discount - for item in self.item_doclist: - distributed_amount = self.doc.flat_discount * item.amount / self.total_amount_for_flat_discount - item.amount = flt(item.amount - distributed_amount, self.precision("amount", item)) + if total_amount_for_flat_discount: + # calculate item amount after flat discount + for item in self.item_doclist: + distributed_amount = self.doc.flat_discount * item.amount / total_amount_for_flat_discount + item.amount = flt(item.amount - distributed_amount, self.precision("amount", item)) - self.flat_discount_applied = True - self.calculate_taxes_and_totals() + self.flat_discount_applied = True + self.calculate_taxes_and_totals() + + def get_flat_discountable_amount(self): + actual_taxes_dict = {} + + for tax in self.tax_doclist: + if tax.charge_type == "Actual": + actual_taxes_dict.setdefault(tax.idx, tax.tax_amount) + elif tax.row_id in actual_taxes_dict: + actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * \ + flt(tax.rate) / 100 + actual_taxes_dict.setdefault(tax.idx, actual_tax_amount) + + total_amount_for_flat_discount = flt(self.doc.grand_total - sum(actual_taxes_dict.values()), + self.precision("grand_total")) + return total_amount_for_flat_discount def calculate_outstanding_amount(self): # NOTE: