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') }}