diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 939d8410900..346fee81812 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -135,6 +135,7 @@ class GLEntry(Document): and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled + and not self.is_cancelled ): if not self.get(dimension.fieldname): frappe.throw( @@ -148,6 +149,7 @@ class GLEntry(Document): and self.company == dimension.company and dimension.mandatory_for_bs and not dimension.disabled + and not self.is_cancelled ): if not self.get(dimension.fieldname): frappe.throw( diff --git a/erpnext/accounts/doctype/ledger_health/__init__.py b/erpnext/accounts/doctype/ledger_health/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.js b/erpnext/accounts/doctype/ledger_health/ledger_health.js new file mode 100644 index 00000000000..e207daef511 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Ledger Health", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.json b/erpnext/accounts/doctype/ledger_health/ledger_health.json new file mode 100644 index 00000000000..fb2da3d2564 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-03-26 17:01:47.443986", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "voucher_type", + "voucher_no", + "checked_on", + "debit_credit_mismatch", + "general_and_payment_ledger_mismatch" + ], + "fields": [ + { + "fieldname": "voucher_type", + "fieldtype": "Data", + "label": "Voucher Type" + }, + { + "fieldname": "voucher_no", + "fieldtype": "Data", + "label": "Voucher No" + }, + { + "default": "0", + "fieldname": "debit_credit_mismatch", + "fieldtype": "Check", + "label": "Debit-Credit mismatch" + }, + { + "fieldname": "checked_on", + "fieldtype": "Datetime", + "label": "Checked On" + }, + { + "default": "0", + "fieldname": "general_and_payment_ledger_mismatch", + "fieldtype": "Check", + "label": "General and Payment Ledger mismatch" + } + ], + "in_create": 1, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-04-09 11:16:07.044484", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Health", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.py b/erpnext/accounts/doctype/ledger_health/ledger_health.py new file mode 100644 index 00000000000..590ff80cc11 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.py @@ -0,0 +1,25 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LedgerHealth(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + checked_on: DF.Datetime | None + debit_credit_mismatch: DF.Check + general_and_payment_ledger_mismatch: DF.Check + name: DF.Int | None + voucher_no: DF.Data | None + voucher_type: DF.Data | None + # end: auto-generated types + + pass diff --git a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py new file mode 100644 index 00000000000..d35b39d9ea1 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py @@ -0,0 +1,109 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe import qb +from frappe.tests.utils import FrappeTestCase +from frappe.utils import nowdate + +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.accounts.utils import run_ledger_health_checks + + +class TestLedgerHealth(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.create_customer() + self.configure_monitoring_tool() + self.clear_old_entries() + + def tearDown(self): + frappe.db.rollback() + + def configure_monitoring_tool(self): + monitor_settings = frappe.get_doc("Ledger Health Monitor") + monitor_settings.enable_health_monitor = True + monitor_settings.enable_for_last_x_days = 60 + monitor_settings.debit_credit_mismatch = True + monitor_settings.general_and_payment_ledger_mismatch = True + exists = [x for x in monitor_settings.companies if x.company == self.company] + if not exists: + monitor_settings.append("companies", {"company": self.company}) + monitor_settings.save() + + def clear_old_entries(self): + super().clear_old_entries() + lh = qb.DocType("Ledger Health") + qb.from_(lh).delete().run() + + def create_journal(self): + je = frappe.new_doc("Journal Entry") + je.company = self.company + je.voucher_type = "Journal Entry" + je.posting_date = nowdate() + je.append( + "accounts", + { + "account": self.debit_to, + "party_type": "Customer", + "party": self.customer, + "debit_in_account_currency": 10000, + }, + ) + je.append("accounts", {"account": self.income_account, "credit_in_account_currency": 10000}) + je.save().submit() + self.je = je + + def test_debit_credit_mismatch(self): + self.create_journal() + + # manually cause debit-credit mismatch + gle = frappe.db.get_all( + "GL Entry", filters={"voucher_no": self.je.name, "account": self.income_account} + )[0] + frappe.db.set_value("GL Entry", gle.name, "credit", 8000) + + run_ledger_health_checks() + expected = { + "voucher_type": self.je.doctype, + "voucher_no": self.je.name, + "debit_credit_mismatch": True, + "general_and_payment_ledger_mismatch": False, + } + actual = frappe.db.get_all( + "Ledger Health", + fields=[ + "voucher_type", + "voucher_no", + "debit_credit_mismatch", + "general_and_payment_ledger_mismatch", + ], + ) + self.assertEqual(len(actual), 1) + self.assertEqual(expected, actual[0]) + + def test_gl_and_pl_mismatch(self): + self.create_journal() + + # manually cause GL and PL discrepancy + ple = frappe.db.get_all("Payment Ledger Entry", filters={"voucher_no": self.je.name})[0] + frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", 11000) + + run_ledger_health_checks() + expected = { + "voucher_type": self.je.doctype, + "voucher_no": self.je.name, + "debit_credit_mismatch": False, + "general_and_payment_ledger_mismatch": True, + } + actual = frappe.db.get_all( + "Ledger Health", + fields=[ + "voucher_type", + "voucher_no", + "debit_credit_mismatch", + "general_and_payment_ledger_mismatch", + ], + ) + self.assertEqual(len(actual), 1) + self.assertEqual(expected, actual[0]) diff --git a/erpnext/accounts/doctype/ledger_health_monitor/__init__.py b/erpnext/accounts/doctype/ledger_health_monitor/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js new file mode 100644 index 00000000000..cf112760f8c --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Ledger Health Monitor", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json new file mode 100644 index 00000000000..6e688333e3f --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json @@ -0,0 +1,104 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-03-27 09:38:07.427997", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable_health_monitor", + "monitor_section", + "monitor_for_last_x_days", + "debit_credit_mismatch", + "general_and_payment_ledger_mismatch", + "section_break_xdsp", + "companies" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable_health_monitor", + "fieldtype": "Check", + "label": "Enable Health Monitor" + }, + { + "fieldname": "monitor_section", + "fieldtype": "Section Break", + "label": "Configuration" + }, + { + "default": "0", + "fieldname": "debit_credit_mismatch", + "fieldtype": "Check", + "label": "Debit-Credit Mismatch" + }, + { + "default": "0", + "fieldname": "general_and_payment_ledger_mismatch", + "fieldtype": "Check", + "label": "Discrepancy between General and Payment Ledger" + }, + { + "default": "60", + "fieldname": "monitor_for_last_x_days", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Monitor for Last 'X' days", + "reqd": 1 + }, + { + "fieldname": "section_break_xdsp", + "fieldtype": "Section Break", + "label": "Companies" + }, + { + "fieldname": "companies", + "fieldtype": "Table", + "options": "Ledger Health Monitor Company" + } + ], + "hide_toolbar": 1, + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2024-03-27 10:14:16.511681", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Health Monitor", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py new file mode 100644 index 00000000000..9f7c569d6d1 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py @@ -0,0 +1,28 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LedgerHealthMonitor(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.accounts.doctype.ledger_health_monitor_company.ledger_health_monitor_company import ( + LedgerHealthMonitorCompany, + ) + + companies: DF.Table[LedgerHealthMonitorCompany] + debit_credit_mismatch: DF.Check + enable_health_monitor: DF.Check + general_and_payment_ledger_mismatch: DF.Check + monitor_for_last_x_days: DF.Int + # end: auto-generated types + + pass diff --git a/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py new file mode 100644 index 00000000000..e0ba4435b82 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLedgerHealthMonitor(FrappeTestCase): + pass diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py b/erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json new file mode 100644 index 00000000000..87fa3e32801 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-03-27 10:04:45.727054", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-03-27 10:06:22.806155", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Health Monitor Company", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py new file mode 100644 index 00000000000..5890410090d --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py @@ -0,0 +1,23 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LedgerHealthMonitorCompany(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + company: DF.Link | None + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + # end: auto-generated types + + pass diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e0e8e2154a6..fefb331ff27 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -338,7 +338,7 @@ class PaymentEntry(AccountsController): self, force: bool = False, update_ref_details_only_for: list | None = None, - ref_exchange_rate: float | None = None, + reference_exchange_details: dict | None = None, ) -> None: for d in self.get("references"): if d.allocated_amount: @@ -352,8 +352,12 @@ class PaymentEntry(AccountsController): ) # Only update exchange rate when the reference is Journal Entry - if ref_exchange_rate and d.reference_doctype == "Journal Entry": - ref_details.update({"exchange_rate": ref_exchange_rate}) + if ( + reference_exchange_details + and d.reference_doctype == reference_exchange_details.reference_doctype + and d.reference_name == reference_exchange_details.reference_name + ): + ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate}) for field, value in ref_details.items(): if d.exchange_gain_loss: diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4d0f6446da4..5993c81a174 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3438,6 +3438,105 @@ class TestSalesInvoice(FrappeTestCase): si.items[0].rate = 10 si.save() + def test_taxes_merging_from_delivery_note(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + dn1 = create_delivery_note(do_not_submit=1) + dn1.items[0].qty = 10 + dn1.items[0].rate = 100 + dn1.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "description": "movement charges", + "tax_amount": 100, + }, + ) + dn1.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Marketing Expenses - _TC", + "description": "marketing", + "tax_amount": 150, + }, + ) + dn1.save().submit() + + dn2 = create_delivery_note(do_not_submit=1) + dn2.items[0].qty = 5 + dn2.items[0].rate = 100 + dn2.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "description": "movement charges", + "tax_amount": 20, + }, + ) + dn2.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Miscellaneous Expenses - _TC", + "description": "marketing", + "tax_amount": 60, + }, + ) + dn2.save().submit() + + # si = make_sales_invoice(dn1.name) + si = create_sales_invoice(do_not_submit=True) + si.customer = dn1.customer + si.items.clear() + + from frappe.model.mapper import map_docs + + map_docs( + method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", + source_names=frappe.json.dumps([dn1.name, dn2.name]), + target_doc=si, + args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}), + ) + si.save().submit() + + expected = [ + { + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "tax_amount": 120.0, + "total": 1520.0, + "base_total": 1520.0, + }, + { + "charge_type": "Actual", + "account_head": "Marketing Expenses - _TC", + "tax_amount": 150.0, + "total": 1670.0, + "base_total": 1670.0, + }, + { + "charge_type": "Actual", + "account_head": "Miscellaneous Expenses - _TC", + "tax_amount": 60.0, + "total": 1610.0, + "base_total": 1610.0, + }, + ] + actual = [ + dict( + charge_type=x.charge_type, + account_head=x.account_head, + tax_amount=x.tax_amount, + total=x.total, + base_total=x.base_total, + ) + for x in si.taxes + ] + self.assertEqual(expected, actual) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 2d5ca497654..3c4e1a05c97 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -55,10 +55,10 @@ - {%= format_currency(data[i].debit, filters.presentation_currency) %} + {%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %} - {%= format_currency(data[i].credit, filters.presentation_currency) %} + {%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %} {% } else { %} diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6ca4aa2ada6..a32c25fbb70 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -13,11 +13,13 @@ from frappe.query_builder import AliasedQuery, Criterion, Table from frappe.query_builder.functions import Round, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( + add_days, cint, create_batch, cstr, flt, formatdate, + get_datetime, get_number_format_info, getdate, now, @@ -682,7 +684,19 @@ def update_reference_in_payment_entry( payment_entry.setup_party_account_field() payment_entry.set_missing_values() if not skip_ref_details_update_for_pe: - payment_entry.set_missing_ref_details(ref_exchange_rate=d.exchange_rate or None) + reference_exchange_details = frappe._dict() + if d.against_voucher_type == "Journal Entry" and d.exchange_rate: + reference_exchange_details.update( + { + "reference_doctype": d.against_voucher_type, + "reference_name": d.against_voucher, + "exchange_rate": d.exchange_rate, + } + ) + payment_entry.set_missing_ref_details( + update_ref_details_only_for=[(d.against_voucher_type, d.against_voucher)], + reference_exchange_details=reference_exchange_details, + ) payment_entry.set_amounts() payment_entry.make_exchange_gain_loss_journal( @@ -2072,3 +2086,44 @@ def create_gain_loss_journal( journal_entry.save() journal_entry.submit() return journal_entry.name + + +def run_ledger_health_checks(): + health_monitor_settings = frappe.get_doc("Ledger Health Monitor") + if health_monitor_settings.enable_health_monitor: + period_end = getdate() + period_start = add_days(period_end, -abs(health_monitor_settings.monitor_for_last_x_days)) + + run_date = get_datetime() + + # Debit-Credit mismatch report + if health_monitor_settings.debit_credit_mismatch: + for x in health_monitor_settings.companies: + filters = {"company": x.company, "from_date": period_start, "to_date": period_end} + voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance") + res = voucher_wise.execute_script_report(filters=filters) + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.debit_credit_mismatch = True + doc.checked_on = run_date + doc.save() + + # General Ledger and Payment Ledger discrepancy + if health_monitor_settings.general_and_payment_ledger_mismatch: + for x in health_monitor_settings.companies: + filters = { + "company": x.company, + "period_start_date": period_start, + "period_end_date": period_end, + } + gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") + res = gl_pl_comparison.execute_script_report(filters=filters) + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.general_and_payment_ledger_mismatch = True + doc.checked_on = run_date + doc.save() diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7755f3e0b64..95c627735d6 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -437,6 +437,7 @@ class Asset(AccountsController): depreciation_amount, from_date, date_of_disposal, + original_schedule_date=schedule_date, ) if depreciation_amount > 0: @@ -1118,14 +1119,20 @@ class Asset(AccountsController): return flt((100 * (1 - depreciation_rate)), float_precision) def get_pro_rata_amt( - self, row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False + self, + row, + depreciation_amount, + from_date, + to_date, + has_wdv_or_dd_non_yearly_pro_rata=False, + original_schedule_date=None, ): days = date_diff(to_date, from_date) months = month_diff(to_date, from_date) if has_wdv_or_dd_non_yearly_pro_rata: - total_days = get_total_days(to_date, 12) + total_days = get_total_days(original_schedule_date or to_date, 12) else: - total_days = get_total_days(to_date, row.frequency_of_depreciation) + total_days = get_total_days(original_schedule_date or to_date, row.frequency_of_depreciation) return (depreciation_amount * flt(days)) / flt(total_days), days, months @@ -1445,32 +1452,35 @@ def get_straight_line_or_manual_depr_amount( # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: if row.daily_prorata_based: - daily_depr_amount = ( - flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - ) / date_diff( - get_last_day( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) - * row.frequency_of_depreciation, - ) - ), - add_days( + amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) + total_days = ( + date_diff( get_last_day( add_months( row.depreciation_start_date, - flt( - row.total_number_of_depreciations - - asset.number_of_depreciations_booked - - number_of_pending_depreciations - - 1 - ) + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) * row.frequency_of_depreciation, ) ), - 1, - ), + add_days( + get_last_day( + add_months( + row.depreciation_start_date, + flt( + row.total_number_of_depreciations + - asset.number_of_depreciations_booked + - number_of_pending_depreciations + - 1 + ) + * row.frequency_of_depreciation, + ) + ), + 1, + ), + ) + + 1 ) + daily_depr_amount = amount / total_days to_date = get_last_day( add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) @@ -1490,22 +1500,28 @@ def get_straight_line_or_manual_depr_amount( # if the Depreciation Schedule is being prepared for the first time else: if row.daily_prorata_based: - daily_depr_amount = ( + + amount = ( flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) - ) / date_diff( - get_last_day( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) - * row.frequency_of_depreciation, - ) - ), - add_days( - get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1 - ), ) + total_days = ( + date_diff( + get_last_day( + add_months( + row.depreciation_start_date, + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) + * row.frequency_of_depreciation, + ) + ), + add_days( + get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1 + ), + ) + + 1 + ) + daily_depr_amount = amount / total_days to_date = get_last_day( add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 708172b2980..ea5b95aaf02 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -219,7 +219,11 @@ class TestAsset(AssetSetup): asset.precision("gross_purchase_amount"), ) pro_rata_amount, _, _ = asset.get_pro_rata_amt( - asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date + asset.finance_books[0], + 9000, + get_last_day(add_months(purchase_date, 1)), + date, + original_schedule_date=get_last_day(nowdate()), ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) self.assertEquals( @@ -287,7 +291,11 @@ class TestAsset(AssetSetup): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") pro_rata_amount, _, _ = asset.get_pro_rata_amt( - asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date + asset.finance_books[0], + 9000, + get_last_day(add_months(purchase_date, 1)), + date, + original_schedule_date=get_last_day(nowdate()), ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) @@ -349,7 +357,7 @@ class TestAsset(AssetSetup): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") - expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]] + expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1737.7, 37737.7]] for i, schedule in enumerate(asset.schedules): self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) @@ -360,7 +368,7 @@ class TestAsset(AssetSetup): expected_gle = ( ( "_Test Accumulated Depreciations - _TC", - 37742.47, + 37737.7, 0.0, ), ( @@ -371,7 +379,7 @@ class TestAsset(AssetSetup): ( "_Test Gain/Loss on Asset Disposal - _TC", 0.0, - 17742.47, + 17737.7, ), ("Debtors - _TC", 40000.0, 0.0), ) @@ -691,18 +699,18 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2023-01-31", 1021.98, 1021.98], - ["2023-02-28", 923.08, 1945.06], - ["2023-03-31", 1021.98, 2967.04], - ["2023-04-30", 989.01, 3956.05], - ["2023-05-31", 1021.98, 4978.03], - ["2023-06-30", 989.01, 5967.04], - ["2023-07-31", 1021.98, 6989.02], - ["2023-08-31", 1021.98, 8011.0], - ["2023-09-30", 989.01, 9000.01], - ["2023-10-31", 1021.98, 10021.99], - ["2023-11-30", 989.01, 11011.0], - ["2023-12-31", 989.0, 12000.0], + ["2023-01-31", 1019.18, 1019.18], + ["2023-02-28", 920.55, 1939.73], + ["2023-03-31", 1019.18, 2958.91], + ["2023-04-30", 986.3, 3945.21], + ["2023-05-31", 1019.18, 4964.39], + ["2023-06-30", 986.3, 5950.69], + ["2023-07-31", 1019.18, 6969.87], + ["2023-08-31", 1019.18, 7989.05], + ["2023-09-30", 986.3, 8975.35], + ["2023-10-31", 1019.18, 9994.53], + ["2023-11-30", 986.3, 10980.83], + ["2023-12-31", 1019.17, 12000.0], ] schedules = [ diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 61bdc4f52a9..534bddcd643 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3,6 +3,7 @@ import json +from collections import defaultdict import frappe from frappe import _, bold, qb, throw @@ -1947,21 +1948,26 @@ class AccountsController(TransactionBase): ) def group_similar_items(self): - group_item_qty = {} - group_item_amount = {} + grouped_items = {} # to update serial number in print count = 0 + fields_to_group = frappe.get_hooks("fields_for_group_similar_items") + fields_to_group = set(fields_to_group) + for item in self.items: - group_item_qty[item.item_code] = group_item_qty.get(item.item_code, 0) + item.qty - group_item_amount[item.item_code] = group_item_amount.get(item.item_code, 0) + item.amount + item_values = grouped_items.setdefault(item.item_code, defaultdict(int)) + + for field in fields_to_group: + item_values[field] += item.get(field, 0) duplicate_list = [] for item in self.items: - if item.item_code in group_item_qty: + if item.item_code in grouped_items: count += 1 - item.qty = group_item_qty[item.item_code] - item.amount = group_item_amount[item.item_code] + + for field in fields_to_group: + item.set(field, grouped_items[item.item_code][field]) if item.qty: item.rate = flt(flt(item.amount) / flt(item.qty), item.precision("rate")) @@ -1969,7 +1975,7 @@ class AccountsController(TransactionBase): item.rate = 0 item.idx = count - del group_item_qty[item.item_code] + del grouped_items[item.item_code] else: duplicate_list.append(item) for item in duplicate_list: @@ -3313,6 +3319,37 @@ def check_if_child_table_updated( return False +def merge_taxes(source_taxes, target_doc): + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( + update_item_wise_tax_detail, + ) + + existing_taxes = target_doc.get("taxes") or [] + idx = 1 + for tax in source_taxes: + found = False + for t in existing_taxes: + if t.account_head == tax.account_head and t.cost_center == tax.cost_center: + t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount) + t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount) + update_item_wise_tax_detail(t, tax) + found = True + + if not found: + tax.charge_type = "Actual" + tax.idx = idx + idx += 1 + tax.included_in_print_rate = 0 + tax.dont_recompute_tax = 1 + tax.row_id = "" + tax.tax_amount = tax.tax_amount_after_discount_amount + tax.base_tax_amount = tax.base_tax_amount_after_discount_amount + tax.item_wise_tax_detail = tax.item_wise_tax_detail + existing_taxes.append(tax) + + target_doc.set("taxes", existing_taxes) + + @erpnext.allow_regional def validate_regional(doc): pass diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index e39d03dccb9..e348d296cd7 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -56,7 +56,8 @@ class TestAccountsController(FrappeTestCase): 20 series - Sales Invoice against Journals 30 series - Sales Invoice against Credit Notes 40 series - Company default Cost center is unset - 50 series = Journals against Journals + 50 series - Journals against Journals + 60 series - Journals against Payment Entries 90 series - Dimension inheritence """ @@ -1574,3 +1575,70 @@ class TestAccountsController(FrappeTestCase): exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name) self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_je, []) + + def test_60_payment_entry_against_journal(self): + # Invoices + exc_rate1 = 75 + exc_rate2 = 77 + amount = 1 + je1 = self.create_journal_entry( + acc1=self.debit_usd, + acc1_exc_rate=exc_rate1, + acc2=self.cash, + acc1_amount=amount, + acc2_amount=(amount * 75), + acc2_exc_rate=1, + ) + je1.accounts[0].party_type = "Customer" + je1.accounts[0].party = self.customer + je1 = je1.save().submit() + + je2 = self.create_journal_entry( + acc1=self.debit_usd, + acc1_exc_rate=exc_rate2, + acc2=self.cash, + acc1_amount=amount, + acc2_amount=(amount * exc_rate2), + acc2_exc_rate=1, + ) + je2.accounts[0].party_type = "Customer" + je2.accounts[0].party = self.customer + je2 = je2.save().submit() + + # Payment + pe = self.create_payment_entry(amount=2, source_exc_rate=exc_rate1).save().submit() + + pr = self.create_payment_reconciliation() + pr.receivable_payable_account = self.debit_usd + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 2) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # There should be no outstanding in both currencies + self.assert_ledger_outstanding(je1.doctype, je1.name, 0.0, 0.0) + self.assert_ledger_outstanding(je2.doctype, je2.name, 0.0, 0.0) + + # Exchange Gain/Loss Journal should've been created only for JE2 + exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name) + exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name) + self.assertEqual(exc_je_for_je1, []) + self.assertEqual(len(exc_je_for_je2), 1) + + # Cancel Payment + pe.reload() + pe.cancel() + + self.assert_ledger_outstanding(je1.doctype, je1.name, (amount * exc_rate1), amount) + self.assert_ledger_outstanding(je2.doctype, je2.name, (amount * exc_rate2), amount) + + # Exchange Gain/Loss Journal should've been cancelled + exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name) + exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name) + self.assertEqual(exc_je_for_je1, []) + self.assertEqual(exc_je_for_je2, []) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e328c686f5d..da4c1974247 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -449,6 +449,7 @@ scheduler_events = { "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily", + "erpnext.accounts.utils.run_ledger_health_checks", ], "weekly": [ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly", @@ -643,3 +644,5 @@ extend_bootinfo = [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", "erpnext.startup.boot.bootinfo", ] + +fields_for_group_similar_items = ["qty", "amount"] diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 429363f1e5c..716b655d739 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -819,7 +819,7 @@ erpnext.utils.map_current_doc = function (opts) { if (opts.source_doctype) { let data_fields = []; - if (opts.source_doctype == "Purchase Receipt") { + if (["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)) { data_fields.push({ fieldname: "merge_taxes", fieldtype: "Check", @@ -845,7 +845,10 @@ erpnext.utils.map_current_doc = function (opts) { return; } opts.source_name = values; - if (opts.allow_child_item_selection || opts.source_doctype == "Purchase Receipt") { + if ( + opts.allow_child_item_selection || + ["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype) + ) { // args contains filtered child docnames opts.args = args; } diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 0a389b602a5..88552d8de08 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -10,7 +10,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.utils import cint, flt -from erpnext.controllers.accounts_controller import get_taxes_and_charges +from erpnext.controllers.accounts_controller import get_taxes_and_charges, merge_taxes from erpnext.controllers.selling_controller import SellingController from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no @@ -623,7 +623,7 @@ def get_returned_qty_map(delivery_note): @frappe.whitelist() -def make_sales_invoice(source_name, target_doc=None): +def make_sales_invoice(source_name, target_doc=None, args=None): doc = frappe.get_doc("Delivery Note", source_name) to_make_invoice_qty_map = {} @@ -637,6 +637,9 @@ def make_sales_invoice(source_name, target_doc=None): if len(target.get("items")) == 0: frappe.throw(_("All these items have already been Invoiced/Returned")) + if args and args.get("merge_taxes"): + merge_taxes(source.get("taxes") or [], target) + target.run_method("calculate_taxes_and_totals") # set company address @@ -701,7 +704,11 @@ def make_sales_invoice(source_name, target_doc=None): if not doc.get("is_return") else get_pending_qty(d) > 0, }, - "Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True}, + "Sales Taxes and Charges": { + "doctype": "Sales Taxes and Charges", + "add_if_empty": True, + "ignore": args.get("merge_taxes") if args else 0, + }, "Sales Team": { "doctype": "Sales Team", "field_map": {"incentives": "incentives"}, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 79e6ab84d95..064c717c320 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -14,6 +14,7 @@ import erpnext from erpnext.accounts.utils import get_account_currency from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled from erpnext.buying.utils import check_on_hold_or_closed_status +from erpnext.controllers.accounts_controller import merge_taxes from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction @@ -974,37 +975,6 @@ def get_item_wise_returned_qty(pr_doc): ) -def merge_taxes(source_taxes, target_doc): - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( - update_item_wise_tax_detail, - ) - - existing_taxes = target_doc.get("taxes") or [] - idx = 1 - for tax in source_taxes: - found = False - for t in existing_taxes: - if t.account_head == tax.account_head and t.cost_center == tax.cost_center: - t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount) - t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount) - update_item_wise_tax_detail(t, tax) - found = True - - if not found: - tax.charge_type = "Actual" - tax.idx = idx - idx += 1 - tax.included_in_print_rate = 0 - tax.dont_recompute_tax = 1 - tax.row_id = "" - tax.tax_amount = tax.tax_amount_after_discount_amount - tax.base_tax_amount = tax.base_tax_amount_after_discount_amount - tax.item_wise_tax_detail = tax.item_wise_tax_detail - existing_taxes.append(tax) - - target_doc.set("taxes", existing_taxes) - - @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None, args=None): from erpnext.accounts.party import get_payment_terms_template diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 878af0e22ca..c7608cc2dd9 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -744,7 +744,9 @@ def get_items( warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False ): ignore_empty_stock = cint(ignore_empty_stock) - items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})] + items = [] + if item_code and warehouse: + items = get_item_and_warehouses(item_code, warehouse) if not item_code: items = get_items_for_stock_reco(warehouse, company) @@ -789,6 +791,20 @@ def get_items( return res +def get_item_and_warehouses(item_code, warehouse): + from frappe.utils.nestedset import get_descendants_of + + items = [] + if frappe.get_cached_value("Warehouse", warehouse, "is_group"): + childrens = get_descendants_of("Warehouse", warehouse, ignore_permissions=True, order_by="lft") + for ch_warehouse in childrens: + items.append(frappe._dict({"item_code": item_code, "warehouse": ch_warehouse})) + else: + items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})] + + return items + + def get_items_for_stock_reco(warehouse, company): lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) items = frappe.db.sql( @@ -803,7 +819,7 @@ def get_items_for_stock_reco(warehouse, company): and i.is_stock_item = 1 and i.has_variants = 0 and exists( - select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse + select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse and is_group = 0 ) """, as_dict=1, @@ -818,7 +834,7 @@ def get_items_for_stock_reco(warehouse, company): where i.name = id.parent and exists( - select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse + select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse and is_group = 0 ) and i.is_stock_item = 1 and i.has_variants = 0 @@ -880,7 +896,7 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): frappe._dict( { "item_code": row[0], - "warehouse": warehouse, + "warehouse": row[3], "qty": row[8], "item_name": row[1], "batch_no": row[4], diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 31ea576ec1d..e6490f1717a 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -117,34 +117,37 @@ def get_stock_ledger_entries(filters): if filters.get(field): query = query.where(sle[field] == filters.get(field)) - return query.run(as_dict=True) + return query def get_item_warehouse_batch_map(filters, float_precision): - sle = get_stock_ledger_entries(filters) - iwb_map = {} + with frappe.db.unbuffered_cursor(): + sle = get_stock_ledger_entries(filters) + sle = sle.run(as_dict=True, as_iterator=True) - from_date = getdate(filters["from_date"]) - to_date = getdate(filters["to_date"]) + iwb_map = {} - for d in sle: - iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault( - d.batch_no, frappe._dict({"opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0}) - ) - qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no] - if d.posting_date < from_date: - qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) + flt( - d.actual_qty, float_precision + from_date = getdate(filters["from_date"]) + to_date = getdate(filters["to_date"]) + + for d in sle: + iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault( + d.batch_no, frappe._dict({"opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0}) ) - elif d.posting_date >= from_date and d.posting_date <= to_date: - if flt(d.actual_qty) > 0: - qty_dict.in_qty = flt(qty_dict.in_qty, float_precision) + flt(d.actual_qty, float_precision) - else: - qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) + abs( - flt(d.actual_qty, float_precision) + qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no] + if d.posting_date < from_date: + qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) + flt( + d.actual_qty, float_precision ) + elif d.posting_date >= from_date and d.posting_date <= to_date: + if flt(d.actual_qty) > 0: + qty_dict.in_qty = flt(qty_dict.in_qty, float_precision) + flt(d.actual_qty, float_precision) + else: + qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) + abs( + flt(d.actual_qty, float_precision) + ) - qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision) + qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision) return iwb_map diff --git a/erpnext/stock/report/item_prices/item_prices.js b/erpnext/stock/report/item_prices/item_prices.js index 868b503eb73..6724c1a2909 100644 --- a/erpnext/stock/report/item_prices/item_prices.js +++ b/erpnext/stock/report/item_prices/item_prices.js @@ -9,9 +9,6 @@ frappe.query_reports["Item Prices"] = { fieldtype: "Select", options: "Enabled Items only\nDisabled Items only\nAll Items", default: "Enabled Items only", - on_change: function (query_report) { - query_report.trigger_refresh(); - }, }, ], }; diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html index faf5e9278c7..8310063e575 100644 --- a/erpnext/templates/includes/footer/footer_powered.html +++ b/erpnext/templates/includes/footer/footer_powered.html @@ -1 +1 @@ -Powered by ERPNext +{{ _("Powered by {0}").format('ERPNext') }}