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 53d93704e7e..0a7df3b537c 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 @@ -1905,16 +1905,42 @@ def get_available_serial_nos(kwargs): if kwargs.warehouse: filters["warehouse"] = kwargs.warehouse - # Since SLEs are not present against Reserved Stock [POS invoices, SRE], need to ignore reserved serial nos. - ignore_serial_nos, consider_serial_nos = get_reserved_serial_nos(kwargs) + reserved_entries = get_reserved_serial_nos_for_sre(kwargs) - if consider_serial_nos: - filters["name"] = ("in", consider_serial_nos) + ignore_serial_nos = [] + if reserved_entries: + if kwargs.get("sabb_voucher_type") == "Delivery Note" and kwargs.get("against_sales_order"): + reserved_voucher_details = [kwargs.get("against_sales_order")] + else: + reserved_voucher_details = get_reserved_voucher_details(kwargs) + + # Check if serial nos are reserved for the current voucher then fetch only those serial nos + if reserved_serial_nos := get_reserved_serial_nos_for_voucher( + kwargs, reserved_entries, reserved_voucher_details + ): + filters["name"] = ("in", reserved_serial_nos) + return frappe.get_all( + "Serial No", + fields=fields, + filters=filters, + limit=cint(kwargs.qty) or 10000000, + order_by=order_by, + ) + + # Check if serial nos are reserved for other vouchers then ignore those serial nos + elif ignore_reserved_serial_nos := get_other_doc_reserved_serials( + kwargs, reserved_entries, reserved_voucher_details + ): + ignore_serial_nos.extend(ignore_reserved_serial_nos) + + if reserved_for_pos := get_reserved_serial_nos_for_pos(kwargs): + ignore_serial_nos.extend(reserved_for_pos) # To ignore serial nos in the same record for the draft state if kwargs.get("ignore_serial_nos"): ignore_serial_nos.extend(kwargs.get("ignore_serial_nos")) + ignore_serial_nos = list(set(ignore_serial_nos)) if kwargs.get("posting_date"): if kwargs.get("posting_time") is None: kwargs.posting_time = nowtime() @@ -1924,9 +1950,15 @@ def get_available_serial_nos(kwargs): if not time_based_serial_nos: return [] + for sn in ignore_serial_nos: + if sn in time_based_serial_nos: + time_based_serial_nos.remove(sn) + filters["name"] = ("in", time_based_serial_nos) elif ignore_serial_nos: filters["name"] = ("not in", ignore_serial_nos) + elif kwargs.get("serial_nos"): + filters["name"] = ("in", kwargs.get("serial_nos")) if kwargs.get("batches"): batches = get_non_expired_batches(kwargs.get("batches")) @@ -2014,46 +2046,6 @@ def get_bundle_wise_serial_nos(data, kwargs): return bundle_wise_serial_nos -def get_reserved_serial_nos(kwargs) -> list: - """Returns a list of `Serial No` reserved in POS Invoice and Stock Reservation Entry.""" - - ignore_serial_nos = [] - consider_serial_nos = [] - - # Extend the list by serial nos reserved in POS Invoice - ignore_serial_nos.extend(get_reserved_serial_nos_for_pos(kwargs)) - - reserved_entries = get_reserved_serial_nos_for_sre(kwargs) - if not reserved_entries: - return ignore_serial_nos, consider_serial_nos - - if kwargs.get("sabb_voucher_type") == "Delivery Note" and kwargs.get("against_sales_order"): - reserved_voucher_details = [kwargs.get("against_sales_order")] - else: - reserved_voucher_details = get_reserved_voucher_details(kwargs) - - serial_nos = [] - for entry in reserved_entries: - if entry.voucher_no in reserved_voucher_details: - consider_serial_nos.append(entry.serial_no) - continue - - if kwargs.get("serial_nos") and entry.serial_no in kwargs.get("serial_nos"): - frappe.throw( - _( - "The Serial No {0} is reserved against the {1} {2} and cannot be used for any other transaction." - ).format(bold(entry.serial_no), entry.voucher_type, bold(entry.voucher_no)), - title=_("Serial No Reserved"), - ) - - serial_nos.append(entry.serial_no) - - # Extend the list by serial nos reserved via SRE - ignore_serial_nos.extend(serial_nos) - - return ignore_serial_nos, consider_serial_nos - - def get_reserved_voucher_details(kwargs): reserved_voucher_details = [] @@ -2161,6 +2153,38 @@ def get_reserved_serial_nos_for_pos(kwargs): return list(ignore_serial_nos_counter - returned_serial_nos_counter) +def get_reserved_serial_nos_for_voucher(kwargs, reserved_entries, reserved_voucher_details): + serial_nos = [] + if not kwargs.get("pick_reserved_items"): + return serial_nos + + for entry in reserved_entries: + if entry.voucher_no in reserved_voucher_details: + serial_nos.append(entry.serial_no) + continue + + if kwargs.get("serial_nos") and entry.serial_no in kwargs.get("serial_nos"): + frappe.throw( + _( + "The Serial No {0} is reserved against the {1} {2} and cannot be used for any other transaction." + ).format(bold(entry.serial_no), entry.voucher_type, bold(entry.voucher_no)), + title=_("Serial No Reserved"), + ) + + return serial_nos + + +def get_other_doc_reserved_serials(kwargs, reserved_entries, reserved_voucher_details): + serial_nos = [] + for entry in reserved_entries: + if entry.voucher_no in reserved_voucher_details: + continue + + serial_nos.append(entry.serial_no) + + return serial_nos + + def get_reserved_serial_nos_for_sre(kwargs) -> list: """Returns a list of `Serial No` reserved in Stock Reservation Entry.""" @@ -2182,6 +2206,7 @@ def get_reserved_serial_nos_for_sre(kwargs) -> list: & (sb_entry.delivered_qty < sb_entry.qty) & (sre.reservation_based_on == "Serial and Batch") ) + .orderby(sb_entry.idx) ) if kwargs.warehouse: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index cd03b30cc10..c5b0bb5735d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2120,7 +2120,7 @@ class StockEntry(StockController): key = (d.item_code, d.s_warehouse) if details := reservation_entries.get(key): if details.get("serial_no"): - d.serial_no = "\n".join(details.get("serial_no")) + d.serial_no = "\n".join(details.get("serial_no")[: cint(d.qty)]) if batches := details.get("batch_no"): for batch_no, qty in batches.items(): @@ -2180,7 +2180,12 @@ class StockEntry(StockController): doctype.transferred_qty, doctype.consumed_qty, ) - .where((doctype.docstatus == 1) & (doctype.voucher_no == self.work_order)) + .where( + (doctype.docstatus == 1) + & (doctype.voucher_no == self.work_order) + & (serial_batch_doc.delivered_qty < serial_batch_doc.qty) + ) + .orderby(serial_batch_doc.idx) ) return query.run(as_dict=True) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f755e40bc31..b84a655eb3d 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -205,6 +205,7 @@ def update_stock(ctx, out, doc=None): "sabb_voucher_no": doc.get("name") if doc else None, "sabb_voucher_detail_no": ctx.child_docname, "sabb_voucher_type": ctx.doctype, + "pick_reserved_items": True, } )