Merge pull request #31405 from deepeshgarg007/e_invoice_discounts

feat: Cash and Non trade discounts in Sales Invoice
This commit is contained in:
Deepesh Garg 2022-07-03 13:34:38 +05:30 committed by GitHub
commit 4038c42922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 15 deletions

View File

@ -476,6 +476,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
this.frm.trigger("calculate_timesheet_totals"); 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 // for backward compatibility: combine new and previous states

View File

@ -106,6 +106,7 @@
"loyalty_redemption_cost_center", "loyalty_redemption_cost_center",
"section_break_49", "section_break_49",
"apply_discount_on", "apply_discount_on",
"is_cash_or_non_trade_discount",
"base_discount_amount", "base_discount_amount",
"additional_discount_account", "additional_discount_account",
"column_break_51", "column_break_51",
@ -1790,8 +1791,6 @@
"width": "50%" "width": "50%"
}, },
{ {
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate", "fieldname": "commission_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hide_days": 1, "hide_days": 1,
@ -1990,7 +1989,7 @@
{ {
"fieldname": "additional_discount_account", "fieldname": "additional_discount_account",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Additional Discount Account", "label": "Discount Account",
"options": "Account" "options": "Account"
}, },
{ {
@ -2028,6 +2027,13 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Amount Eligible for Commission", "label": "Amount Eligible for Commission",
"read_only": 1 "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", "icon": "fa fa-file-text",
@ -2040,7 +2046,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2022-06-10 03:52:51.409913", "modified": "2022-06-16 16:22:44.870575",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -1030,7 +1030,7 @@ class SalesInvoice(SellingController):
) )
if grand_total and not self.is_internal_transfer(): 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( gl_entries.append(
self.get_gl_dict( 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): def make_tax_gl_entries(self, gl_entries):
enable_discount_accounting = cint( enable_discount_accounting = cint(
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting") frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")

View File

@ -2731,6 +2731,63 @@ class TestSalesInvoice(unittest.TestCase):
einvoice = make_einvoice(si) einvoice = make_einvoice(si)
validate_totals(einvoice) 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): def test_item_tax_net_range(self):
item = create_item("T Shirt") item = create_item("T Shirt")

View File

@ -500,6 +500,9 @@ class calculate_taxes_and_totals(object):
else: else:
self.doc.grand_total = flt(self.doc.net_total) 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"): if self.doc.get("taxes"):
self.doc.total_taxes_and_charges = flt( self.doc.total_taxes_and_charges = flt(
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), 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: if not self.doc.apply_discount_on:
frappe.throw(_("Please select 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.base_discount_amount = flt(
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount") self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
) )

View File

@ -271,14 +271,14 @@ def get_item_list(invoice):
item.description = sanitize_for_json(d.item_name) item.description = sanitize_for_json(d.item_name)
item.qty = abs(item.qty) 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.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N"
item.serial_no = "" item.serial_no = ""
@ -352,6 +352,13 @@ def update_item_taxes(invoice, item):
def get_invoice_value_details(invoice): def get_invoice_value_details(invoice):
invoice_value_details = frappe._dict(dict()) 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.base_total = abs(sum([i.taxable_value for i in invoice.get("items")]))
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.invoice_discount_amt = 0
invoice_value_details.round_off = invoice.base_rounding_adjustment invoice_value_details.round_off = invoice.base_rounding_adjustment

View File

@ -1060,6 +1060,14 @@ def update_taxable_values(doc, method):
considered_rows.append(prev_row_id) considered_rows.append(prev_row_id)
for item in doc.get("items"): for item in doc.get("items"):
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 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 total_value = doc.base_net_total if doc.base_net_total else doc.total_qty