diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index dd8acec196e..535046aad1b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2828,6 +2828,60 @@ class TestSalesInvoice(FrappeTestCase): self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map) + def test_item_tax_template_change_with_grand_total_discount(self): + """ + Test that when item tax template changes due to discount on Grand Total, + the tax calculations are consistent. + """ + item = create_item("Test Item With Multiple Tax Templates") + + item.set("taxes", []) + item.append( + "taxes", + { + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", + "minimum_net_rate": 0, + "maximum_net_rate": 500, + }, + ) + + item.append( + "taxes", + { + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", + "minimum_net_rate": 501, + "maximum_net_rate": 1000, + }, + ) + + item.save() + + si = create_sales_invoice(item=item.name, rate=700, do_not_save=True) + si.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account Excise Duty - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "rate": 0, + }, + ) + si.insert() + + self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") + + si.apply_discount_on = "Grand Total" + si.discount_amount = 300 + si.save() + + # Verify template changed to 10% + self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + self.assertEqual(si.taxes[0].tax_amount, 70) # 10% of 700 + self.assertEqual(si.grand_total, 470) # 700 + 70 - 300 + + si.submit() + @change_settings("Selling Settings", {"enable_discount_accounting": 1}) def test_sales_invoice_with_discount_accounting_enabled(self): discount_account = create_account( diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index ced62b71cf9..102622adf7a 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -42,17 +42,23 @@ class calculate_taxes_and_totals: items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items"))) return items - def calculate(self): + def calculate(self, ignore_tax_template_validation=False): if not len(self._items): return self.discount_amount_applied = False + self.need_recomputation = False + self.ignore_tax_template_validation = ignore_tax_template_validation + self._calculate() if self.doc.meta.get_field("discount_amount"): self.set_discount_amount() self.apply_discount_amount() + if not ignore_tax_template_validation and self.need_recomputation: + return self.calculate(ignore_tax_template_validation=True) + # Update grand total as per cash and non trade discount 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 @@ -96,6 +102,9 @@ class calculate_taxes_and_totals: self.doc.base_tax_withholding_net_total = sum_base_net_amount def validate_item_tax_template(self): + if self.ignore_tax_template_validation: + return + if self.doc.get("is_return") and self.doc.get("return_against"): return @@ -136,6 +145,10 @@ class calculate_taxes_and_totals: ) ) + # For correct tax_amount calculation re-computation is required + if self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total": + self.need_recomputation = True + def update_item_tax_map(self): for item in self.doc.items: item.item_tax_rate = get_item_tax_map( diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 9aaa5728ad7..ee7a9b05f10 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1089,9 +1089,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe plc_conversion_rate() { if(this.frm.doc.price_list_currency === this.get_company_currency()) { this.frm.set_value("plc_conversion_rate", 1.0); - } else if(this.frm.doc.price_list_currency === this.frm.doc.currency - && this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 && - cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) { + } else if ( + this.frm.doc.price_list_currency === this.frm.doc.currency && + this.frm.doc.plc_conversion_rate && + flt(this.frm.doc.plc_conversion_rate) != 1 && + flt(this.frm.doc.plc_conversion_rate) != flt(this.frm.doc.conversion_rate) + ) { this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate); } diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 67e340d0f70..6f94f40aefd 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -41,9 +41,37 @@ def get_data(report_filters): gl_data = voucher_wise_gl_data.get(key) or {} d.account_value = gl_data.get("account_value", 0) d.difference_value = d.stock_value - d.account_value + d.ledger_type = "Stock Ledger Entry" if abs(d.difference_value) > 0.1: data.append(d) + if key in voucher_wise_gl_data: + del voucher_wise_gl_data[key] + + if voucher_wise_gl_data: + data += get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data) + + return data + + +def get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data): + data = [] + + for key in voucher_wise_gl_data: + gl_data = voucher_wise_gl_data.get(key) or {} + data.append( + { + "name": gl_data.get("name"), + "ledger_type": "GL Entry", + "voucher_type": gl_data.get("voucher_type"), + "voucher_no": gl_data.get("voucher_no"), + "posting_date": gl_data.get("posting_date"), + "stock_value": 0, + "account_value": gl_data.get("account_value", 0), + "difference_value": gl_data.get("account_value", 0) * -1, + } + ) + return data @@ -88,6 +116,7 @@ def get_gl_data(report_filters, filters): "voucher_type", "voucher_no", "sum(debit_in_account_currency) - sum(credit_in_account_currency) as account_value", + "posting_date", ], group_by="voucher_type, voucher_no", ) @@ -105,10 +134,15 @@ def get_columns(filters): { "label": _("Stock Ledger ID"), "fieldname": "name", - "fieldtype": "Link", - "options": "Stock Ledger Entry", + "fieldtype": "Dynamic Link", + "options": "ledger_type", "width": "80", }, + { + "label": _("Ledger Type"), + "fieldname": "ledger_type", + "fieldtype": "Data", + }, {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date"}, {"label": _("Posting Time"), "fieldname": "posting_time", "fieldtype": "Time"}, {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": "110"},