From 16fbf8299f0a90e0faf5620019c28e28cea25ed2 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 27 May 2026 00:23:04 +0000 Subject: [PATCH 01/13] chore(release): Bumped to Version 15.109.0 # [15.109.0](https://github.com/frappe/erpnext/compare/v15.108.3...v15.109.0) (2026-05-27) ### Bug Fixes * consider batchwise valuation in stock ageing report (backport [#54919](https://github.com/frappe/erpnext/issues/54919)) ([#55229](https://github.com/frappe/erpnext/issues/55229)) ([418a7fb](https://github.com/frappe/erpnext/commit/418a7fb3015553884ecb739126793dfd809e00b5)) * consumed operation cost calculation (backport [#54858](https://github.com/frappe/erpnext/issues/54858)) ([#55132](https://github.com/frappe/erpnext/issues/55132)) ([46d5395](https://github.com/frappe/erpnext/commit/46d5395148747f8b4259a418fab98035bc4897b5)) * default use_for_shopping_cart to 0 in set_taxes ([960be3e](https://github.com/frappe/erpnext/commit/960be3e081959f6dff9537d5cab58b466020a5da)) * edit stock uom qty for purchase documents (backport [#55135](https://github.com/frappe/erpnext/issues/55135)) ([#55178](https://github.com/frappe/erpnext/issues/55178)) ([425e6c5](https://github.com/frappe/erpnext/commit/425e6c52f411c1bd69ea18fb3ce5e16cf04b6398)) * ERPNextTestSuite to change_settings ([76078a7](https://github.com/frappe/erpnext/commit/76078a7fb941ba2b820e1af9ac8c040f6a774654)) * faster range calculation on process period closing voucher ([bf27f2d](https://github.com/frappe/erpnext/commit/bf27f2d869417789a1c4d74c30c6ff3bc5a3d06e)) * fg valuation rate in repack entry when multiple FGs ([238f168](https://github.com/frappe/erpnext/commit/238f1685f1fcf33f6bef6a6a18b5c2b02ab27b11)) * **general-ledger:** show raw GL entries when categorize_by is empty (backport [#54816](https://github.com/frappe/erpnext/issues/54816)) ([#54829](https://github.com/frappe/erpnext/issues/54829)) ([b972b7c](https://github.com/frappe/erpnext/commit/b972b7c307cf01f754d02ee872b6f949908e9a68)) * import change_settings ([9d21199](https://github.com/frappe/erpnext/commit/9d211990c3a3782a938fb2ad7c24d2941783a73e)) * inclusive tax amount not considered while setting LCV from purchase invoice ([cba4c9f](https://github.com/frappe/erpnext/commit/cba4c9f0ee64f882657ed4bbea27f2dc5475f61a)) * incoming rate for legacy serial no ([6e6ef83](https://github.com/frappe/erpnext/commit/6e6ef83d60d3f42b32b2d578889e8bd5f0f2e62b)) * incorrect error message string in sales order (backport [#55090](https://github.com/frappe/erpnext/issues/55090)) ([#55094](https://github.com/frappe/erpnext/issues/55094)) ([04e28f9](https://github.com/frappe/erpnext/commit/04e28f95564e1579e25b25f87214a5916a069b87)) * invalid filter on item_group (backport [#55186](https://github.com/frappe/erpnext/issues/55186)) ([#55187](https://github.com/frappe/erpnext/issues/55187)) ([25739ae](https://github.com/frappe/erpnext/commit/25739ae217bee2411dd3d8564fd431f1c80ce6b5)) * merge conflicts ([59e9f51](https://github.com/frappe/erpnext/commit/59e9f5192c56d4bff7c10290e01fb30f8c3f301e)) * **payment_entry:** sync paid/received amounts for cross-currency entries (backport [#55270](https://github.com/frappe/erpnext/issues/55270)) ([#55271](https://github.com/frappe/erpnext/issues/55271)) ([d31a051](https://github.com/frappe/erpnext/commit/d31a051c747923d76559207e5642c02d706d7b28)) * prevent AttributeError in batch query filters (backport [#55257](https://github.com/frappe/erpnext/issues/55257)) ([#55278](https://github.com/frappe/erpnext/issues/55278)) ([4f89f3a](https://github.com/frappe/erpnext/commit/4f89f3a85646061ef39730eb658b9904f7b3b0b3)) * **project:** update customer and sales order as no copy ([9145760](https://github.com/frappe/erpnext/commit/914576040eb4d30b2f85aa4733268e8b9474315a)) * removed redundant code ([259f499](https://github.com/frappe/erpnext/commit/259f499e2550dcaa71907ce2f32965a817a6f376)) * set bin details when adding item using update items (backport [#55096](https://github.com/frappe/erpnext/issues/55096)) ([#55097](https://github.com/frappe/erpnext/issues/55097)) ([aa79247](https://github.com/frappe/erpnext/commit/aa79247c390d0f8506764035434f75f0ffb2ee94)) * single variant creation error (backport [#55286](https://github.com/frappe/erpnext/issues/55286)) ([#55288](https://github.com/frappe/erpnext/issues/55288)) ([937eb87](https://github.com/frappe/erpnext/commit/937eb87932e9fd6fdbacca33638e9012a4f15f77)) * **stock:** apply posting datetime filters while fetching available batches (backport [#54976](https://github.com/frappe/erpnext/issues/54976)) ([#55184](https://github.com/frappe/erpnext/issues/55184)) ([ff442cd](https://github.com/frappe/erpnext/commit/ff442cd8e7611d62d5f187376d3dc6160fd91992)) * **stock:** remove precision for valuation rate while creating sle (backport [#55249](https://github.com/frappe/erpnext/issues/55249)) ([#55259](https://github.com/frappe/erpnext/issues/55259)) ([8b241b4](https://github.com/frappe/erpnext/commit/8b241b45e2c6768ee33515eff4d537c5513158d1)) * **stock:** remove recalculate current qty function ([#55121](https://github.com/frappe/erpnext/issues/55121)) ([1c90c3b](https://github.com/frappe/erpnext/commit/1c90c3bbc210a1280fbedc1cac0f2061b97f9428)) * update import ([31c251d](https://github.com/frappe/erpnext/commit/31c251d9561d8149bc8d79e0e2c4a41bec18e7f6)) * use passed posting date in make_reverse_gl_entries ([4436585](https://github.com/frappe/erpnext/commit/4436585aa07a82ab3704d8091fd99482854aa854)) ### Features * add get_parent_supplier_groups using query builder ([6517ed7](https://github.com/frappe/erpnext/commit/6517ed72b4816b69f080259c6a7135bd82e30fe9)) ### Performance Improvements * skip delink_original_entry during cancellation when Immutable Ledger is enabled ([#55130](https://github.com/frappe/erpnext/issues/55130)) ([034e159](https://github.com/frappe/erpnext/commit/034e159ee4cb474d45fd76f44b7cfb95fea4460c)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index dbda07fc37e..36897538fc3 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from frappe.utils.user import is_website_user -__version__ = "15.108.3" +__version__ = "15.109.0" def get_default_company(user=None): From bfdf1e43f9f2bf4e855190bce96b543c3f1afe01 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 17:35:20 +0530 Subject: [PATCH 02/13] fix: material transfer in transit issue (backport #55320) (backport #55324) (#55404) Co-authored-by: Rohit Waghchaure fix: material transfer in transit issue (backport #55320) (#55324) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f0d7e10ad73..e6d6dab7426 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -3132,12 +3132,12 @@ class StockEntry(StockController): args = { "source_dt": "Stock Entry Detail", "target_field": "transferred_qty", - "target_ref_field": "qty", + "target_ref_field": "transfer_qty", "target_dt": "Stock Entry Detail", "join_field": "ste_detail", "target_parent_dt": "Stock Entry", "target_parent_field": "per_transferred", - "source_field": "qty", + "source_field": "transfer_qty", "percent_join_field": "against_stock_entry", } From 9e7b03173d0d16823e2261dbc1cfcc935ee47c0b Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Fri, 29 May 2026 12:07:01 +0000 Subject: [PATCH 03/13] chore(release): Bumped to Version 15.109.1 ## [15.109.1](https://github.com/frappe/erpnext/compare/v15.109.0...v15.109.1) (2026-05-29) ### Bug Fixes * material transfer in transit issue (backport [#55320](https://github.com/frappe/erpnext/issues/55320)) (backport [#55324](https://github.com/frappe/erpnext/issues/55324)) ([#55404](https://github.com/frappe/erpnext/issues/55404)) ([bfdf1e4](https://github.com/frappe/erpnext/commit/bfdf1e43f9f2bf4e855190bce96b543c3f1afe01)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 36897538fc3..19e050c3069 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from frappe.utils.user import is_website_user -__version__ = "15.109.0" +__version__ = "15.109.1" def get_default_company(user=None): From c2063c4707eccbf67e1ec63b00587a4f8e6044b3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 29 May 2026 19:17:34 +0530 Subject: [PATCH 04/13] fix: billing address does not belongs to the company error (cherry picked from commit 9df07b367a29eceae700614d5a98485cf48df080) (cherry picked from commit 5c392d61236176c367544dc336468b82b38eff5b) --- erpnext/public/js/controllers/buying.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 01d458d6b16..ff7218d78ae 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -176,14 +176,9 @@ erpnext.buying = { callback: (r) => { if (!r.message) return; - if (!this.frm.doc.billing_address) { - this.frm.set_value("billing_address", r.message.primary_address || ""); - } + this.frm.set_value("billing_address", r.message.primary_address || ""); - if ( - frappe.meta.has_field(this.frm.doc.doctype, "shipping_address") && - !this.frm.doc.shipping_address - ) { + if (frappe.meta.has_field(this.frm.doc.doctype, "shipping_address")) { this.frm.set_value("shipping_address", r.message.shipping_address || ""); } }, From 1c5220b86f136e7f0faec020d057ada68e282c85 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 1 Jun 2026 06:08:30 +0000 Subject: [PATCH 05/13] chore(release): Bumped to Version 15.109.2 ## [15.109.2](https://github.com/frappe/erpnext/compare/v15.109.1...v15.109.2) (2026-06-01) ### Bug Fixes * billing address does not belongs to the company error ([c2063c4](https://github.com/frappe/erpnext/commit/c2063c4707eccbf67e1ec63b00587a4f8e6044b3)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 19e050c3069..6c6d7c3894d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from frappe.utils.user import is_website_user -__version__ = "15.109.1" +__version__ = "15.109.2" def get_default_company(user=None): From 4a6af25d11fd2d352a44005d7cf6bd277cf2a2e7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jun 2026 12:13:49 +0530 Subject: [PATCH 06/13] fix: only consider non-opening balance for Balance sheet accounts (cherry picked from commit a2b83340461de901f097a99910de8e79052f8b67) --- .../process_period_closing_voucher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index e85ebd29e52..e23751a8200 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -559,6 +559,9 @@ def process_individual_date(docname: str, date, report_type, parentfield): if parentfield == "z_opening_balances": query = query.where(gle.is_opening.eq("Yes")) + else: + # Keep balances aligned with legacy PCV logic (non-opening transactions only) + query = query.where(gle.is_opening.eq("No")) query = query.groupby(gle.account) for dim in dimensions: From 94e15ae9efacd91ae417a3f7ac4dda74c5722c21 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jun 2026 12:17:44 +0530 Subject: [PATCH 07/13] refactor: tabbed view for process period closing voucher (cherry picked from commit 1960c816194d8f3ce91c1508b2db23430ef481c1) --- .../process_period_closing_voucher.json | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index a06a16f156c..0a30b6564b5 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_bulk_edit": 1, "autoname": "format:Process-PCV-{###}", "creation": "2025-09-25 15:44:03.534699", "doctype": "DocType", @@ -7,11 +8,13 @@ "field_order": [ "parent_pcv", "status", + "amended_from", + "section_normal_balances", "p_l_closing_balance", - "normal_balances", "bs_closing_balance", - "z_opening_balances", - "amended_from" + "normal_balances", + "section_opening_balances", + "z_opening_balances" ], "fields": [ { @@ -64,17 +67,27 @@ "fieldname": "bs_closing_balance", "fieldtype": "JSON", "label": "Balance Sheet Closing Balance" + }, + { + "fieldname": "section_normal_balances", + "fieldtype": "Tab Break", + "label": "Normal Balances" + }, + { + "fieldname": "section_opening_balances", + "fieldtype": "Tab Break", + "label": "Opening Balances" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-11-05 11:40:24.996403", + "modified": "2026-06-01 12:16:37.374412", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", - "naming_rule": "Expression", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { From e314d0cfc5dfd5a22264d38abacaa2c77377f537 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jun 2026 12:51:04 +0530 Subject: [PATCH 08/13] refactor: color coded status in list view (cherry picked from commit cfeffbb3543ae05dd4fcd8f644c63e7a33144b72) --- .../process_period_closing_voucher_list.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher_list.js diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher_list.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher_list.js new file mode 100644 index 00000000000..4b117b8fbcf --- /dev/null +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher_list.js @@ -0,0 +1,17 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +// render +frappe.listview_settings["Process Period Closing Voucher"] = { + add_fields: ["status"], + get_indicator: function (doc) { + const status_colors = { + Queued: "blue", + Running: "orange", + Paused: "gray", + Completed: "green", + Cancelled: "red", + }; + return [__(doc.status), status_colors[doc.status], "status,=," + doc.status]; + }, +}; From 13eeddd1f6a691987018a3d6fe41c4b89ae135d7 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Mon, 1 Jun 2026 14:01:43 +0000 Subject: [PATCH 09/13] chore(release): Bumped to Version 15.109.3 ## [15.109.3](https://github.com/frappe/erpnext/compare/v15.109.2...v15.109.3) (2026-06-01) ### Bug Fixes * only consider non-opening balance for Balance sheet accounts ([4a6af25](https://github.com/frappe/erpnext/commit/4a6af25d11fd2d352a44005d7cf6bd277cf2a2e7)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6c6d7c3894d..a74faef55c6 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from frappe.utils.user import is_website_user -__version__ = "15.109.2" +__version__ = "15.109.3" def get_default_company(user=None): From d6b2fb2f96a32018ee79cdeb89e39321bfb959f1 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 2 Jun 2026 16:55:43 +0000 Subject: [PATCH 10/13] chore(release): Bumped to Version 15.110.0 # [15.110.0](https://github.com/frappe/erpnext/compare/v15.109.3...v15.110.0) (2026-06-02) ### Bug Fixes * billing address does not belongs to the company error ([5c392d6](https://github.com/frappe/erpnext/commit/5c392d61236176c367544dc336468b82b38eff5b)) * **book_appointment:** when scheduling is disabled, block API endpoints (backport [#55455](https://github.com/frappe/erpnext/issues/55455)) ([#55456](https://github.com/frappe/erpnext/issues/55456)) ([2a12ae1](https://github.com/frappe/erpnext/commit/2a12ae1afe368c122a63d56a5153271f23fa311f)) * check perm for account (backport [#55479](https://github.com/frappe/erpnext/issues/55479)) ([#55482](https://github.com/frappe/erpnext/issues/55482)) ([1238aeb](https://github.com/frappe/erpnext/commit/1238aeb30a4a48e7e6830cfaf8d81db74ad4b0a3)) * **issue:** check permission before issue status modification (backport [#55458](https://github.com/frappe/erpnext/issues/55458)) ([#55459](https://github.com/frappe/erpnext/issues/55459)) ([338feb3](https://github.com/frappe/erpnext/commit/338feb31e1410bde26c887fe56d7fa6aeaaea273)) * **je:** preserve account on duplicate row when party row exists (backport [#55180](https://github.com/frappe/erpnext/issues/55180)) ([#55513](https://github.com/frappe/erpnext/issues/55513)) ([741216d](https://github.com/frappe/erpnext/commit/741216d3eb726d5429b12512e3aefaebf00e094c)) * **manufacturing:** allow to edit batch size while creating a work order ([#55332](https://github.com/frappe/erpnext/issues/55332)) ([41bf2f3](https://github.com/frappe/erpnext/commit/41bf2f32fd223b1074988bea0a5dcecfaee651d7)) * material transfer in transit issue (backport [#55320](https://github.com/frappe/erpnext/issues/55320)) ([#55324](https://github.com/frappe/erpnext/issues/55324)) ([067c23f](https://github.com/frappe/erpnext/commit/067c23f20e5541eacb37a188be379121c2e7ddfb)) * new bom version should not recalculate operations through routing (backport [#55370](https://github.com/frappe/erpnext/issues/55370)) ([#55371](https://github.com/frappe/erpnext/issues/55371)) ([4669ff2](https://github.com/frappe/erpnext/commit/4669ff295f6beab550fd05980bc6927297ee36a7)) * pick correct name when creating user from RFQ (backport [#55468](https://github.com/frappe/erpnext/issues/55468)) ([#55471](https://github.com/frappe/erpnext/issues/55471)) ([e429e60](https://github.com/frappe/erpnext/commit/e429e608c2ab9451bf307b46776563dc8a3d2a44)) * **pos:** escape html output in pos page templates (backport [#55527](https://github.com/frappe/erpnext/issues/55527)) ([#55528](https://github.com/frappe/erpnext/issues/55528)) ([689a3f5](https://github.com/frappe/erpnext/commit/689a3f50ae114c2e9751fe9dba2184b40d6674d9)) * **pos:** escape item data on pos item selector (backport [#55503](https://github.com/frappe/erpnext/issues/55503)) ([#55523](https://github.com/frappe/erpnext/issues/55523)) ([96bd97d](https://github.com/frappe/erpnext/commit/96bd97dd6d90b6d0c2186fb83d018621197b57da)) * **pos:** preserve contacts and enforce permissions in set_customer_info (backport [#55463](https://github.com/frappe/erpnext/issues/55463)) ([#55465](https://github.com/frappe/erpnext/issues/55465)) ([0353262](https://github.com/frappe/erpnext/commit/03532624b847598e6b27e7fd222f38bb748bbe5f)) * **ppr:** make default_advance_account optional ([aa94c3f](https://github.com/frappe/erpnext/commit/aa94c3ff22b6f1cb92b1b49c7bbd3b441228f707)) * **quotation:** made customer contact column visible (backport [#55433](https://github.com/frappe/erpnext/issues/55433)) ([#55434](https://github.com/frappe/erpnext/issues/55434)) ([a2d924c](https://github.com/frappe/erpnext/commit/a2d924c48f4192eccba4be010b04dd6f68b3ea44)) * **regional:** Japanese CT Rate (backport [#54998](https://github.com/frappe/erpnext/issues/54998)) ([#55437](https://github.com/frappe/erpnext/issues/55437)) ([2a52ea6](https://github.com/frappe/erpnext/commit/2a52ea6850e84e45338456d93c226c8085962746)) * replace get_query with get_list for permission-aware queries in v15 ([ad511b8](https://github.com/frappe/erpnext/commit/ad511b80c05b8f2efddc1b03494b9a473808f7b9)) * stock reco for legacy serial nos ([93dcba4](https://github.com/frappe/erpnext/commit/93dcba40ec3819a175c9c088d885e37159f848d8)) * **stock:** add warning message to notify the user to configure the inspection ([42e2fd5](https://github.com/frappe/erpnext/commit/42e2fd5fc98c4881aadfa9959a0fb725e287109c)) * **stock:** allow to create quality inspection after purchase/delivery ([10664b7](https://github.com/frappe/erpnext/commit/10664b7b9539f822e126014c9e8dae418ce818d6)) * **stock:** change qb to qb get_query to fix filter issues (backport [#55443](https://github.com/frappe/erpnext/issues/55443)) ([#55444](https://github.com/frappe/erpnext/issues/55444)) ([75d00ef](https://github.com/frappe/erpnext/commit/75d00ef173c24d4230d20f8311325520277f90e4)) * **stock:** change valuation rate column label in stock ledger entry/report (backport [#55323](https://github.com/frappe/erpnext/issues/55323)) ([#55393](https://github.com/frappe/erpnext/issues/55393)) ([94fd15e](https://github.com/frappe/erpnext/commit/94fd15e550bc2a058463c9002a5ba043b7c677cf)) * **stock:** get_actual_qty during cancellations (backport [#55388](https://github.com/frappe/erpnext/issues/55388)) ([#55391](https://github.com/frappe/erpnext/issues/55391)) ([ad6e3a4](https://github.com/frappe/erpnext/commit/ad6e3a45d224b420a7b3ff4ed20c72cb40783e7f)) * update default_advance_account type ([7200c22](https://github.com/frappe/erpnext/commit/7200c228900b2033a681a7f3e65417c98626b6b7)) * use get_query instead of get_all for data fetching ([264433b](https://github.com/frappe/erpnext/commit/264433b23d61ffa41a75adcc9c554941e1de4e29)) ### Features * **payment-entry:** warn user before cancelling reconciled payment entry ([87c6ad4](https://github.com/frappe/erpnext/commit/87c6ad4f85f1401fdf5cd804c4b130398d4ad80b)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a74faef55c6..4be8b579505 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from frappe.utils.user import is_website_user -__version__ = "15.109.3" +__version__ = "15.110.0" def get_default_company(user=None): From 2b3e3dfd83558539f79bc6a5ecd5462f6678dd21 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 10 Jun 2026 00:24:00 +0000 Subject: [PATCH 11/13] chore(release): Bumped to Version 15.111.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # [15.111.0](https://github.com/frappe/erpnext/compare/v15.110.0...v15.111.0) (2026-06-10) ### Bug Fixes * **accounts:** include asset items in purchase receipt validation ([#55150](https://github.com/frappe/erpnext/issues/55150)) ([611849f](https://github.com/frappe/erpnext/commit/611849f9534068d621f35cec155f83d9eebcade4)) * add permission checks in accounts whitelisted methods ([2d1c0dc](https://github.com/frappe/erpnext/commit/2d1c0dcb53e4ff1069ceb39f67063cb954f578dd)) * bypass project permission check when updating consumed material … (backport [#55645](https://github.com/frappe/erpnext/issues/55645)) ([#55706](https://github.com/frappe/erpnext/issues/55706)) ([cedd0a1](https://github.com/frappe/erpnext/commit/cedd0a19035f6e37d17c907478fcb7587cf6f4fb)) * **cheque_print_template:** print format creation from cheque print template requires system manager (backport [#55708](https://github.com/frappe/erpnext/issues/55708)) ([#55711](https://github.com/frappe/erpnext/issues/55711)) ([2f5b93e](https://github.com/frappe/erpnext/commit/2f5b93e308446f0842c413cf9e2c2eb1d9de0d42)) * correct field order in Address and Contacts report ([bde46cf](https://github.com/frappe/erpnext/commit/bde46cffd0e2321860435eceeaa6c59589ef315c)) * correct field order in Address and Contacts report ([08f3cf9](https://github.com/frappe/erpnext/commit/08f3cf98f9e0738a01ca8a5809fdbfb1b7bb6104)) * do not allow to make changes in SABB after submit ([07b6111](https://github.com/frappe/erpnext/commit/07b61113af40c5a247b1cb51c6bdb4afc8e83eeb)) * drop ignore_permissions handling from add_ac ([2d0e3fd](https://github.com/frappe/erpnext/commit/2d0e3fd9af521e407be35fd09e9427176609513f)) * duplicating a Customer/Supplier shouldn't inherit the source's primary contact and address (backport [#55421](https://github.com/frappe/erpnext/issues/55421)) ([#55608](https://github.com/frappe/erpnext/issues/55608)) ([013bd1a](https://github.com/frappe/erpnext/commit/013bd1a5664a7f3a5b65f26b77a63644e06f68eb)) * handle multi-select stock ageing filters ([#55776](https://github.com/frappe/erpnext/issues/55776)) ([95f46df](https://github.com/frappe/erpnext/commit/95f46dfc015517ca390b29ad27c46fa4575f7fb2)) * **item:** format integer numeric variant attributes without decimals (backport [#55561](https://github.com/frappe/erpnext/issues/55561)) ([#55563](https://github.com/frappe/erpnext/issues/55563)) ([4772799](https://github.com/frappe/erpnext/commit/4772799db22292cf236a4a8759938f726a691b59)) * naming series issue ([e7eaa87](https://github.com/frappe/erpnext/commit/e7eaa87a77db77b0d421b1aa3c70a5024c4e378f)) * persist main item code for MR plan items ([#55623](https://github.com/frappe/erpnext/issues/55623)) ([e8e0514](https://github.com/frappe/erpnext/commit/e8e0514a30e3c8514c25c85e74e7d36ad612dc67)) * prevent leakage of party-derived fields in cross doctype transactions (backport [#55336](https://github.com/frappe/erpnext/issues/55336)) ([#55578](https://github.com/frappe/erpnext/issues/55578)) ([0e64acb](https://github.com/frappe/erpnext/commit/0e64acb0fa04042268805e11fa4f7b4a082708aa)) * prevent selling items from sample retention warehouse (backport [#55613](https://github.com/frappe/erpnext/issues/55613)) ([#55633](https://github.com/frappe/erpnext/issues/55633)) ([c15012c](https://github.com/frappe/erpnext/commit/c15012cd51c80baff894081aa25472a6c6d53959)) * **process statement of accounts:** validate pdf_name and validate permission before triggering send_auto_email (backport [#55781](https://github.com/frappe/erpnext/issues/55781)) ([#55782](https://github.com/frappe/erpnext/issues/55782)) ([18ca96c](https://github.com/frappe/erpnext/commit/18ca96c36ba65362bf1c25abb8eab32c64c6c7dd)) * remove item name from update items dialog item code column (backport [#55718](https://github.com/frappe/erpnext/issues/55718)) ([#55722](https://github.com/frappe/erpnext/issues/55722)) ([09453f8](https://github.com/frappe/erpnext/commit/09453f883ba8ea1a355d044d3f416f8925d663df)) * resolve conflict ([271ddb6](https://github.com/frappe/erpnext/commit/271ddb6add5f062fdc33750c21e124ae275d7bc3)) * restrict already invoiced qty in intercompany purchase invoice ([#55754](https://github.com/frappe/erpnext/issues/55754)) ([a5c23a3](https://github.com/frappe/erpnext/commit/a5c23a3d16dedcb05bf05e924b8630e1dec44e41)) * **selling:** consider delivered qty (backport [#55597](https://github.com/frappe/erpnext/issues/55597)) ([#55606](https://github.com/frappe/erpnext/issues/55606)) ([e8267e3](https://github.com/frappe/erpnext/commit/e8267e32377e264e44e829fdf2e27b610f5fe28b)) * simplify New Zealand sales accounts ([eebb37f](https://github.com/frappe/erpnext/commit/eebb37f9fddf00b559f33de8d285627cd07cc9e3)) * sql injection ([a94e362](https://github.com/frappe/erpnext/commit/a94e362b8ce33144b69afb1fcfc94a261374a551)) * **stock:** add validation for work order seial nos and batch nos ([6d3f9d3](https://github.com/frappe/erpnext/commit/6d3f9d3c6f37930567deb02c20d7649499c18f2e)) * update items respects workflow "Only Allow Edit For" role (backport [#55667](https://github.com/frappe/erpnext/issues/55667)) ([#55705](https://github.com/frappe/erpnext/issues/55705)) ([7852ea6](https://github.com/frappe/erpnext/commit/7852ea65af4ec5208b90d63d60fb7f6be8e9206a)) * use new_doc with field allowlist in CRM integration endpoints ([45b232d](https://github.com/frappe/erpnext/commit/45b232d369eb1624383986d7047d5625c7e4f08f)) * validate fg and materials qty in the disassemble entry ([ba19a24](https://github.com/frappe/erpnext/commit/ba19a245266f280dceb7e9d550d4b7d76e27f26c)) * work order status should be in process if material transfer is skipped (backport [#55641](https://github.com/frappe/erpnext/issues/55641) to version-15-hotfix) ([#55643](https://github.com/frappe/erpnext/issues/55643)) ([55b0715](https://github.com/frappe/erpnext/commit/55b07153104482f1edabe9615821adc3ca95404a)) ### Features * add New Zealand chart of accounts ([f8a123e](https://github.com/frappe/erpnext/commit/f8a123e79dcecdaddb635a55f38af2caf2dfb2bf)) ### Performance Improvements * **transaction:** exit early before backend query (backport [#55556](https://github.com/frappe/erpnext/issues/55556)) ([#55557](https://github.com/frappe/erpnext/issues/55557)) ([ccbca57](https://github.com/frappe/erpnext/commit/ccbca57420b23207c8c0edf7b9e92f0a79f7588c)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4be8b579505..5b488095239 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from frappe.utils.user import is_website_user -__version__ = "15.110.0" +__version__ = "15.111.0" def get_default_company(user=None): From e27016339f3c50eacbdc26842ce0f7d9f72cd8b5 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 16 Jun 2026 21:53:42 +0000 Subject: [PATCH 12/13] chore(release): Bumped to Version 15.112.0 # [15.112.0](https://github.com/frappe/erpnext/compare/v15.111.0...v15.112.0) (2026-06-16) ### Bug Fixes * **accounts:** removed whitelist on `get_balance_on` (backport [#55956](https://github.com/frappe/erpnext/issues/55956)) ([#55964](https://github.com/frappe/erpnext/issues/55964)) ([64bbc01](https://github.com/frappe/erpnext/commit/64bbc019aded28862892a0e94b16301464404971)) * Add authorization checks on internal functions (backport [#55709](https://github.com/frappe/erpnext/issues/55709)) (backport [#55726](https://github.com/frappe/erpnext/issues/55726)) ([#55736](https://github.com/frappe/erpnext/issues/55736)) ([8c7a313](https://github.com/frappe/erpnext/commit/8c7a313a38dfe38c9e35ca41e91389bcfaed2404)) * added doctype filter validation for sales person wise transaction summary report (backport [#55812](https://github.com/frappe/erpnext/issues/55812)) ([#55817](https://github.com/frappe/erpnext/issues/55817)) ([f1fc9e3](https://github.com/frappe/erpnext/commit/f1fc9e3261a154a9f97c4a3354573c78432e8f33)) * apply user permissions to receivable/payable reports ([c03a66a](https://github.com/frappe/erpnext/commit/c03a66a1bf48a53c42d01c9d936d9b22aa013e11)) * BOM Creator by removing redundant permissions ([08d9b82](https://github.com/frappe/erpnext/commit/08d9b8275daa1763dfc0bb05e56d4929a522b43c)) * **bom:** fetch routing operations when Routing is selected ([#55813](https://github.com/frappe/erpnext/issues/55813)) ([02e38e8](https://github.com/frappe/erpnext/commit/02e38e80a7fe964b03f470dc58e44ff202483f66)) * **budget:** add root_type filter on account field ([#55934](https://github.com/frappe/erpnext/issues/55934)) ([e1fbf78](https://github.com/frappe/erpnext/commit/e1fbf78409d788e34c3e2021eebb0bb0e760596a)) * clearance date update with permission checks ([46b3e0c](https://github.com/frappe/erpnext/commit/46b3e0c385658e1292f1c7c01bfd3fd356f923ad)) * **company:** replaced "this company" with company name on delete transactions dialog (backport [#56021](https://github.com/frappe/erpnext/issues/56021)) ([#56022](https://github.com/frappe/erpnext/issues/56022)) ([8a9461f](https://github.com/frappe/erpnext/commit/8a9461ff45f990a8f4740f3defb34a1ac7407a93)) * converted whitelist non class methods to class methods ([7c78aa6](https://github.com/frappe/erpnext/commit/7c78aa6e5d5ddbd4fd98bab4c741fc30ba556638)) * **get_exchange_rate:** using get_single_value to fetch `disabled` value from `currency_exchange_settings` ([9405b49](https://github.com/frappe/erpnext/commit/9405b49e93b9ce1c04bc3cca78b848325fe7a6dc)) * **Lead:** stop storing Gravatar image URLs for Leads (backport [#55880](https://github.com/frappe/erpnext/issues/55880)) ([#55881](https://github.com/frappe/erpnext/issues/55881)) ([8b3a0fe](https://github.com/frappe/erpnext/commit/8b3a0fe04571c9f48f74bf68fb3ff160269b5151)) * **manufacturing:** fetch raw materials from work order ([7e9c1ef](https://github.com/frappe/erpnext/commit/7e9c1efab72057cd1fad10ef2e027df99ef102e7)) * multiple issues related to BOM Creator ([f4e6f14](https://github.com/frappe/erpnext/commit/f4e6f143421ce92639668c70d2ac1d1aaa500d9e)) * opportunity creation from contact us page (backport [#55841](https://github.com/frappe/erpnext/issues/55841)) ([#55866](https://github.com/frappe/erpnext/issues/55866)) ([d460577](https://github.com/frappe/erpnext/commit/d4605771dae7d9171052cecf271bd3c716bfc93b)) * pass source cost center to target cost center ([209977f](https://github.com/frappe/erpnext/commit/209977f6a30551daa1dfaedbc12deae49c9551a3)) * pemission for whitelist functions ([559c95c](https://github.com/frappe/erpnext/commit/559c95c8a8cb2b41b71c1a86372175fb9fd665d9)) * permission in bom compare tool ([ef3046d](https://github.com/frappe/erpnext/commit/ef3046dca2f3571f5c76cf1e8c82bd7515ce5374)) * permissions in workstation file ([808ca06](https://github.com/frappe/erpnext/commit/808ca06801f0b20dffdb0a4ffc26fecadf2b8ff2)) * prefetch batchwise valuations before streaming SLEs in stock ageing ([c617650](https://github.com/frappe/erpnext/commit/c6176500d269cba563b3d00ae5e53ed8f0a5f375)) * prevent exchange rate flow from transaction to payment ([72c7b79](https://github.com/frappe/erpnext/commit/72c7b79933c2569fa11f329c822316345151fbc5)) * provision to recalculate valuation rate during reposting ([df0c8ee](https://github.com/frappe/erpnext/commit/df0c8ee21eccccbbafd724f8fcb48983e95b741c)) * recalculate incoming rate in SLE for purchase documents during repost ([081887b](https://github.com/frappe/erpnext/commit/081887bec5b7daead28377b9b8b0d7f338d6b2ab)) * regression issues related to security fixes (backport [#55902](https://github.com/frappe/erpnext/issues/55902)) ([#55925](https://github.com/frappe/erpnext/issues/55925)) ([1f47b24](https://github.com/frappe/erpnext/commit/1f47b2417bbaffa59b0d2ebcc84352e539cf682d)) * restricting currency_exchange_settings write permission only to system manager ([7b65206](https://github.com/frappe/erpnext/commit/7b6520664c90c510cb94e98af5172c6d19c77a1e)) * show company name in delete transactions confirmation dialog ([3356583](https://github.com/frappe/erpnext/commit/3356583865708b8ec1bbd595c958b9bef6ac6c79)) * show user disable audit log ([559585f](https://github.com/frappe/erpnext/commit/559585fb7b4f8651d0d799c4059e22f88edddeed)) * **stock:** enable quality inspection for all Stock Entry purposes ([1ffef19](https://github.com/frappe/erpnext/commit/1ffef19957b359619f5a29c6b674286b482c964d)), closes [#55830](https://github.com/frappe/erpnext/issues/55830) [#70945](https://github.com/frappe/erpnext/issues/70945) * **stock:** make uom mandatory in item uom table ([c3a3eb3](https://github.com/frappe/erpnext/commit/c3a3eb3df313f07c2df7c72688fb44f9ca6cc829)) * **stock:** show only batched items in batch item selector ([7f30a2c](https://github.com/frappe/erpnext/commit/7f30a2cfb6d85f0a513935d1db75752e04bc1792)) * **stock:** update stock value calculation in stock balance report ([18ad323](https://github.com/frappe/erpnext/commit/18ad3238286ecf540232f46e9c6ab917c6e74820)) * sync employee user status after save ([e40999c](https://github.com/frappe/erpnext/commit/e40999c8792c542951d6292df0a547cbd1555cb7)) * update system manager permissions ([81e05fc](https://github.com/frappe/erpnext/commit/81e05fc1f61ff8ba0e6168830082edb3c725f902)) * updated role based permission for terms and conditions doctype (backport [#55674](https://github.com/frappe/erpnext/issues/55674)) ([#55675](https://github.com/frappe/erpnext/issues/55675)) ([5f5b2a6](https://github.com/frappe/erpnext/commit/5f5b2a6ae2f5047e9dbfb3e316ccdcbfbc68d9dd)) * use frankfurter v2 by default for new install ([d62985d](https://github.com/frappe/erpnext/commit/d62985d9a76ca474b138a71b97cd9a98d342b7e1)) ### Features * **currency exchange settings:** frankfurter v2 support ([70628c0](https://github.com/frappe/erpnext/commit/70628c06c99d279b59acc5619ab0e77d4077349a)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 5b488095239..bfb1c251270 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from frappe.utils.user import is_website_user -__version__ = "15.111.0" +__version__ = "15.112.0" def get_default_company(user=None): From 144483765323f4197085bf183ff973a533d70269 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 18 Jun 2026 11:21:13 +0530 Subject: [PATCH 13/13] refactor: posting datetime for SABB (backport #49710 + #50248) (#56072) * refactor: posting datetime for SABB (backport #49710 + #50248) Backport of #49710 "refactor: posting datetime for SABB" to version-15-hotfix, including the regression fix #50248 "fix: missed refactoring code". Replaces Serial and Batch Bundle's `posting_date`/`posting_time` with a single `posting_datetime` field and updates all readers/writers; adds a patch to populate `posting_datetime` from the linked Stock Ledger Entry and drop the now-unused single-column indexes. Adapted to version-15-hotfix where the surrounding stock code had already moved to `posting_datetime` (SLE) and diverged from develop. Co-Authored-By: Claude Opus 4.8 * chore: fix conflicts * chore: fixed formatting --------- Co-authored-by: Claude Opus 4.8 (cherry picked from commit 98caefea8871053e7e3074faa41ecda5474f0f94) --- .../controllers/sales_and_purchase_return.py | 5 +- erpnext/controllers/selling_controller.py | 5 +- erpnext/controllers/stock_controller.py | 4 +- .../controllers/subcontracting_controller.py | 4 +- erpnext/patches.txt | 1 + ...ting_datetime_for_sabb_and_drop_indexes.py | 31 ++++ erpnext/stock/deprecated_serial_batch.py | 45 +++--- erpnext/stock/doctype/batch/batch.py | 41 +++--- .../serial_and_batch_bundle.json | 34 ++--- .../serial_and_batch_bundle.py | 134 +++++++----------- .../test_serial_and_batch_bundle.py | 8 +- .../stock/doctype/stock_entry/stock_entry.py | 8 +- .../doctype/stock_entry/stock_entry_utils.py | 8 +- .../stock_reconciliation.py | 10 +- .../serial_and_batch_summary.py | 6 +- erpnext/stock/serial_batch_bundle.py | 55 +++---- erpnext/stock/stock_ledger.py | 8 +- erpnext/stock/utils.py | 24 ++-- 18 files changed, 216 insertions(+), 215 deletions(-) create mode 100644 erpnext/patches/v16_0/set_posting_datetime_for_sabb_and_drop_indexes.py diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index ef5feafb45c..8f6edbe75ae 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -11,7 +11,7 @@ from frappe.utils import cint, flt, format_datetime, get_datetime import erpnext from erpnext.stock.serial_batch_bundle import get_batches_from_bundle from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle -from erpnext.stock.utils import get_incoming_rate, get_valuation_method, getdate +from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method, getdate class StockOverReturnError(frappe.ValidationError): @@ -1180,8 +1180,7 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f "batches": data.get("batches"), "serial_nos_valuation": data.get("serial_nos_valuation"), "batches_valuation": data.get("batches_valuation"), - "posting_date": parent_doc.posting_date, - "posting_time": parent_doc.posting_time, + "posting_datetime": get_combine_datetime(parent_doc.posting_date, parent_doc.posting_time), "voucher_type": parent_doc.doctype, "voucher_no": parent_doc.name, "voucher_detail_no": child_doc.name, diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 3383bd51fdd..5655fea9915 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return, i from erpnext.controllers.stock_controller import StockController from erpnext.stock.doctype.item.item import set_item_default from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor -from erpnext.stock.utils import get_incoming_rate, get_valuation_method +from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method class SellingController(StockController): @@ -1084,8 +1084,7 @@ def get_serial_and_batch_bundle(child, parent, delivery_note_child=None): "voucher_type": parent.doctype, "voucher_no": parent.name if parent.docstatus < 2 else None, "voucher_detail_no": delivery_note_child.name if delivery_note_child else child.name, - "posting_date": parent.posting_date, - "posting_time": parent.posting_time, + "posting_datetime": get_combine_datetime(parent.posting_date, parent.posting_time), "qty": child.qty, "type_of_transaction": "Outward" if child.qty > 0 and parent.docstatus < 2 else "Inward", "company": parent.company, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 9e681bce814..32968952fc6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -26,6 +26,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import ( get_evaluated_inventory_dimension, ) from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + combine_datetime, get_type_of_transaction, ) from erpnext.stock.stock_ledger import get_items_to_be_repost @@ -282,8 +283,7 @@ class StockController(AccountsController): ): bundle_details = { "item_code": row.get("rm_item_code") or row.item_code, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": combine_datetime(self.posting_date, self.posting_time), "voucher_type": self.doctype, "voucher_no": self.name, "voucher_detail_no": row.name, diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index c32edfaa7b2..6b08cc15838 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -13,6 +13,7 @@ from frappe.utils import cint, flt, get_link_to_form from erpnext.controllers.stock_controller import StockController from erpnext.stock.doctype.batch.batch import get_batch_qty from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + combine_datetime, get_auto_batch_nos, get_available_serial_nos, get_voucher_wise_serial_batch_from_bundle, @@ -570,8 +571,7 @@ class SubcontractingController(StockController): "qty": qty, "serial_nos": serial_nos, "batches": batches, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": combine_datetime(self.posting_date, self.posting_time), "voucher_type": "Subcontracting Receipt", "do_not_submit": True, "type_of_transaction": "Outward" if qty > 0 else "Inward", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 239066f7c98..3b0ac5cf552 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -436,3 +436,4 @@ erpnext.patches.v16_0.depends_on_inv_dimensions erpnext.patches.v16_0.clear_procedures_from_receivable_report erpnext.patches.v16_0.migrate_address_contact_custom_fields erpnext.patches.v15_0.set_main_item_code_in_material_request_plan_item +erpnext.patches.v16_0.set_posting_datetime_for_sabb_and_drop_indexes diff --git a/erpnext/patches/v16_0/set_posting_datetime_for_sabb_and_drop_indexes.py b/erpnext/patches/v16_0/set_posting_datetime_for_sabb_and_drop_indexes.py new file mode 100644 index 00000000000..c4a71791671 --- /dev/null +++ b/erpnext/patches/v16_0/set_posting_datetime_for_sabb_and_drop_indexes.py @@ -0,0 +1,31 @@ +import click +import frappe + + +def execute(): + frappe.db.sql( + """ + UPDATE `tabSerial and Batch Bundle` + JOIN `tabStock Ledger Entry` + ON `tabSerial and Batch Bundle`.`name` = `tabStock Ledger Entry`.`serial_and_batch_bundle` + SET `tabSerial and Batch Bundle`.`posting_datetime` = `tabStock Ledger Entry`.`posting_datetime` + WHERE `tabStock Ledger Entry`.`is_cancelled` = 0 + """ + ) + + drop_indexes() + + +def drop_indexes(): + table = "tabSerial and Batch Bundle" + index_list = ["voucher_no_index", "item_code_index", "warehouse_index", "company_index"] + + for index in index_list: + if not frappe.db.has_index(table, index): + continue + + try: + frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`") + click.echo(f"✓ dropped {index} index from {table}") + except Exception: + frappe.log_error("Failed to drop index") diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 00a06e98d2e..b57b683c2d8 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -36,6 +36,11 @@ class DeprecatedSerialNoValuation: # get rate from serial nos within same company incoming_values = 0.0 + posting_datetime = self.sle.posting_datetime + + if not posting_datetime and self.sle.posting_date: + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + for serial_no in serial_nos: sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1) if ( @@ -64,10 +69,7 @@ class DeprecatedSerialNoValuation: & (table.serial_and_batch_bundle.isnull()) & (table.actual_qty > 0) & (table.is_cancelled == 0) - & ( - table.posting_datetime - <= get_combine_datetime(self.sle.posting_date, self.sle.posting_time) - ) + & (table.posting_datetime <= posting_datetime) ) .orderby(table.posting_datetime, order=Order.desc) .limit(1) @@ -98,11 +100,8 @@ class DeprecatedBatchNoValuation: sle = frappe.qb.DocType("Stock Ledger Entry") timestamp_condition = None - if self.sle.posting_date: - if self.sle.posting_time is None: - self.sle.posting_time = nowtime() - - posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if self.sle.posting_datetime: + posting_datetime = self.sle.posting_datetime if not self.sle.creation: posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) @@ -202,7 +201,11 @@ class DeprecatedBatchNoValuation: sle = frappe.qb.DocType("Stock Ledger Entry") batch = frappe.qb.DocType("Batch") - posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + posting_datetime = self.sle.posting_datetime + + if not posting_datetime and self.sle.posting_date: + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if not self.sle.creation: posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) @@ -266,7 +269,10 @@ class DeprecatedBatchNoValuation: sle = frappe.qb.DocType("Stock Ledger Entry") - posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + posting_datetime = self.sle.posting_datetime + if not posting_datetime and self.sle.posting_date: + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + if not self.sle.creation: posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1) @@ -308,19 +314,22 @@ class DeprecatedBatchNoValuation: @deprecated def set_balance_value_from_bundle(self) -> None: + from erpnext.stock.utils import get_combine_datetime + bundle = frappe.qb.DocType("Serial and Batch Bundle") bundle_child = frappe.qb.DocType("Serial and Batch Entry") batch = frappe.qb.DocType("Batch") - timestamp_condition = CombineDatetime(bundle.posting_date, bundle.posting_time) < CombineDatetime( - self.sle.posting_date, self.sle.posting_time - ) + posting_datetime = self.sle.posting_datetime + if not posting_datetime and self.sle.posting_date: + posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time) + + timestamp_condition = bundle.posting_datetime < posting_datetime if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(bundle.posting_date, bundle.posting_time) - == CombineDatetime(self.sle.posting_date, self.sle.posting_time) - ) & (bundle.creation < self.sle.creation) + timestamp_condition |= (bundle.posting_datetime == posting_datetime) & ( + bundle.creation < self.sle.creation + ) query = ( frappe.qb.from_(bundle) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 6b2b1360cf9..7f31c65dfcf 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt +import datetime from collections import OrderedDict, defaultdict import frappe @@ -10,7 +11,7 @@ from frappe.model.document import Document from frappe.model.naming import make_autoname, revert_series_if_last from frappe.query_builder.functions import CurDate, Sum from frappe.utils import cint, flt, get_link_to_form -from frappe.utils.data import add_days +from frappe.utils.data import DateTimeLikeObject, add_days class UnableToSelectBatchError(frappe.ValidationError): @@ -232,17 +233,18 @@ class Batch(Document): @frappe.whitelist() def get_batch_qty( - batch_no=None, - warehouse=None, - item_code=None, - creation=None, - posting_date=None, - posting_time=None, - ignore_voucher_nos=None, - for_stock_levels=False, - consider_negative_batches=False, - do_not_check_future_batches=False, - ignore_reserved_stock=False, + batch_no: str | None = None, + warehouse: str | None = None, + item_code: str | None = None, + creation: DateTimeLikeObject | None = None, + posting_datetime: DateTimeLikeObject | None = None, + posting_date: DateTimeLikeObject | None = None, + posting_time: datetime.timedelta | None = None, + ignore_voucher_nos: list | None = None, + for_stock_levels: bool = False, + consider_negative_batches: bool = False, + do_not_check_future_batches: bool = False, + ignore_reserved_stock: bool = False, ): """Returns batch actual qty if warehouse is passed, or returns dict of qty by warehouse if warehouse is None @@ -255,6 +257,7 @@ def get_batch_qty( :param for_stock_levels: True consider expired batches""" from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + combine_datetime, get_auto_batch_nos, ) @@ -264,8 +267,6 @@ def get_batch_qty( "item_code": item_code, "warehouse": warehouse, "creation": creation, - "posting_date": posting_date, - "posting_time": posting_time, "batch_no": batch_no, "based_on": frappe.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"), "ignore_voucher_nos": ignore_voucher_nos, @@ -276,6 +277,10 @@ def get_batch_qty( } ) + kwargs["posting_datetime"] = posting_datetime + if not kwargs.get("posting_datetime") and posting_date: + kwargs["posting_datetime"] = combine_datetime(posting_date, posting_time) + batches = get_auto_batch_nos(kwargs) if not (batch_no and warehouse): @@ -357,6 +362,7 @@ def make_batch_bundle( ): from frappe.utils import nowtime, today + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import combine_datetime from erpnext.stock.serial_batch_bundle import SerialBatchCreation return ( @@ -364,8 +370,7 @@ def make_batch_bundle( { "item_code": item_code, "warehouse": warehouse, - "posting_date": today(), - "posting_time": nowtime(), + "posting_datetime": combine_datetime(today(), nowtime()), "voucher_type": "Stock Entry", "qty": qty, "type_of_transaction": type_of_transaction, @@ -476,9 +481,13 @@ def get_pos_reserved_batch_qty(filters): def get_available_batches(kwargs): from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + combine_datetime, get_auto_batch_nos, ) + if kwargs.get("posting_date"): + kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + batchwise_qty = OrderedDict() batches = get_auto_batch_nos(kwargs) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json index 0a24bfb5645..864a0c51008 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json @@ -29,8 +29,7 @@ "voucher_no", "voucher_detail_no", "column_break_aouy", - "posting_date", - "posting_time", + "posting_datetime", "returned_against", "section_break_wzou", "is_cancelled", @@ -49,8 +48,7 @@ "in_list_view": 1, "label": "Company", "options": "Company", - "reqd": 1, - "search_index": 1 + "reqd": 1 }, { "fetch_from": "item_code.item_group", @@ -79,8 +77,7 @@ "in_standard_filter": 1, "label": "Item Code", "options": "Item", - "reqd": 1, - "search_index": 1 + "reqd": 1 }, { "fetch_from": "item_code.item_name", @@ -117,8 +114,7 @@ "in_standard_filter": 1, "label": "Voucher No", "no_copy": 1, - "options": "voucher_type", - "search_index": 1 + "options": "voucher_type" }, { "default": "0", @@ -188,8 +184,7 @@ "in_standard_filter": 1, "label": "Warehouse", "mandatory_depends_on": "eval:doc.type_of_transaction != \"Maintenance\"", - "options": "Warehouse", - "search_index": 1 + "options": "Warehouse" }, { "fieldname": "type_of_transaction", @@ -211,18 +206,6 @@ "fieldname": "section_break_wzou", "fieldtype": "Section Break" }, - { - "fieldname": "posting_date", - "fieldtype": "Date", - "label": "Posting Date", - "no_copy": 1 - }, - { - "fieldname": "posting_time", - "fieldtype": "Time", - "label": "Posting Time", - "no_copy": 1 - }, { "fieldname": "voucher_detail_no", "fieldtype": "Data", @@ -251,12 +234,17 @@ "label": "Naming Series", "options": "\nSABB-.########", "set_only_once": 1 + }, + { + "fieldname": "posting_datetime", + "fieldtype": "Datetime", + "label": "Posting Datetime" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-09-15 14:37:26.441742", + "modified": "2025-09-24 16:24:48.154853", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Bundle", diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 39e5f1425f0..ce185e40a31 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -74,8 +74,7 @@ class SerialandBatchBundle(Document): item_group: DF.Link | None item_name: DF.Data | None naming_series: DF.Literal["", "SABB-.########"] - posting_date: DF.Date | None - posting_time: DF.Time | None + posting_datetime: DF.Datetime | None returned_against: DF.Data | None total_amount: DF.Float total_qty: DF.Float @@ -280,8 +279,7 @@ class SerialandBatchBundle(Document): kwargs.update( { "voucher_no": self.voucher_no, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": self.posting_datetime, } ) @@ -332,8 +330,7 @@ class SerialandBatchBundle(Document): kwargs = frappe._dict( { "item_code": self.item_code, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": self.posting_datetime, "serial_nos": serial_nos, "check_serial_nos": True, } @@ -669,8 +666,7 @@ class SerialandBatchBundle(Document): def get_sle_for_outward_transaction(self): sle = frappe._dict( { - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": self.posting_datetime, "item_code": self.item_code, "warehouse": self.warehouse, "serial_and_batch_bundle": self.name, @@ -829,11 +825,10 @@ class SerialandBatchBundle(Document): if not self.voucher_detail_no or self.voucher_detail_no != row.name: values_to_set["voucher_detail_no"] = row.name - if parent.get("posting_date") and (not self.posting_date or self.posting_date != parent.posting_date): - values_to_set["posting_date"] = parent.posting_date or today() - - if parent.get("posting_time") and (not self.posting_time or self.posting_time != parent.posting_time): - values_to_set["posting_time"] = parent.posting_time + if parent.get("posting_date") and parent.get("posting_time"): + posting_datetime = combine_datetime(parent.posting_date, parent.posting_time) + if not self.posting_datetime or self.posting_datetime != posting_datetime: + values_to_set["posting_datetime"] = posting_datetime if row.get("doctype") == "Packed Item" and row.get("parent_detail_docname"): values_to_set["voucher_detail_no"] = row.get("parent_detail_docname") @@ -911,9 +906,7 @@ class SerialandBatchBundle(Document): parent = frappe.qb.DocType("Serial and Batch Bundle") child = frappe.qb.DocType("Serial and Batch Entry") - timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) > CombineDatetime( - self.posting_date, self.posting_time - ) + timestamp_condition = parent.posting_datetime > self.posting_datetime future_entries = ( frappe.qb.from_(parent) @@ -2083,6 +2076,8 @@ def create_serial_batch_no_ledgers( if parent_doc.get("doctype") == "Stock Entry": warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse + posting_datetime = combine_datetime(parent_doc.get("posting_date"), parent_doc.get("posting_time")) + doc = frappe.get_doc( { "doctype": "Serial and Batch Bundle", @@ -2091,8 +2086,7 @@ def create_serial_batch_no_ledgers( "warehouse": warehouse, "is_rejected": child_row.is_rejected, "type_of_transaction": type_of_transaction, - "posting_date": parent_doc.get("posting_date"), - "posting_time": parent_doc.get("posting_time"), + "posting_datetime": posting_datetime, "company": parent_doc.get("company"), } ) @@ -2128,6 +2122,12 @@ def create_serial_batch_no_ledgers( return doc +def combine_datetime(date, time=None): + from erpnext.stock.utils import get_combine_datetime + + return get_combine_datetime(date, time) + + def get_batch(item_code): from erpnext.stock.doctype.batch.batch import make_batch @@ -2180,8 +2180,8 @@ def update_serial_batch_no_ledgers(bundle, entries, child_row, parent_doc, wareh ) doc.voucher_detail_no = child_row.name - doc.posting_date = parent_doc.posting_date - doc.posting_time = parent_doc.posting_time + doc.posting_datetime = combine_datetime(parent_doc.get("posting_date"), parent_doc.get("posting_time")) + doc.warehouse = warehouse or doc.warehouse doc.set("entries", []) @@ -2269,6 +2269,9 @@ def get_available_serial_nos(kwargs): elif kwargs.based_on == "Expiry": order_by = "amc_expiry_date" + if not kwargs.get("posting_datetime") and kwargs.get("posting_date"): + kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + filters = {"item_code": kwargs.item_code} # ignore_warehouse is used for backdated stock transactions @@ -2285,10 +2288,8 @@ def get_available_serial_nos(kwargs): if kwargs.get("ignore_serial_nos"): ignore_serial_nos.extend(kwargs.get("ignore_serial_nos")) - if kwargs.get("posting_date"): - if kwargs.get("posting_time") is None: - kwargs.posting_time = nowtime() - + ignore_serial_nos = list(set(ignore_serial_nos)) + if kwargs.get("posting_datetime"): time_based_serial_nos = get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos) if not time_based_serial_nos: @@ -2692,6 +2693,9 @@ def get_reserved_batches_for_sre(kwargs) -> dict: def get_auto_batch_nos(kwargs): + if not kwargs.get("posting_datetime") and kwargs.get("posting_date"): + kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + available_batches = get_available_batches(kwargs) qty = flt(kwargs.qty) @@ -2721,7 +2725,7 @@ def get_auto_batch_nos(kwargs): if kwargs.based_on == "Expiry": available_batches = sorted(available_batches, key=lambda x: x.expiry_date or getdate("9999-12-31")) - if not kwargs.get("do_not_check_future_batches") and available_batches and kwargs.get("posting_date"): + if not kwargs.get("do_not_check_future_batches") and available_batches and kwargs.get("posting_datetime"): filter_zero_near_batches(available_batches, kwargs) if not kwargs.consider_negative_batches: @@ -2737,8 +2741,7 @@ def get_auto_batch_nos(kwargs): def filter_zero_near_batches(available_batches, kwargs): kwargs.batch_no = [d.batch_no for d in available_batches] - del kwargs["posting_date"] - del kwargs["posting_time"] + del kwargs["posting_datetime"] kwargs.do_not_check_future_batches = 1 available_batches_in_future = get_auto_batch_nos(kwargs) @@ -2804,8 +2807,6 @@ def update_available_batches(available_batches, *reserved_batches) -> None: def get_available_batches(kwargs): - from erpnext.stock.utils import get_combine_datetime - stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") batch_ledger = frappe.qb.DocType("Serial and Batch Entry") batch_table = frappe.qb.DocType("Batch") @@ -2833,23 +2834,15 @@ def get_available_batches(kwargs): if not kwargs.get("for_stock_levels"): query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())) - if kwargs.get("posting_date"): - if kwargs.get("posting_time") is None: - kwargs.posting_time = nowtime() - - timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime( - kwargs.posting_date, kwargs.posting_time - ) + if kwargs.get("posting_datetime"): + timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime if kwargs.get("creation"): - timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime( - kwargs.posting_date, kwargs.posting_time - ) + timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime - timestamp_condition |= ( - stock_ledger_entry.posting_datetime - == get_combine_datetime(kwargs.posting_date, kwargs.posting_time) - ) & (stock_ledger_entry.creation < kwargs.creation) + timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & ( + stock_ledger_entry.creation < kwargs.creation + ) query = query.where(timestamp_condition) @@ -3035,15 +3028,14 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: serial_batch_table.incoming_rate, bundle_table.voucher_detail_no, bundle_table.voucher_no, - bundle_table.posting_date, - bundle_table.posting_time, + bundle_table.posting_datetime, ) .where( (bundle_table.docstatus == 1) & (bundle_table.is_cancelled == 0) & (bundle_table.type_of_transaction.isin(["Inward", "Outward"])) ) - .orderby(bundle_table.posting_date, bundle_table.posting_time) + .orderby(bundle_table.posting_datetime) ) for key, val in kwargs.items(): @@ -3061,7 +3053,7 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: query = query.where(bundle_table[key].isin(val)) else: query = query.where(bundle_table[key] == val) - elif key in ["posting_date", "posting_time"]: + elif key in ["posting_datetime"]: query = query.where(bundle_table[key] >= val) else: if isinstance(val, list): @@ -3073,8 +3065,6 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]: def get_stock_ledgers_for_serial_nos(kwargs): - from erpnext.stock.utils import get_combine_datetime - stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") query = ( @@ -3090,23 +3080,15 @@ def get_stock_ledgers_for_serial_nos(kwargs): .orderby(stock_ledger_entry.creation) ) - if kwargs.get("posting_date"): - if kwargs.get("posting_time") is None: - kwargs.posting_time = nowtime() - - timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime( - kwargs.posting_date, kwargs.posting_time - ) + if kwargs.get("posting_datetime"): + timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime if kwargs.get("creation"): - timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime( - kwargs.posting_date, kwargs.posting_time - ) + timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime - timestamp_condition |= ( - stock_ledger_entry.posting_datetime - == get_combine_datetime(kwargs.posting_date, kwargs.posting_time) - ) & (stock_ledger_entry.creation < kwargs.creation) + timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & ( + stock_ledger_entry.creation < kwargs.creation + ) query = query.where(timestamp_condition) @@ -3129,8 +3111,6 @@ def get_stock_ledgers_for_serial_nos(kwargs): def get_stock_ledgers_batches(kwargs): - from erpnext.stock.utils import get_combine_datetime - stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") batch_table = frappe.qb.DocType("Batch") @@ -3164,23 +3144,15 @@ def get_stock_ledgers_batches(kwargs): if not kwargs.get("for_stock_levels"): query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())) - if kwargs.get("posting_date"): - if kwargs.get("posting_time") is None: - kwargs.posting_time = nowtime() - - timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime( - kwargs.posting_date, kwargs.posting_time - ) + if kwargs.get("posting_datetime"): + timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime if kwargs.get("creation"): - timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime( - kwargs.posting_date, kwargs.posting_time - ) + timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime - timestamp_condition |= ( - stock_ledger_entry.posting_datetime - == get_combine_datetime(kwargs.posting_date, kwargs.posting_time) - ) & (stock_ledger_entry.creation < kwargs.creation) + timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & ( + stock_ledger_entry.creation < kwargs.creation + ) query = query.where(timestamp_condition) @@ -3268,3 +3240,7 @@ def get_stock_reco_details(voucher_detail_no): ], as_dict=True, ) + + +def on_doctype_update(): + frappe.db.add_index("Serial and Batch Bundle", ["item_code", "warehouse", "posting_datetime", "creation"]) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index 2c184cbb876..2cee2bb6f0a 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -10,6 +10,7 @@ from frappe.utils import flt, nowtime, today from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( add_serial_batch_ledgers, + combine_datetime, make_batch_nos, make_serial_nos, ) @@ -1271,14 +1272,17 @@ def make_serial_batch_bundle(kwargs): if kwargs.get("type_of_transaction"): type_of_transaction = kwargs.get("type_of_transaction") + posting_datetime = None + if kwargs.get("posting_date"): + posting_datetime = combine_datetime(kwargs.posting_date, kwargs.posting_time or nowtime()) + sb = SerialBatchCreation( { "item_code": kwargs.item_code, "warehouse": kwargs.warehouse, "voucher_type": kwargs.voucher_type, "voucher_no": kwargs.voucher_no, - "posting_date": kwargs.posting_date, - "posting_time": kwargs.posting_time, + "posting_datetime": posting_datetime, "qty": kwargs.qty, "avg_rate": kwargs.rate, "batches": kwargs.batches, diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index aebcc6dffb2..5a32a7bb641 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -52,7 +52,7 @@ from erpnext.stock.serial_batch_bundle import ( get_serial_or_batch_items, ) from erpnext.stock.stock_ledger import NegativeStockError, get_previous_sle, get_valuation_rate -from erpnext.stock.utils import get_bin, get_incoming_rate +from erpnext.stock.utils import get_bin, get_combine_datetime, get_incoming_rate class FinishedGoodError(frappe.ValidationError): @@ -1527,8 +1527,7 @@ class StockEntry(StockController): { "item_code": row.item_code, "warehouse": row.s_warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": get_combine_datetime(self.posting_date, self.posting_time), "voucher_type": self.doctype, "voucher_detail_no": row.name, "qty": row.transfer_qty * -1, @@ -4058,8 +4057,7 @@ def create_serial_and_batch_bundle(parent_doc, row, child, type_of_transaction=N "item_code": child.item_code, "warehouse": child.warehouse, "type_of_transaction": type_of_transaction, - "posting_date": parent_doc.posting_date, - "posting_time": parent_doc.posting_time, + "posting_datetime": get_combine_datetime(parent_doc.posting_date, parent_doc.posting_time), } ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py index e5f4e73e443..1b4c3d7407f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py @@ -8,6 +8,7 @@ import frappe from frappe.utils import cint, flt import erpnext +from erpnext.stock.utils import get_combine_datetime if TYPE_CHECKING: from erpnext.stock.doctype.stock_entry.stock_entry import StockEntry @@ -140,6 +141,10 @@ def make_stock_entry(**args): elif args.batches: batches = args.batches + posting_datetime = None + if args.posting_date and args.posting_time: + posting_datetime = get_combine_datetime(args.posting_date, args.posting_time) + bundle_id = ( SerialBatchCreation( { @@ -151,8 +156,7 @@ def make_stock_entry(**args): "serial_nos": args.serial_no, "type_of_transaction": "Outward" if args.source else "Inward", "company": s.company, - "posting_date": s.posting_date, - "posting_time": s.posting_time, + "posting_datetime": posting_datetime, "rate": args.rate or args.basic_rate, "do_not_submit": True, } diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index b3e330a0a85..67f9b57c172 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -13,6 +13,7 @@ from erpnext.controllers.stock_controller import StockController from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + combine_datetime, get_available_serial_nos, ) from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -136,8 +137,7 @@ class StockReconciliation(StockController): { "item_code": row.item_code, "warehouse": row.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": combine_datetime(self.posting_date, self.posting_time), "voucher_type": self.doctype, "voucher_no": self.name, "voucher_detail_no": row.name, @@ -243,8 +243,7 @@ class StockReconciliation(StockController): "doctype": "Serial and Batch Bundle", "item_code": item.item_code, "warehouse": item.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": combine_datetime(self.posting_date, self.posting_time), "voucher_type": self.doctype, "type_of_transaction": "Outward", } @@ -262,8 +261,7 @@ class StockReconciliation(StockController): { "item_code": item.item_code, "warehouse": item.warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, + "posting_datetime": combine_datetime(self.posting_date, self.posting_time), "ignore_warehouse": 1, } ) diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py index 486828af1cc..f821899e209 100644 --- a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py +++ b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py @@ -19,7 +19,7 @@ def get_data(filters): "Serial and Batch Bundle", fields=[ "`tabSerial and Batch Bundle`.`voucher_type`", - "`tabSerial and Batch Bundle`.`posting_date`", + "`tabSerial and Batch Bundle`.`posting_datetime` as posting_date", "`tabSerial and Batch Bundle`.`name`", "`tabSerial and Batch Bundle`.`company`", "`tabSerial and Batch Bundle`.`voucher_no`", @@ -33,7 +33,7 @@ def get_data(filters): "`tabSerial and Batch Entry`.`qty`", ], filters=filter_conditions, - order_by="posting_date", + order_by="posting_datetime", ) @@ -54,7 +54,7 @@ def get_filter_conditions(filters): filter_conditions.append( [ "Serial and Batch Bundle", - "posting_date", + "posting_datetime", "between", [filters.get("from_date"), filters.get("to_date")], ] diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index b2eba994538..1b030dbe2fa 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -5,7 +5,7 @@ from frappe import _, bold from frappe.model.naming import NamingSeries, make_autoname, parse_naming_series from frappe.query_builder import Case from frappe.query_builder.functions import CombineDatetime, Max, Sum, Timestamp -from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today +from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, now, nowtime, today from pypika import Order from pypika.terms import ExistsCriterion @@ -140,8 +140,7 @@ class SerialBatchBundle: { "item_code": self.item_code, "warehouse": self.warehouse, - "posting_date": self.sle.posting_date, - "posting_time": self.sle.posting_time, + "posting_datetime": self.sle.posting_datetime, "voucher_type": self.sle.voucher_type, "voucher_no": self.sle.voucher_no, "voucher_detail_no": self.sle.voucher_detail_no, @@ -484,7 +483,7 @@ class SerialBatchBundle: if status == "Delivered": warranty_period = frappe.get_cached_value("Item", sle.item_code, "warranty_period") if warranty_period: - warranty_expiry_date = add_days(sle.posting_date, cint(warranty_period)) + warranty_expiry_date = add_days(getdate(sle.posting_datetime), cint(warranty_period)) query = query.set(sn_table.warranty_expiry_date, warranty_expiry_date) query = query.set(sn_table.warranty_period, warranty_period) else: @@ -509,7 +508,7 @@ class SerialBatchBundle: sle_doctype.voucher_no, sle_doctype.is_cancelled, sle_doctype.item_code, - sle_doctype.posting_date, + sle_doctype.posting_datetime, sle_doctype.company, ) .where( @@ -663,7 +662,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation): .on(bundle.name == bundle_child.parent) .select( bundle_child.serial_no, - Max(CombineDatetime(bundle.posting_date, bundle.posting_time)).as_("max_posting_dt"), + Max(bundle.posting_datetime).as_("max_posting_dt"), ) .where( (bundle.is_cancelled == 0) @@ -681,13 +680,8 @@ class SerialNoValuation(DeprecatedSerialNoValuation): if self.sle.voucher_no: latest_posting = latest_posting.where(bundle.voucher_no != self.sle.voucher_no) - if self.sle.posting_date: - if self.sle.posting_time is None: - self.sle.posting_time = nowtime() - - timestamp_condition = CombineDatetime( - bundle.posting_date, bundle.posting_time - ) <= CombineDatetime(self.sle.posting_date, self.sle.posting_time) + if self.sle.posting_datetime: + timestamp_condition = bundle.posting_datetime <= self.sle.posting_datetime latest_posting = latest_posting.where(timestamp_condition) @@ -704,10 +698,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation): .join(latest_posting) .on( (latest_posting.serial_no == bundle_child.serial_no) - & ( - latest_posting.max_posting_dt - == CombineDatetime(bundle.posting_date, bundle.posting_time) - ) + & (latest_posting.max_posting_dt == bundle.posting_datetime) ) .select( bundle_child.serial_no, @@ -839,19 +830,13 @@ class BatchNoValuation(DeprecatedBatchNoValuation): child = frappe.qb.DocType("Serial and Batch Entry") timestamp_condition = "" - if self.sle.posting_date: - if self.sle.posting_time is None: - self.sle.posting_time = nowtime() - - timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) < CombineDatetime( - self.sle.posting_date, self.sle.posting_time - ) + if self.sle.posting_datetime: + timestamp_condition = parent.posting_datetime < self.sle.posting_datetime if self.sle.creation: - timestamp_condition |= ( - CombineDatetime(parent.posting_date, parent.posting_time) - == CombineDatetime(self.sle.posting_date, self.sle.posting_time) - ) & (parent.creation < self.sle.creation) + timestamp_condition |= (parent.posting_datetime == self.sle.posting_datetime) & ( + parent.creation < self.sle.creation + ) query = ( frappe.qb.from_(parent) @@ -1074,9 +1059,9 @@ class SerialBatchCreation: self.__dict__.update(item_details) def set_other_details(self): - if not self.get("posting_date"): - self.posting_date = today() - self.__dict__["posting_date"] = self.posting_date + if not self.get("posting_datetime"): + self.posting_datetime = now() + self.__dict__["posting_datetime"] = self.posting_datetime if not self.get("actual_qty"): qty = self.get("qty") or self.get("total_qty") @@ -1101,8 +1086,7 @@ class SerialBatchCreation: new_package.docstatus = 0 new_package.warehouse = self.warehouse new_package.voucher_no = "" - new_package.posting_date = self.posting_date if hasattr(self, "posting_date") else today() - new_package.posting_time = self.posting_time if hasattr(self, "posting_time") else nowtime() + new_package.posting_datetime = self.posting_datetime if hasattr(self, "posting_datetime") else now() new_package.type_of_transaction = self.type_of_transaction new_package.returned_against = self.get("returned_against") @@ -1242,9 +1226,8 @@ class SerialBatchCreation: elif self.has_serial_no and not self.get("serial_nos"): self.serial_nos = get_serial_nos_for_outward(kwargs) elif not self.has_serial_no and self.has_batch_no and not self.get("batches"): - if self.get("posting_date"): - kwargs["posting_date"] = self.get("posting_date") - kwargs["posting_time"] = self.get("posting_time") + if self.get("posting_datetime"): + kwargs["posting_datetime"] = self.get("posting_datetime") self.batches = get_available_batches(kwargs) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 7ae0a2fda49..bcc5b3e8a26 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1975,9 +1975,6 @@ def get_valuation_rate( # Get moving average rate of a specific batch number if warehouse and serial_and_batch_bundle: - sabb = frappe.db.get_value( - "Serial and Batch Bundle", serial_and_batch_bundle, ["posting_date", "posting_time"], as_dict=True - ) batch_obj = BatchNoValuation( sle=frappe._dict( { @@ -1985,8 +1982,9 @@ def get_valuation_rate( "warehouse": warehouse, "actual_qty": -1, "serial_and_batch_bundle": serial_and_batch_bundle, - "posting_date": sabb.posting_date, - "posting_time": sabb.posting_time, + "posting_datetime": frappe.get_value( + "Serial and Batch Bundle", serial_and_batch_bundle, "posting_datetime" + ), } ) ) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 8dfcf5a833e..92a42ec714c 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -2,12 +2,14 @@ # License: GNU General Public License v3. See license.txt +import datetime import json import frappe from frappe import _ from frappe.query_builder.functions import CombineDatetime, IfNull, Sum from frappe.utils import cstr, flt, get_link_to_form, get_time, getdate, nowdate, nowtime +from frappe.utils.data import DateTimeLikeObject import erpnext from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions @@ -94,13 +96,13 @@ def get_stock_value_on( @frappe.whitelist() def get_stock_balance( - item_code, - warehouse, - posting_date=None, - posting_time=None, - with_valuation_rate=False, - with_serial_no=False, - inventory_dimensions_dict=None, + item_code: str, + warehouse: str | None, + posting_date: DateTimeLikeObject | None = None, + posting_time: DateTimeLikeObject | datetime.timedelta | None = None, + with_valuation_rate: bool = False, + with_serial_no: bool = False, + inventory_dimensions_dict: dict | None = None, ): """Returns stock balance quantity at given warehouse on given posting date or current date. @@ -146,8 +148,7 @@ def get_stock_balance( { "item_code": item_code, "warehouse": warehouse, - "posting_date": posting_date, - "posting_time": posting_time, + "posting_datetime": get_combine_datetime(posting_date, posting_time), "ignore_warehouse": 1, } ) @@ -247,13 +248,16 @@ def _create_bin(item_code, warehouse): @frappe.whitelist() -def get_incoming_rate(args, raise_error_if_no_rate=True, fallbacks: bool = True): +def get_incoming_rate(args: dict | str, raise_error_if_no_rate: bool = True, fallbacks: bool = True): """Get Incoming Rate based on valuation method""" from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate if isinstance(args, str): args = json.loads(args) + if not args.get("posting_datetime") and args.get("posting_date"): + args["posting_datetime"] = get_combine_datetime(args.get("posting_date"), args.get("posting_time")) + in_rate = None item_details = frappe.get_cached_value(