diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 919bbb477f4..463cb859970 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -415,7 +415,6 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, p stock_ledger_entry.batch_no, Sum(stock_ledger_entry.actual_qty).as_("qty"), ) - .where((batch_table.expiry_date >= expiry_date) | (batch_table.expiry_date.isnull())) .where(stock_ledger_entry.is_cancelled == 0) .where( (stock_ledger_entry.item_code == filters.get("item_code")) @@ -428,6 +427,9 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, p .limit(page_len) ) + if not filters.get("include_expired_batches"): + query = query.where((batch_table.expiry_date >= expiry_date) | (batch_table.expiry_date.isnull())) + query = query.select( Concat("MFG-", batch_table.manufacturing_date).as_("manufacturing_date"), Concat("EXP-", batch_table.expiry_date).as_("expiry_date"), @@ -466,7 +468,6 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0 bundle.batch_no, Sum(bundle.qty).as_("qty"), ) - .where((batch_table.expiry_date >= expiry_date) | (batch_table.expiry_date.isnull())) .where(stock_ledger_entry.is_cancelled == 0) .where( (stock_ledger_entry.item_code == filters.get("item_code")) @@ -479,6 +480,11 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0 .limit(page_len) ) + if not filters.get("include_expired_batches"): + bundle_query = bundle_query.where( + (batch_table.expiry_date >= expiry_date) | (batch_table.expiry_date.isnull()) + ) + bundle_query = bundle_query.select( Concat("MFG-", batch_table.manufacturing_date), Concat("EXP-", batch_table.expiry_date), diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 61b40d6fef0..de1faf36ef5 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -462,6 +462,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { is_inward = true; } + let include_expired_batches = me.include_expired_batches(); + return { query: "erpnext.controllers.queries.get_batch_no", filters: { @@ -469,6 +471,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { warehouse: this.item.s_warehouse || this.item.t_warehouse || this.item.warehouse, is_inward: is_inward, + include_expired_batches: include_expired_batches, }, }; }, @@ -497,6 +500,14 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { return fields; } + include_expired_batches() { + return ( + this.frm.doc.doctype === "Stock Reconciliation" || + (this.frm.doc.doctype === "Stock Entry" && + ["Material Receipt", "Material Transfer", "Material Issue"].includes(this.frm.doc.purpose)) + ); + } + get_auto_data() { let { qty, based_on } = this.dialog.get_values(); 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 ed6d5e155d7..08aa978aa99 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 @@ -689,6 +689,9 @@ class SerialandBatchBundle(Document): serial_batches = {} for row in self.entries: if not row.qty and row.batch_no and not row.serial_no: + if self.voucher_type == "Stock Reconciliation" and self.type_of_transaction == "Inward": + continue + frappe.throw( _("At row {0}: Qty is mandatory for the batch {1}").format( bold(row.idx), bold(row.batch_no) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ea47ffd79ea..0e39c2a9756 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -117,6 +117,10 @@ frappe.ui.form.on("Stock Entry", { filters["is_inward"] = 1; } + if (["Material Receipt", "Material Transfer", "Material Issue"].includes(doc.purpose)) { + filters["include_expired_batches"] = 1; + } + return { query: "erpnext.controllers.queries.get_batch_no", filters: filters, diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 8ee3d9b3901..f671c11712a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -324,6 +324,7 @@ class StockReconciliation(StockController): row.item_code, posting_date=self.posting_date, posting_time=self.posting_time, + for_stock_levels=True, ) total_current_qty += current_qty @@ -1322,7 +1323,16 @@ def get_stock_balance_for( qty, rate = data if item_dict.get("has_batch_no"): - qty = get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0 + qty = ( + get_batch_qty( + batch_no, + warehouse, + posting_date=posting_date, + posting_time=posting_time, + for_stock_levels=True, + ) + or 0 + ) return { "qty": qty, diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index c1002095b62..cb156aea119 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -124,7 +124,7 @@ class SerialBatchBundle: "Outward": self.sle.actual_qty < 0, }.get(sn_doc.type_of_transaction) - if not condition: + if not condition and self.sle.actual_qty: correct_type = "Inward" if sn_doc.type_of_transaction == "Inward": correct_type = "Outward" @@ -133,7 +133,7 @@ class SerialBatchBundle: frappe.throw(_(msg), title=_("Incorrect Type of Transaction")) precision = sn_doc.precision("total_qty") - if flt(sn_doc.total_qty, precision) != flt(self.sle.actual_qty, precision): + if self.sle.actual_qty and flt(sn_doc.total_qty, precision) != flt(self.sle.actual_qty, precision): msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {link} is not equal to Actual Qty {flt(self.sle.actual_qty, precision)} in the {self.sle.voucher_type} {self.sle.voucher_no}" frappe.throw(_(msg)) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ccf7c7643c8..26e7af150b9 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1183,6 +1183,7 @@ class update_entries_after: stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: + # Update only the row that matches the voucher_detail_no or the row containing the FG/Scrap Item. if d.name == voucher_detail_no or (not d.s_warehouse and d.t_warehouse): d.db_update()