From f2ad27eb06c092a02041859775d096b0d3819a1f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 9 Nov 2025 23:30:14 +0530 Subject: [PATCH] perf: DN submission with SABB --- erpnext/controllers/stock_controller.py | 5 ++ .../serial_and_batch_bundle.py | 18 +++-- .../serial_and_batch_entry.json | 50 +++++++++++++- .../serial_and_batch_entry.py | 11 +++- erpnext/stock/serial_batch_bundle.py | 65 ++++++++++--------- 5 files changed, 110 insertions(+), 39 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 8d933dd8ed5..ad2581935ba 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -2074,6 +2074,11 @@ def make_bundle_for_material_transfer(**kwargs): row.is_outward = 1 row.warehouse = kwargs.warehouse + row.posting_datetime = bundle_doc.posting_datetime + row.voucher_type = bundle_doc.voucher_type + row.voucher_no = bundle_doc.voucher_no + row.voucher_detail_no = bundle_doc.voucher_detail_no + row.type_of_transaction = bundle_doc.type_of_transaction bundle_doc.set_incoming_rate() bundle_doc.calculate_qty_and_amount() 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 9cf0ef8e622..b91e8f5d619 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 @@ -1300,11 +1300,25 @@ class SerialandBatchBundle(Document): def before_submit(self): self.validate_serial_and_batch_data() self.validate_serial_and_batch_no_for_returned() + self.set_child_details() self.set_source_document_no() def on_submit(self): self.validate_serial_nos_inventory() + def set_child_details(self): + for row in self.entries: + for field in [ + "warehouse", + "posting_datetime", + "voucher_type", + "voucher_no", + "voucher_detail_no", + "type_of_transaction", + ]: + if not row.get(field) or row.get(field) != self.get(field): + row.set(field, self.get(field)) + def set_source_document_no(self): if self.flags.ignore_validate_serial_batch: return @@ -3033,7 +3047,3 @@ 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_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json index 68a7045584d..69aaf261945 100644 --- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json +++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json @@ -19,7 +19,13 @@ "is_outward", "stock_queue", "section_break_gmim", - "reference_for_reservation" + "reference_for_reservation", + "voucher_type", + "voucher_no", + "column_break_eykr", + "posting_datetime", + "type_of_transaction", + "voucher_detail_no" ], "fields": [ { @@ -73,6 +79,7 @@ "fieldtype": "Float", "label": "Valuation Rate", "no_copy": 1, + "non_negative": 1, "read_only": 1, "read_only_depends_on": "eval:parent.type_of_transaction == \"Outward\"" }, @@ -134,18 +141,55 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "posting_datetime", + "fieldtype": "Datetime", + "label": "Posting Datetime", + "read_only": 1 + }, + { + "fieldname": "voucher_type", + "fieldtype": "Data", + "label": "Voucher Type", + "read_only": 1 + }, + { + "fieldname": "voucher_no", + "fieldtype": "Data", + "label": "Voucher No", + "read_only": 1 + }, + { + "fieldname": "voucher_detail_no", + "fieldtype": "Data", + "label": "Voucher Detail No", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "type_of_transaction", + "fieldtype": "Data", + "label": "Type of Transaction", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_eykr", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-01-02 21:51:52.528916", + "modified": "2025-11-09 23:28:35.191959", "modified_by": "Administrator", "module": "Stock", "name": "Serial and Batch Entry", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.py b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.py index 4b8dc8812c9..1f084e60c9c 100644 --- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.py +++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.py @@ -1,7 +1,7 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document @@ -22,12 +22,21 @@ class SerialandBatchEntry(Document): parent: DF.Data parentfield: DF.Data parenttype: DF.Data + posting_datetime: DF.Datetime | None qty: DF.Float reference_for_reservation: DF.Data | None serial_no: DF.Link | None stock_queue: DF.SmallText | None stock_value_difference: DF.Float + type_of_transaction: DF.Data | None + voucher_detail_no: DF.Data | None + voucher_no: DF.Data | None + voucher_type: DF.Data | None warehouse: DF.Link | None # end: auto-generated types pass + + +def on_doctype_update(): + frappe.db.add_index("Serial and Batch Entry", ["warehouse", "batch_no", "posting_datetime"]) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 732faefe38f..a1dd0a5af07 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -742,7 +742,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation): "Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "total_amount" ) else: - entries = self.get_batch_no_ledgers() + entries = self.get_batch_stock_before_date() self.stock_value_change = 0.0 self.batch_avg_rate = defaultdict(float) self.available_qty = defaultdict(float) @@ -751,8 +751,9 @@ class BatchNoValuation(DeprecatedBatchNoValuation): for ledger in entries: self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate) self.available_qty[ledger.batch_no] += flt(ledger.qty) + self.total_qty[ledger.batch_no] += flt(ledger.qty) - entries = self.get_batch_wise_total_available_qty() + entries = self.get_batch_stock_after_date() for row in entries: self.total_qty[row.batch_no] += flt(row.total_qty) @@ -760,29 +761,33 @@ class BatchNoValuation(DeprecatedBatchNoValuation): self.calculate_avg_rate_for_non_batchwise_valuation() self.set_stock_value_difference() - def get_batch_wise_total_available_qty(self) -> list[dict]: + def get_batch_stock_after_date(self) -> list[dict]: # Get total qty of each batch no from Serial and Batch Bundle without checking time condition if not self.batchwise_valuation_batches: return [] - parent = frappe.qb.DocType("Serial and Batch Bundle") child = frappe.qb.DocType("Serial and Batch Entry") + timestamp_condition = "" + if self.sle.posting_datetime: + timestamp_condition = child.posting_datetime > self.sle.posting_datetime + + if self.sle.creation: + timestamp_condition |= (child.posting_datetime == self.sle.posting_datetime) & ( + child.creation > self.sle.creation + ) + query = ( - frappe.qb.from_(parent) - .inner_join(child) - .on(parent.name == child.parent) + frappe.qb.from_(child) .select( child.batch_no, Sum(child.qty).as_("total_qty"), ) .where( - (parent.warehouse == self.sle.warehouse) - & (parent.item_code == self.sle.item_code) + (child.warehouse == self.sle.warehouse) & (child.batch_no.isin(self.batchwise_valuation_batches)) - & (parent.docstatus == 1) - & (parent.is_cancelled == 0) - & (parent.type_of_transaction.isin(["Inward", "Outward"])) + & (child.docstatus == 1) + & (child.type_of_transaction.isin(["Inward", "Outward"])) ) .for_update() .groupby(child.batch_no) @@ -790,47 +795,45 @@ class BatchNoValuation(DeprecatedBatchNoValuation): # Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference if self.sle.voucher_detail_no: - query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no) + query = query.where(child.voucher_detail_no != self.sle.voucher_detail_no) elif self.sle.voucher_no: - query = query.where(parent.voucher_no != self.sle.voucher_no) + query = query.where(child.voucher_no != self.sle.voucher_no) - query = query.where(parent.voucher_type != "Pick List") + query = query.where(child.voucher_type != "Pick List") + + if timestamp_condition: + query = query.where(timestamp_condition) return query.run(as_dict=True) - def get_batch_no_ledgers(self) -> list[dict]: + def get_batch_stock_before_date(self) -> list[dict]: # Get batch wise stock value difference from Serial and Batch Bundle considering time condition if not self.batchwise_valuation_batches: return [] - parent = frappe.qb.DocType("Serial and Batch Bundle") child = frappe.qb.DocType("Serial and Batch Entry") timestamp_condition = "" if self.sle.posting_datetime: - timestamp_condition = parent.posting_datetime < self.sle.posting_datetime + timestamp_condition = child.posting_datetime < self.sle.posting_datetime if self.sle.creation: - timestamp_condition |= (parent.posting_datetime == self.sle.posting_datetime) & ( - parent.creation < self.sle.creation + timestamp_condition |= (child.posting_datetime == self.sle.posting_datetime) & ( + child.creation < self.sle.creation ) query = ( - frappe.qb.from_(parent) - .inner_join(child) - .on(parent.name == child.parent) + frappe.qb.from_(child) .select( child.batch_no, Sum(child.stock_value_difference).as_("incoming_rate"), Sum(child.qty).as_("qty"), ) .where( - (parent.warehouse == self.sle.warehouse) - & (parent.item_code == self.sle.item_code) + (child.warehouse == self.sle.warehouse) & (child.batch_no.isin(self.batchwise_valuation_batches)) - & (parent.docstatus == 1) - & (parent.is_cancelled == 0) - & (parent.type_of_transaction.isin(["Inward", "Outward"])) + & (child.docstatus == 1) + & (child.type_of_transaction.isin(["Inward", "Outward"])) ) .for_update() .groupby(child.batch_no) @@ -838,11 +841,11 @@ class BatchNoValuation(DeprecatedBatchNoValuation): # Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference if self.sle.voucher_detail_no: - query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no) + query = query.where(child.voucher_detail_no != self.sle.voucher_detail_no) elif self.sle.voucher_no: - query = query.where(parent.voucher_no != self.sle.voucher_no) + query = query.where(child.voucher_no != self.sle.voucher_no) - query = query.where(parent.voucher_type != "Pick List") + query = query.where(child.voucher_type != "Pick List") if timestamp_condition: query = query.where(timestamp_condition)