diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index aefa9a59dd..cdb187f218 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -476,6 +476,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e this.frm.trigger("calculate_timesheet_totals"); } } + + is_cash_or_non_trade_discount() { + this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount); + if (!this.frm.doc.is_cash_or_non_trade_discount) { + this.frm.set_value("additional_discount_account", ""); + } + } }; // for backward compatibility: combine new and previous states diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 327545aa54..499377d426 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -106,6 +106,7 @@ "loyalty_redemption_cost_center", "section_break_49", "apply_discount_on", + "is_cash_or_non_trade_discount", "base_discount_amount", "additional_discount_account", "column_break_51", @@ -1790,8 +1791,6 @@ "width": "50%" }, { - "fetch_from": "sales_partner.commission_rate", - "fetch_if_empty": 1, "fieldname": "commission_rate", "fieldtype": "Float", "hide_days": 1, @@ -1990,7 +1989,7 @@ { "fieldname": "additional_discount_account", "fieldtype": "Link", - "label": "Additional Discount Account", + "label": "Discount Account", "options": "Account" }, { @@ -2028,6 +2027,13 @@ "fieldtype": "Currency", "label": "Amount Eligible for Commission", "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval: doc.apply_discount_on == \"Grand Total\"", + "fieldname": "is_cash_or_non_trade_discount", + "fieldtype": "Check", + "label": "Is Cash or Non Trade Discount" } ], "icon": "fa fa-file-text", @@ -2040,7 +2046,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-06-10 03:52:51.409913", + "modified": "2022-06-16 16:22:44.870575", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 657cd994fe..6530e5ce86 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1030,7 +1030,7 @@ class SalesInvoice(SellingController): ) if grand_total and not self.is_internal_transfer(): - # Didnot use base_grand_total to book rounding loss gle + # Did not use base_grand_total to book rounding loss gle gl_entries.append( self.get_gl_dict( { @@ -1055,6 +1055,22 @@ class SalesInvoice(SellingController): ) ) + if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"): + gl_entries.append( + self.get_gl_dict( + { + "account": self.additional_discount_account, + "against": self.debit_to, + "debit": self.base_discount_amount, + "debit_in_account_currency": self.discount_amount, + "cost_center": self.cost_center, + "project": self.project, + }, + self.currency, + item=self, + ) + ) + def make_tax_gl_entries(self, gl_entries): enable_discount_accounting = cint( frappe.db.get_single_value("Selling Settings", "enable_discount_accounting") diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 448ec54b1e..c2e82fb214 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2731,6 +2731,63 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) + def test_einvoice_discounts(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + + # 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.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["ValDtls"]["Discount"], 0) + + # 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"], 316.83) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + # 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"], 268.5) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + # 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["ValDtls"]["Discount"], 400) + def test_item_tax_net_range(self): item = create_item("T Shirt") diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 2144055b34..0d8cffe03f 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -500,6 +500,9 @@ class calculate_taxes_and_totals(object): else: self.doc.grand_total = flt(self.doc.net_total) + if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"): + self.doc.grand_total -= self.doc.discount_amount + if self.doc.get("taxes"): self.doc.total_taxes_and_charges = flt( self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), @@ -594,6 +597,12 @@ class calculate_taxes_and_totals(object): if not self.doc.apply_discount_on: frappe.throw(_("Please select Apply Discount On")) + if self.doc.apply_discount_on == "Grand Total" and self.doc.get( + "is_cash_or_non_trade_discount" + ): + self.discount_amount_applied = True + return + self.doc.base_discount_amount = flt( self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount") ) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 5eb14a5ddd..fb2779bb48 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -271,14 +271,14 @@ def get_item_list(invoice): item.description = sanitize_for_json(d.item_name) item.qty = abs(item.qty) - 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 + if invoice.get("apply_discount_on"): + item.discount_amount = item.base_amount - item.base_net_amount + + item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty + + item.gross_amount = abs(item.taxable_value) + item.discount_amount + item.taxable_value = abs(item.taxable_value) item.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N" item.serial_no = "" @@ -352,7 +352,14 @@ def update_item_taxes(invoice, item): def get_invoice_value_details(invoice): invoice_value_details = frappe._dict(dict()) invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get("items")])) - invoice_value_details.invoice_discount_amt = 0 + if ( + invoice.apply_discount_on == "Grand Total" + and invoice.discount_amount + and invoice.get("is_cash_or_non_trade_discount") + ): + invoice_value_details.invoice_discount_amt = invoice.discount_amount + else: + invoice_value_details.invoice_discount_amt = 0 invoice_value_details.round_off = invoice.base_rounding_adjustment invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs( diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0262469571..f1586fc3dc 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -1060,8 +1060,16 @@ def update_taxable_values(doc, method): considered_rows.append(prev_row_id) for item in doc.get("items"): - proportionate_value = item.base_net_amount if doc.base_net_total else item.qty - total_value = doc.base_net_total if doc.base_net_total else doc.total_qty + if ( + doc.apply_discount_on == "Grand Total" + and doc.discount_amount + and doc.get("is_cash_or_non_trade_discount") + ): + proportionate_value = item.base_amount if doc.base_total else item.qty + total_value = doc.base_total if doc.base_total else doc.total_qty + else: + proportionate_value = item.base_net_amount if doc.base_net_total else item.qty + total_value = doc.base_net_total if doc.base_net_total else doc.total_qty applicable_charges = flt( flt(