From 69a8e0dfacb80405d76b01ebd3c2d6dc8902223c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Feb 2025 09:05:17 +0530 Subject: [PATCH 01/18] fix: inventory dimension for maintence visit (cherry picked from commit cd4ba69262d7f99b54d5744b3b649bf345430175) # Conflicts: # erpnext/stock/doctype/inventory_dimension/inventory_dimension.py --- .../doctype/inventory_dimension/inventory_dimension.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 5d701a73393..4574e95c4d0 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -196,6 +196,16 @@ class InventoryDimension(Document): custom_fields["Stock Ledger Entry"] = dimension_field filter_custom_fields = {} +<<<<<<< HEAD +======= + ignore_doctypes = [ + "Serial and Batch Bundle", + "Serial and Batch Entry", + "Pick List Item", + "Maintenance Visit Purpose", + ] + +>>>>>>> cd4ba69262 (fix: inventory dimension for maintence visit) if custom_fields: for doctype, fields in custom_fields.items(): if isinstance(fields, dict): From 811953b9b0c74571c2f3b0ebcad57b16109e8a70 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 23 Feb 2025 19:47:06 +0530 Subject: [PATCH 02/18] chore: fix conflicts --- .../doctype/inventory_dimension/inventory_dimension.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index 4574e95c4d0..ecc726fd86c 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -196,18 +196,17 @@ class InventoryDimension(Document): custom_fields["Stock Ledger Entry"] = dimension_field filter_custom_fields = {} -<<<<<<< HEAD -======= + ignore_doctypes = [ - "Serial and Batch Bundle", - "Serial and Batch Entry", "Pick List Item", "Maintenance Visit Purpose", ] ->>>>>>> cd4ba69262 (fix: inventory dimension for maintence visit) if custom_fields: for doctype, fields in custom_fields.items(): + if doctype in ignore_doctypes: + continue + if isinstance(fields, dict): fields = [fields] From ca94ad3a2440102f1c64229d4111c4237beec1ab Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 Mar 2025 13:38:38 +0530 Subject: [PATCH 03/18] fix: incorrectly billed amount in the purchase receipt (cherry picked from commit a5271fdb2e5c826ec24162ddd101ae95ed9fdcd7) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- .../purchase_receipt/purchase_receipt.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 132 ++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 4a9e0d08bd8..e7e17924e12 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -865,15 +865,19 @@ def get_billed_amount_against_po(po_items): if not po_items: return {} + purchase_invoice = frappe.qb.DocType("Purchase Invoice") purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(purchase_invoice_item) + .inner_join(purchase_invoice) + .on(purchase_invoice_item.parent == purchase_invoice.name) .select(fn.Sum(purchase_invoice_item.amount).as_("billed_amt"), purchase_invoice_item.po_detail) .where( (purchase_invoice_item.po_detail.isin(po_items)) - & (purchase_invoice_item.docstatus == 1) + & (purchase_invoice.docstatus == 1) & (purchase_invoice_item.pr_detail.isnull()) + & (purchase_invoice.update_stock == 0) ) .groupby(purchase_invoice_item.po_detail) ).run(as_dict=1) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index adb6e690596..ac2da34785e 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2731,6 +2731,138 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(return_pr.per_billed, 100) self.assertEqual(return_pr.status, "Completed") +<<<<<<< HEAD +======= + def test_do_not_allow_to_inward_same_serial_no_multiple_times(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 0) + + item_code = make_item( + "Test Do Not Allow INWD Item 123", {"has_serial_no": 1, "serial_no_series": "SN-TDAISN-.#####"} + ).name + + pr = make_purchase_receipt(item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1) + serial_no = get_serial_nos_from_bundle(pr.items[0].serial_and_batch_bundle)[0] + + status = frappe.db.get_value("Serial No", serial_no, "status") + self.assertTrue(status == "Active") + + make_stock_entry( + item_code=item_code, + source=pr.items[0].warehouse, + qty=1, + serial_no=serial_no, + use_serial_batch_fields=1, + ) + + status = frappe.db.get_value("Serial No", serial_no, "status") + self.assertFalse(status == "Active") + + pr = make_purchase_receipt( + item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1, do_not_submit=1 + ) + pr.items[0].serial_no = serial_no + pr.save() + + self.assertRaises(frappe.exceptions.ValidationError, pr.submit) + + frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1) + + def test_seral_no_return_validation(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return, + ) + + sn_item_code = make_item( + "Test Serial No for Validation", {"has_serial_no": 1, "serial_no_series": "SN-TSNFVAL-.#####"} + ).name + + pr1 = make_purchase_receipt(item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1) + pr1_serial_nos = get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle) + + serial_no_pr = make_purchase_receipt( + item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1 + ) + serial_no_pr_serial_nos = get_serial_nos_from_bundle(serial_no_pr.items[0].serial_and_batch_bundle) + + sn_return = make_purchase_return(serial_no_pr.name) + sn_return.items[0].qty = -1 + sn_return.items[0].received_qty = -1 + sn_return.items[0].serial_no = pr1_serial_nos[0] + sn_return.save() + self.assertRaises(frappe.ValidationError, sn_return.submit) + + sn_return = make_purchase_return(serial_no_pr.name) + sn_return.items[0].qty = -1 + sn_return.items[0].received_qty = -1 + sn_return.items[0].serial_no = serial_no_pr_serial_nos[0] + sn_return.save() + sn_return.submit() + + def test_batch_no_return_validation(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return, + ) + + batch_item_code = make_item( + "Test Batch No for Validation", + {"has_batch_no": 1, "batch_number_series": "BT-TSNFVAL-.#####", "create_new_batch": 1}, + ).name + + pr1 = make_purchase_receipt(item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1) + batch_no = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle) + + batch_no_pr = make_purchase_receipt( + item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1 + ) + original_batch_no = get_batch_from_bundle(batch_no_pr.items[0].serial_and_batch_bundle) + + batch_return = make_purchase_return(batch_no_pr.name) + batch_return.items[0].qty = -1 + batch_return.items[0].received_qty = -1 + batch_return.items[0].batch_no = batch_no + batch_return.save() + self.assertRaises(frappe.ValidationError, batch_return.submit) + + batch_return = make_purchase_return(batch_no_pr.name) + batch_return.items[0].qty = -1 + batch_return.items[0].received_qty = -1 + batch_return.items[0].batch_no = original_batch_no + batch_return.save() + batch_return.submit() + + def test_pr_status_based_on_invoices_with_update_stock(self): + from erpnext.buying.doctype.purchase_order.purchase_order import ( + make_purchase_invoice as _make_purchase_invoice, + ) + from erpnext.buying.doctype.purchase_order.purchase_order import ( + make_purchase_receipt as _make_purchase_receipt, + ) + from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_pr_against_po, + create_purchase_order, + ) + + item_code = "Test Item for PR Status Based on Invoices" + create_item(item_code) + + po = create_purchase_order(item_code=item_code, qty=10) + pi = _make_purchase_invoice(po.name) + pi.update_stock = 1 + pi.items[0].qty = 5 + pi.submit() + + po.reload() + self.assertEqual(po.per_billed, 50) + + pr = _make_purchase_receipt(po.name) + self.assertEqual(pr.items[0].qty, 5) + pr.submit() + pr.reload() + self.assertEqual(pr.status, "To Bill") + +>>>>>>> a5271fdb2e (fix: incorrectly billed amount in the purchase receipt) def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From eebf6cf877bc371d2619917fd2c2239ebff1abf1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 3 Mar 2025 14:48:07 +0530 Subject: [PATCH 04/18] chore: fix conflicts --- .../purchase_receipt/test_purchase_receipt.py | 102 ------------------ 1 file changed, 102 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index ac2da34785e..ef690cda67d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2731,107 +2731,6 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(return_pr.per_billed, 100) self.assertEqual(return_pr.status, "Completed") -<<<<<<< HEAD -======= - def test_do_not_allow_to_inward_same_serial_no_multiple_times(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - - frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 0) - - item_code = make_item( - "Test Do Not Allow INWD Item 123", {"has_serial_no": 1, "serial_no_series": "SN-TDAISN-.#####"} - ).name - - pr = make_purchase_receipt(item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1) - serial_no = get_serial_nos_from_bundle(pr.items[0].serial_and_batch_bundle)[0] - - status = frappe.db.get_value("Serial No", serial_no, "status") - self.assertTrue(status == "Active") - - make_stock_entry( - item_code=item_code, - source=pr.items[0].warehouse, - qty=1, - serial_no=serial_no, - use_serial_batch_fields=1, - ) - - status = frappe.db.get_value("Serial No", serial_no, "status") - self.assertFalse(status == "Active") - - pr = make_purchase_receipt( - item_code=item_code, qty=1, rate=100, use_serial_batch_fields=1, do_not_submit=1 - ) - pr.items[0].serial_no = serial_no - pr.save() - - self.assertRaises(frappe.exceptions.ValidationError, pr.submit) - - frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1) - - def test_seral_no_return_validation(self): - from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - make_purchase_return, - ) - - sn_item_code = make_item( - "Test Serial No for Validation", {"has_serial_no": 1, "serial_no_series": "SN-TSNFVAL-.#####"} - ).name - - pr1 = make_purchase_receipt(item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1) - pr1_serial_nos = get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle) - - serial_no_pr = make_purchase_receipt( - item_code=sn_item_code, qty=5, rate=100, use_serial_batch_fields=1 - ) - serial_no_pr_serial_nos = get_serial_nos_from_bundle(serial_no_pr.items[0].serial_and_batch_bundle) - - sn_return = make_purchase_return(serial_no_pr.name) - sn_return.items[0].qty = -1 - sn_return.items[0].received_qty = -1 - sn_return.items[0].serial_no = pr1_serial_nos[0] - sn_return.save() - self.assertRaises(frappe.ValidationError, sn_return.submit) - - sn_return = make_purchase_return(serial_no_pr.name) - sn_return.items[0].qty = -1 - sn_return.items[0].received_qty = -1 - sn_return.items[0].serial_no = serial_no_pr_serial_nos[0] - sn_return.save() - sn_return.submit() - - def test_batch_no_return_validation(self): - from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - make_purchase_return, - ) - - batch_item_code = make_item( - "Test Batch No for Validation", - {"has_batch_no": 1, "batch_number_series": "BT-TSNFVAL-.#####", "create_new_batch": 1}, - ).name - - pr1 = make_purchase_receipt(item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1) - batch_no = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle) - - batch_no_pr = make_purchase_receipt( - item_code=batch_item_code, qty=5, rate=100, use_serial_batch_fields=1 - ) - original_batch_no = get_batch_from_bundle(batch_no_pr.items[0].serial_and_batch_bundle) - - batch_return = make_purchase_return(batch_no_pr.name) - batch_return.items[0].qty = -1 - batch_return.items[0].received_qty = -1 - batch_return.items[0].batch_no = batch_no - batch_return.save() - self.assertRaises(frappe.ValidationError, batch_return.submit) - - batch_return = make_purchase_return(batch_no_pr.name) - batch_return.items[0].qty = -1 - batch_return.items[0].received_qty = -1 - batch_return.items[0].batch_no = original_batch_no - batch_return.save() - batch_return.submit() - def test_pr_status_based_on_invoices_with_update_stock(self): from erpnext.buying.doctype.purchase_order.purchase_order import ( make_purchase_invoice as _make_purchase_invoice, @@ -2862,7 +2761,6 @@ class TestPurchaseReceipt(FrappeTestCase): pr.reload() self.assertEqual(pr.status, "To Bill") ->>>>>>> a5271fdb2e (fix: incorrectly billed amount in the purchase receipt) def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 369a692af9c777d365671253b1cac9510b5de147 Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Fri, 28 Feb 2025 11:08:30 +0530 Subject: [PATCH 05/18] fix: exclude cancelled gl entries (cherry picked from commit 3251a331dd76767e51f49dfb3cbbad6e932cb7cc) --- .../report/budget_variance_report/budget_variance_report.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index e540aa9993c..db42d23a839 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -263,6 +263,7 @@ def get_actual_details(name, filters): and ba.account=gl.account and b.{budget_against} = gl.{budget_against} and gl.fiscal_year between %s and %s + and gl.is_cancelled = 0 and b.{budget_against} = %s and exists( select From f8bbcab3a517b1f1bc06968bd49e239a7f2e6ea1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 21:36:57 +0530 Subject: [PATCH 06/18] fix(patch): Ensure SLE indexes (backport #46131) (#46134) * fix(patch): Ensure SLE indexes (#46131) Because of the way this change was pushed in parts, some sites don't see this as "update" and don't have the new indexes. (cherry picked from commit f62aa8fc57dcf4f2d4282811db1c85ff4259a2a5) # Conflicts: # erpnext/patches.txt * fix: resolved conflict --------- Co-authored-by: Ankush Menat Co-authored-by: Nabin Hait --- erpnext/patches.txt | 2 +- .../stock/doctype/stock_ledger_entry/patches/__init__.py | 0 .../stock_ledger_entry/patches/ensure_sle_indexes.py | 9 +++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 erpnext/stock/doctype/stock_ledger_entry/patches/__init__.py create mode 100644 erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ab842095412..cdec75b64ec 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -371,4 +371,4 @@ erpnext.patches.v14_0.update_stock_uom_in_work_order_item erpnext.patches.v14_0.disable_add_row_in_gross_profit execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.update_posting_datetime - +erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes diff --git a/erpnext/stock/doctype/stock_ledger_entry/patches/__init__.py b/erpnext/stock/doctype/stock_ledger_entry/patches/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py b/erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py new file mode 100644 index 00000000000..7f29b27af3f --- /dev/null +++ b/erpnext/stock/doctype/stock_ledger_entry/patches/ensure_sle_indexes.py @@ -0,0 +1,9 @@ +from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import ( + on_doctype_update as create_sle_indexes, +) + + +def execute(): + """Ensure SLE Indexes""" + + create_sle_indexes() From df4f4d9a310a1010bdb1c749f29ecc51a0d397ec Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Wed, 5 Feb 2025 18:40:21 +0530 Subject: [PATCH 07/18] fix: change voucher_type and voucher_no field type to data (cherry picked from commit f8ab02192037171e4f8de7d79edfd9978238ac09) # Conflicts: # erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json # erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py --- .../tax_withheld_vouchers.json | 14 ++++++++------ .../tax_withheld_vouchers.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index 46b430c6594..4b1f586b60c 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -13,17 +13,15 @@ "fields": [ { "fieldname": "voucher_type", - "fieldtype": "Link", + "fieldtype": "Data", "in_list_view": 1, - "label": "Voucher Type", - "options": "DocType" + "label": "Voucher Type" }, { "fieldname": "voucher_name", - "fieldtype": "Dynamic Link", + "fieldtype": "Data", "in_list_view": 1, - "label": "Voucher Name", - "options": "voucher_type" + "label": "Voucher Name" }, { "fieldname": "taxable_amount", @@ -36,7 +34,11 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-01-13 13:40:41.479208", +======= + "modified": "2025-02-05 16:39:14.863698", +>>>>>>> f8ab021920 (fix: change voucher_type and voucher_no field type to data) "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py index ea54c5403a8..e2e5bc851ca 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py @@ -6,4 +6,23 @@ from frappe.model.document import Document class TaxWithheldVouchers(Document): +<<<<<<< HEAD +======= + # 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 + + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + taxable_amount: DF.Currency + voucher_name: DF.Data | None + voucher_type: DF.Data | None + # end: auto-generated types + +>>>>>>> f8ab021920 (fix: change voucher_type and voucher_no field type to data) pass From 1089cdf21337603c3fc5fcffe50d0bcf929ac170 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 4 Mar 2025 15:46:05 +0530 Subject: [PATCH 08/18] chore: resolve conflicts --- .../tax_withheld_vouchers.json | 4 ---- .../tax_withheld_vouchers.py | 19 ------------------- 2 files changed, 23 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index 4b1f586b60c..51dc3674594 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -34,11 +34,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-01-13 13:40:41.479208", -======= "modified": "2025-02-05 16:39:14.863698", ->>>>>>> f8ab021920 (fix: change voucher_type and voucher_no field type to data) "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py index e2e5bc851ca..ea54c5403a8 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py @@ -6,23 +6,4 @@ from frappe.model.document import Document class TaxWithheldVouchers(Document): -<<<<<<< HEAD -======= - # 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 - - parent: DF.Data - parentfield: DF.Data - parenttype: DF.Data - taxable_amount: DF.Currency - voucher_name: DF.Data | None - voucher_type: DF.Data | None - # end: auto-generated types - ->>>>>>> f8ab021920 (fix: change voucher_type and voucher_no field type to data) pass From 8e02dcfcaa0dd56cbd62996dfc176d8ca710e5e9 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 13 Feb 2025 14:56:11 +0530 Subject: [PATCH 09/18] fix: auto allocation for negative amount outstanding for Customers in Payment Entry (cherry picked from commit 6275b44a0bdc016d6ed492475cf474c0e94e7a89) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 25b67e2db31..d40efc52d84 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1519,7 +1519,7 @@ class PaymentEntry(AccountsController): allocated_positive_outstanding = paid_amount + allocated_negative_outstanding - elif self.party_type in ("Supplier", "Employee"): + elif self.party_type in ("Supplier", "Customer"): if paid_amount > total_negative_outstanding: if total_negative_outstanding == 0: frappe.msgprint( From cedf577b4c6f4e0de839bfda69fd755fc52032b9 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 4 Mar 2025 12:49:48 +0530 Subject: [PATCH 10/18] fix: do not include opening invoices in billed items to be received report (cherry picked from commit c1ddf444c65b50ec59f95e6a88b7e263d303aadc) --- .../billed_items_to_be_received/billed_items_to_be_received.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py index f6efc8a685c..dc6192e7544 100644 --- a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py @@ -27,6 +27,7 @@ def get_report_filters(report_filters): ["Purchase Invoice", "docstatus", "=", 1], ["Purchase Invoice", "per_received", "<", 100], ["Purchase Invoice", "update_stock", "=", 0], + ["Purchase Invoice", "is_opening", "!=", "Yes"], ] if report_filters.get("purchase_invoice"): From 7cde990d690125a263c0a2dc1aadb706e1ef64b0 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 20 Feb 2025 11:33:53 +0530 Subject: [PATCH 11/18] refactor: add new line ragardless of postal code (cherry picked from commit 746adfd057cb77cbc601da96dcd069b3bc50027a) --- erpnext/regional/address_template/templates/united_states.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/address_template/templates/united_states.html b/erpnext/regional/address_template/templates/united_states.html index 77fce46b9d7..f00f99c1299 100644 --- a/erpnext/regional/address_template/templates/united_states.html +++ b/erpnext/regional/address_template/templates/united_states.html @@ -1,4 +1,4 @@ {{ address_line1 }}
{% if address_line2 %}{{ address_line2 }}
{% endif -%} -{{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}
{% endif -%} +{{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}{% endif -%}
{% if country != "United States" %}{{ country }}{% endif -%} From 167069b823a01bd0a7eda2cc1e6689a49514e5c3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:37:56 +0530 Subject: [PATCH 12/18] fix: don't allow renaming account while system is actively in use (backport #46176) (#46209) * fix: don't allow renaming account while system is actively in use (#46176) (cherry picked from commit 999f1cf96db3752ca816093a1f3b75236688ab71) # Conflicts: # erpnext/accounts/doctype/account/account.py * chore: conflicts --------- Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/account/account.py | 28 ++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 8cbaef43180..e41d1b0f768 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -4,7 +4,7 @@ import frappe from frappe import _, throw -from frappe.utils import cint, cstr +from frappe.utils import add_to_date, cint, cstr, pretty_date from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of import erpnext @@ -400,6 +400,7 @@ def validate_account_number(name, account_number, company): @frappe.whitelist() def update_account_number(name, account_name, account_number=None, from_descendant=False): + _ensure_idle_system() account = frappe.db.get_value("Account", name, "company", as_dict=True) if not account: return @@ -461,6 +462,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda @frappe.whitelist() def merge_account(old, new): + _ensure_idle_system() # Validate properties before merging new_account = frappe.get_cached_doc("Account", new) old_account = frappe.get_cached_doc("Account", old) @@ -514,3 +516,27 @@ def sync_update_account_number_in_child( for d in frappe.db.get_values("Account", filters=filters, fieldname=["company", "name"], as_dict=True): update_account_number(d["name"], account_name, account_number, from_descendant=True) + + +def _ensure_idle_system(): + # Don't allow renaming if accounting entries are actively being updated, there are two main reasons: + # 1. Correctness: It's next to impossible to ensure that renamed account is not being used *right now*. + # 2. Performance: Renaming requires locking out many tables entirely and severely degrades performance. + + if frappe.flags.in_test: + return + + try: + # We also lock inserts to GL entry table with for_update here. + last_gl_update = frappe.db.get_value("GL Entry", {}, "modified", for_update=True, wait=False) + except frappe.QueryTimeoutError: + # wait=False fails immediately if there's an active transaction. + last_gl_update = add_to_date(None, seconds=-1) + + if last_gl_update > add_to_date(None, minutes=-5): + frappe.throw( + _( + "Last GL Entry update was done {}. This operation is not allowed while system is actively being used. Please wait for 5 minutes before retrying." + ).format(pretty_date(last_gl_update)), + title=_("System In Use"), + ) From fbf6d8c9e2b7fae93da32e6377ece7142f61d6c3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:37:49 +0530 Subject: [PATCH 13/18] chore: erpnext.com -> frappe.io/erpnext (backport #46288) (#46289) * chore: erpnext.com -> frappe.io/erpnext (#46288) (cherry picked from commit 41fe30ea6e339e98633a73444f443c4b463bbe57) # Conflicts: # README.md # pyproject.toml * Update README.md * Update pyproject.toml --------- Co-authored-by: Ankush Menat --- erpnext/hooks.py | 4 ++-- erpnext/setup/install.py | 2 +- erpnext/templates/includes/footer/footer_powered.html | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 14948d60e46..45ba4304985 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -4,7 +4,7 @@ app_publisher = "Frappe Technologies Pvt. Ltd." app_description = """ERP made simple""" app_icon = "fa fa-th" app_color = "#e74c3c" -app_email = "info@erpnext.com" +app_email = "hello@frappe.io" app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" app_logo_url = "/assets/erpnext/images/erpnext-logo.svg" @@ -479,7 +479,7 @@ email_brand_image = "assets/erpnext/images/erpnext-logo.jpg" default_mail_footer = """ Sent via - + ERPNext diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index dcf086f2d05..a5a75ebcce8 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -15,7 +15,7 @@ from erpnext.setup.doctype.incoterm.incoterm import create_incoterms from .default_success_action import get_default_success_action default_mail_footer = """
Sent via - ERPNext
""" + ERPNext""" def after_install(): diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html index 8310063e575..fb73931d18e 100644 --- a/erpnext/templates/includes/footer/footer_powered.html +++ b/erpnext/templates/includes/footer/footer_powered.html @@ -1 +1 @@ -{{ _("Powered by {0}").format('ERPNext') }} +{{ _("Powered by {0}").format('ERPNext') }} diff --git a/package.json b/package.json index 6c11e9dddc7..2f819f83eaf 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git+https://github.com/frappe/erpnext.git" }, - "homepage": "https://erpnext.com", + "homepage": "https://frappe.io/erpnext", "author": "Frappe Technologies Pvt. Ltd.", "license": "GPL-3.0", "bugs": { From b41ee667b9f6c97f071b6e3da1694ce0b07cd6f0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:03:27 +0530 Subject: [PATCH 14/18] perf: don't track seen for POS Invoice (backport #46187) (#46188) * perf: don't track seen for POS Invoice (#46187) This is a moving doctype. Do people even browse the list view? It doesn't make much sense, either. POS INvoices are rarely "reviewed" by multiple users. (cherry picked from commit ded0aab680932730273c6544256632b72ebed5e5) # Conflicts: # erpnext/accounts/doctype/pos_invoice/pos_invoice.json * chore: conflicts --------- Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index f5a9d17f088..7c13fe50f1a 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1623,6 +1623,5 @@ "states": [], "timeline_field": "customer", "title_field": "title", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file + "track_changes": 1 +} From 557a05b0ad69b98a080335f925ddf7871127674e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:04:39 +0530 Subject: [PATCH 15/18] fix: Add permission check in POS's `Toggle Recent Orders` (backport #46010) (#46273) * fix: use get_list to check permissions (cherry picked from commit a08bc6b913b932231abfd72ebf3acf82cf32288c) # Conflicts: # erpnext/selling/page/point_of_sale/point_of_sale.py * fix: resolve conflicts --------- Co-authored-by: Sanket322 Co-authored-by: ljain112 --- erpnext/selling/page/point_of_sale/point_of_sale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 7db9faacca2..6cb72d53b59 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -275,12 +275,12 @@ def get_past_order_list(search_term, status, limit=20): invoice_list = [] if search_term and status: - invoices_by_customer = frappe.db.get_all( + invoices_by_customer = frappe.db.get_list( "POS Invoice", filters={"customer": ["like", f"%{search_term}%"], "status": status}, fields=fields, ) - invoices_by_name = frappe.db.get_all( + invoices_by_name = frappe.db.get_list( "POS Invoice", filters={"name": ["like", f"%{search_term}%"], "status": status}, fields=fields, @@ -288,7 +288,7 @@ def get_past_order_list(search_term, status, limit=20): invoice_list = invoices_by_customer + invoices_by_name elif status: - invoice_list = frappe.db.get_all("POS Invoice", filters={"status": status}, fields=fields) + invoice_list = frappe.db.get_list("POS Invoice", filters={"status": status}, fields=fields) return invoice_list From 90b5f0b7bf2e0e8a9baca1c069bfe5ddd9c17052 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:17:17 +0530 Subject: [PATCH 16/18] fix: only include submitted docs for internal received quantity validation (backport #46262) (#46303) fix: only include submitted docs for internal received quantity validation (#46262) (cherry picked from commit 88fcdbb81e5aa95fdf29b17d5cdc6f4cc58ccabd) Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com> --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b37658b7784..2f4c154cb00 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -801,7 +801,7 @@ class StockController(AccountsController): child_tab.item_code, child_tab.qty, ) - .where(parent_tab.docstatus < 2) + .where(parent_tab.docstatus == 1) ) if self.doctype == "Purchase Invoice": From ac25d3e1c4de3842e81a5edda78e50c2f8501c6d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 5 Mar 2025 14:29:22 +0530 Subject: [PATCH 17/18] fix: incorrect batch picked in the pick list --- erpnext/stock/doctype/pick_list/pick_list.js | 7 +++ erpnext/stock/doctype/pick_list/pick_list.py | 66 +++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index 38b1ca7f76e..8f74373ac8d 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -2,6 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on("Pick List", { + after_save(frm) { + setTimeout(() => { + // Added to fix the issue of locations table not getting updated after save + frm.reload_doc(); + }, 500); + }, + setup: (frm) => { frm.set_indicator_formatter("item_code", function (doc) { return doc.stock_qty === 0 ? "red" : "green"; diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index a2a07e6d11e..a94370c48a5 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -25,10 +25,38 @@ from erpnext.stock.get_item_details import get_conversion_factor class PickList(Document): def validate(self): + self.validate_expired_batches() self.validate_for_qty() self.validate_stock_qty() self.check_serial_no_status() + def validate_expired_batches(self): + batches = [] + for row in self.get("locations"): + if row.get("batch_no") and row.get("picked_qty"): + batches.append(row.batch_no) + + if batches: + batch = frappe.qb.DocType("Batch") + query = ( + frappe.qb.from_(batch) + .select(batch.name) + .where( + (batch.name.isin(batches)) + & (batch.expiry_date <= frappe.utils.nowdate()) + & (batch.expiry_date.isnotnull()) + ) + ) + + expired_batches = query.run(as_dict=True) + if expired_batches: + msg = "
    " + "".join(f"
  • {batch.name}
  • " for batch in expired_batches) + "
" + + frappe.throw( + _("The following batches are expired, please restock them:
{0}").format(msg), + title=_("Expired Batches"), + ) + def before_save(self): self.update_status() if not self.pick_manually: @@ -271,6 +299,7 @@ class PickList(Document): self.remove(row) updated_locations = frappe._dict() + len_idx = len(self.get("locations")) or 0 for item_doc in items: item_code = item_doc.item_code @@ -313,6 +342,8 @@ class PickList(Document): if location.picked_qty > location.stock_qty: location.picked_qty = location.stock_qty + len_idx += 1 + location.idx = len_idx self.append("locations", location) # If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red @@ -321,6 +352,9 @@ class PickList(Document): for location in locations_replica: location.stock_qty = 0 location.picked_qty = 0 + + len_idx += 1 + location.idx = len_idx self.append("locations", location) frappe.msgprint( _( @@ -430,9 +464,11 @@ class PickList(Document): pi_item.item_code, pi_item.warehouse, pi_item.batch_no, - Sum(Case().when(pi_item.picked_qty > 0, pi_item.picked_qty).else_(pi_item.stock_qty)).as_( - "picked_qty" - ), + Sum( + Case() + .when((pi_item.picked_qty > 0) & (pi_item.docstatus == 1), pi_item.picked_qty) + .else_(pi_item.stock_qty) + ).as_("picked_qty"), Replace(GROUP_CONCAT(pi_item.serial_no), ",", "\n").as_("serial_no"), ) .where( @@ -465,8 +501,32 @@ class PickList(Document): else: picked_items[item_data.item_code][key] = data + self.update_picked_item_from_current_pick_list(picked_items) + return picked_items + def update_picked_item_from_current_pick_list(self, picked_items): + for row in self.get("locations"): + if flt(row.picked_qty) > 0: + key = (row.warehouse, row.batch_no) if row.batch_no else row.warehouse + serial_no = [x for x in row.serial_no.split("\n") if x] if row.serial_no else None + if row.item_code not in picked_items: + picked_items[row.item_code] = {} + + if key not in picked_items[row.item_code]: + picked_items[row.item_code][key] = frappe._dict( + { + "picked_qty": 0, + "serial_no": [], + "batch_no": row.batch_no or "", + "warehouse": row.warehouse, + } + ) + + picked_items[row.item_code][key]["picked_qty"] += flt(row.stock_qty) or flt(row.picked_qty) + if serial_no: + picked_items[row.item_code][key]["serial_no"].extend(serial_no) + def _get_product_bundles(self) -> dict[str, str]: # Dict[so_item_row: item_code] product_bundles = {} From b3b7e62a9016a1976946008627ad1887158b1f2d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:18:34 +0530 Subject: [PATCH 18/18] fix: consolidate gl entries by project in General Ledger Report (backport #46314) (#46320) fix: consolidate gl entries by project in General Ledger Report (#46314) (cherry picked from commit 1f685efcaf01d11583b7c0f23a4e2f35a32dce92) Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com> --- erpnext/accounts/report/general_ledger/general_ledger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index aa06dffb043..25d52998449 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -506,6 +506,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): for dim in accounting_dimensions: keylist.append(gle.get(dim)) keylist.append(gle.get("cost_center")) + keylist.append(gle.get("project")) key = tuple(keylist) if key not in consolidated_gle: @@ -617,10 +618,11 @@ def get_columns(filters): {"label": _("Against Account"), "fieldname": "against", "width": 120}, {"label": _("Party Type"), "fieldname": "party_type", "width": 100}, {"label": _("Party"), "fieldname": "party", "width": 100}, - {"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100}, ] if filters.get("include_dimensions"): + columns.append({"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100}) + for dim in get_accounting_dimensions(as_list=False): columns.append( {"label": _(dim.label), "options": dim.label, "fieldname": dim.fieldname, "width": 100}