diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 55ac9b1048a..662bf884afb 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -884,6 +884,9 @@ class ProductionPlan(Document): wo = frappe.new_doc("Work Order") wo.update(item) + if not wo.source_warehouse: + wo.source_warehouse = item.get("fg_warehouse") + wo.reserve_stock = self.reserve_stock wo.planned_start_date = item.get("planned_start_date") or item.get("schedule_date") @@ -891,7 +894,7 @@ class ProductionPlan(Document): wo.fg_warehouse = item.get("warehouse") wo.set_work_order_operations() - wo.set_required_items() + wo.set_required_items(reset_source_warehouse=True) try: wo.flags.ignore_mandatory = True diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 3dd1d695ab0..840fbaf7a69 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1213,7 +1213,7 @@ class WorkOrder(Document): if self.wip_warehouse: d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse) - def set_required_items(self, reset_only_qty=False): + def set_required_items(self, reset_only_qty=False, reset_source_warehouse=False): """set required_items for production to keep track of reserved qty""" if not reset_only_qty: self.required_items = [] @@ -1247,7 +1247,9 @@ class WorkOrder(Document): "description": item.description, "allow_alternative_item": item.allow_alternative_item, "required_qty": item.qty, - "source_warehouse": item.source_warehouse or item.default_warehouse, + "source_warehouse": (item.source_warehouse or item.default_warehouse) + if not reset_source_warehouse + else self.source_warehouse, "include_item_in_manufacturing": item.include_item_in_manufacturing, "operation_row_id": item.operation_row_id, }, @@ -1295,22 +1297,37 @@ class WorkOrder(Document): self.update_qty_in_stock_reservation(row, transferred_qty, row_wise_serial_batch) def update_qty_in_stock_reservation(self, row, transferred_qty, row_wise_serial_batch): - if name := frappe.db.get_value( + if names := frappe.get_all( "Stock Reservation Entry", - { + filters={ "voucher_no": self.name, "item_code": row.item_code, "voucher_detail_no": row.name, "warehouse": row.source_warehouse, }, - "name", + pluck="name", ): - doc = frappe.get_doc("Stock Reservation Entry", name) - doc.db_set("transferred_qty", flt(transferred_qty), update_modified=False) - if (doc.has_batch_no or doc.has_serial_no) and doc.reservation_based_on == "Serial and Batch": - doc.consume_serial_batch_for_material_transfer(row_wise_serial_batch) - doc.update_status() - doc.update_reserved_stock_in_bin() + for name in names: + doc = frappe.get_doc("Stock Reservation Entry", name) + qty_to_update = 0.0 + if transferred_qty <= 0: + continue + + if transferred_qty > flt(doc.reserved_qty - doc.consumed_qty): + qty_to_update = doc.reserved_qty - doc.transferred_qty + transferred_qty -= qty_to_update + else: + qty_to_update = transferred_qty + transferred_qty = 0.0 + + if qty_to_update <= 0: + continue + + doc.db_set("transferred_qty", flt(qty_to_update), update_modified=False) + if (doc.has_batch_no or doc.has_serial_no) and doc.reservation_based_on == "Serial and Batch": + doc.consume_serial_batch_for_material_transfer(row_wise_serial_batch) + doc.update_status() + doc.update_reserved_stock_in_bin() def update_returned_qty(self): ste = frappe.qb.DocType("Stock Entry") @@ -2167,8 +2184,8 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create "hour_rate": row.get("hour_rate"), "serial_no": row.get("serial_no"), "time_required": row.get("time_in_mins"), - "source_warehouse": row.get("source_warehouse"), - "target_warehouse": row.get("fg_warehouse"), + "source_warehouse": row.get("source_warehouse") or work_order.get("source_warehouse"), + "target_warehouse": row.get("fg_warehouse") or work_order.get("fg_warehouse"), "wip_warehouse": work_order.wip_warehouse or row.get("wip_warehouse") if not work_order.skip_transfer or work_order.from_wip_warehouse else work_order.source_warehouse or row.get("source_warehouse"), diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 4be44d2ee55..d88af98bca8 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -953,7 +953,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-05-31 18:51:32.651562", + "modified": "2025-05-31 19:51:32.651562", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", 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 ffdf3d72c0a..c9dc82fc612 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 @@ -1923,9 +1923,10 @@ def get_reserved_serial_nos(kwargs) -> list: if not reserved_entries: return ignore_serial_nos, consider_serial_nos - reserved_voucher_details = get_reserved_voucher_details(kwargs) - if not reserved_voucher_details: - 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: diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 4193c83c923..cad29e6fd9c 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -1156,7 +1156,7 @@ class StockReservation: sre.voucher_no = item.get("voucher_no") or self.doc.name sre.voucher_detail_no = item.get(child_doctype) or item.name or item.get("voucher_detail_no") sre.available_qty = self.available_qty_to_reserve - sre.voucher_qty = qty + sre.voucher_qty = self.qty_to_be_reserved sre.reserved_qty = self.qty_to_be_reserved sre.company = self.doc.company sre.stock_uom = item_details.stock_uom diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index 573da6a53c6..aa49726374a 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -365,17 +365,9 @@ class TestStockReservationEntry(IntegrationTestCase): dn2 = make_delivery_note(so.name) for item in dn2.items: - item.qty = 80 + item.qty = 70 dn2.save() - for row in dn2.items: - if row.item_code in item_wise_serial_nos: - consumed_serial_no = ", ".join(item_wise_serial_nos[row.item_code]) - picked_serial_no = get_serial_nos(row.serial_no) - - serial_nos = list(set(picked_serial_no) - set(consumed_serial_no)) - row.serial_no = "\n".join(serial_nos) - dn2.submit() for item in so.items: diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 62022cab7a0..ef9c12b7b37 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -208,6 +208,9 @@ def update_stock(ctx, out, doc=None): } ) + if ctx.get("doctype") == "Delivery Note": + kwargs["against_sales_order"] = ctx.get("against_sales_order") + if ctx.get("ignore_serial_nos"): kwargs["ignore_serial_nos"] = ctx.get("ignore_serial_nos")