diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 2ff1b34e35e..0a5172eaba6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2623,20 +2623,34 @@ class TestSalesInvoice(unittest.TestCase): def test_einvoice_discounts(self): from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + frappe.db.set_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice", False) + # Normal Itemized Discount si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "" - si.items[0].discount_amount = 4000 - si.items[1].discount_amount = 300 + si.items[0].price_list_rate = 12 + si.items[0].discount_percentage = 16.6666666667 + si.items[0].rate = 10 + + si.items[1].price_list_rate = 15 + si.items[1].discount_amount = 5 + si.items[1].rate = 10 si.save() einvoice = make_einvoice(si) validate_totals(einvoice) self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000) - self.assertEqual(einvoice["ItemList"][1]["Discount"], 300) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 2100) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 222) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5555) self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + # Invoice Discount on net total si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "Net Total" @@ -2646,10 +2660,17 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) - self.assertEqual(einvoice["ItemList"][0]["Discount"], 316.83) - self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17) + self.assertEqual(einvoice["ItemList"][0]["Discount"], 253.61) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 66.57) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 243.11) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5613.71) self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + # Invoice Discount on grand total (Itemized Discount) si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "Grand Total" @@ -2659,10 +2680,17 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) - self.assertEqual(einvoice["ItemList"][0]["Discount"], 268.5) - self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48) + self.assertEqual(einvoice["ItemList"][0]["Discount"], 214.93) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 56.42) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 239.89) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5604.75) self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + # Invoice Discount on grand total (Cash/Non-Trade Discount) si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "Grand Total" @@ -2675,8 +2703,107 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 222.0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5555.0) self.assertEqual(einvoice["ValDtls"]["Discount"], 400) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + + def test_einvoice_without_discounts(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + + frappe.db.set_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice", True) + + # Normal Itemized Discount + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "" + si.items[0].price_list_rate = 12 + si.items[0].discount_percentage = 16.6666666667 + si.items[0].rate = 10 + + si.items[1].price_list_rate = 15 + si.items[1].discount_amount = 5 + si.items[1].rate = 10 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 10) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 10) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5) + + # Invoice Discount on net total + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Net Total" + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 11.87) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 14.84) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 17.81) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 4.95) + + # Invoice Discount on grand total (Itemized Discount) + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Grand Total" + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 11.89) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 14.87) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 17.84) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 4.96) + + # Invoice Discount on grand total (Cash/Non-Trade Discount) + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Grand Total" + si.is_cash_or_non_trade_discount = 1 + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 400) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5) + def test_item_tax_net_range(self): item = create_item("T Shirt") @@ -3276,6 +3403,36 @@ def get_sales_invoice_for_e_invoice(): }, ) + si.append( + "items", + { + "item_code": "_Test Item", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 111, + "price_list_rate": 20, + "discount_percentage": 10, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ) + + si.append( + "items", + { + "item_code": "_Test Item 2", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 1111, + "price_list_rate": 10, + "rate": 5, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ) + return si diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index 16b29633010..14fad403213 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -9,6 +9,9 @@ "section_break_2", "sandbox_mode", "applicable_from", + "column_break_4", + "dont_show_discounts_in_e_invoice", + "section_break_7", "credentials", "advanced_settings_section", "client_id", @@ -80,12 +83,28 @@ { "fieldname": "column_break_8", "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "default": "1", + "description": "Enabling this will directly report net rates in e-Invoice post discounts", + "fieldname": "dont_show_discounts_in_e_invoice", + "fieldtype": "Check", + "label": "Don't show discounts in e-Invoice" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hide_border": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-11-16 19:50:28.029517", + "modified": "2022-07-17 14:57:50.783517", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index d4a2e026453..b0f837b51e7 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -273,10 +273,41 @@ def get_item_list(invoice): item.qty = abs(item.qty) - if invoice.get("apply_discount_on"): - item.discount_amount = item.base_amount - item.base_net_amount + hide_discount_in_einvoice = cint( + frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice") + ) - item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty + if hide_discount_in_einvoice: + if flt(item.qty) != 0.0: + item.unit_rate = abs(item.taxable_value / item.qty) + else: + item.unit_rate = abs(item.taxable_value) + item.gross_amount = abs(item.taxable_value) + item.taxable_value = abs(item.taxable_value) + item.discount_amount = 0 + + else: + if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount") or 0.0) > 0.0): + # TODO: need to handle case when tax included in basic rate is checked. + item.discount_amount = (item.discount_amount * item.qty) + ( + abs(item.base_amount) - abs(item.base_net_amount) + ) + else: + item.discount_amount = item.discount_amount * item.qty + + if invoice.get("is_return") or invoice.get("is_debit_note"): + item.unit_rate = (abs(item.taxable_value) + item.discount_amount) / ( + 1 if (item.qty == 0) else item.qty + ) + else: + try: + item.unit_rate = abs(item.taxable_value + item.discount_amount) / item.qty + except ZeroDivisionError: + # This will never run but added as safety measure + frappe.throw( + title=_("Error: Qty is Zero"), + msg=_("Quantity can't be zero unless it's Credit/Debit Note."), + ) item.gross_amount = abs(item.taxable_value) + item.discount_amount item.taxable_value = abs(item.taxable_value) diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index 30d933e247d..d3988743f6d 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -164,4 +164,13 @@ class TestItemPrice(FrappeTestCase): self.assertEqual(price, 21) +def make_item_price(item_code, price_list, price_list_rate): + item_price = frappe.new_doc("Item Price") + item_price.price_list = price_list + item_price.item_code = item_code + item_price.price_list_rate = price_list_rate + item_price.insert() + return item_price + + test_records = frappe.get_test_records("Item Price")