mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-13 06:31:48 +00:00
fix: recompute transferred qty before deciding work order status
work order status was decided using a stale transferred-qty value, computed before the current stock entry's transfer got recomputed. this left work orders stuck at "not started" for pick-list-driven transfers, since those entries never set fg_completed_qty and their transferred qty can only be known from actual item-level transfers. an earlier attempt fixed this by setting fg_completed_qty from the pick list's for_qty, but that broke two things tied to fg_completed_qty being zero: the excess-transfer guard, and the partial-transfer fraction logic used to avoid marking a work order as fully supplied too early. recompute the transferred qty first, then decide status from the fresh value. revert the fg_completed_qty change since it's no longer needed.
This commit is contained in:
@@ -649,6 +649,12 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
def update_status(self, status=None):
|
def update_status(self, status=None):
|
||||||
"""Update status of work order if unknown"""
|
"""Update status of work order if unknown"""
|
||||||
|
if self.docstatus == 1:
|
||||||
|
# Refresh material_transferred_for_manufacturing before deciding status so pick-list-
|
||||||
|
# driven transfers (where this qty is derived from item transfers, not fg_completed_qty)
|
||||||
|
# are reflected immediately, instead of only after the next status update call.
|
||||||
|
self.refresh_material_transferred_for_manufacturing()
|
||||||
|
|
||||||
if self.status != "Closed":
|
if self.status != "Closed":
|
||||||
if status not in ["Stopped", "Closed"]:
|
if status not in ["Stopped", "Closed"]:
|
||||||
status = self.get_status(status)
|
status = self.get_status(status)
|
||||||
@@ -1672,6 +1678,31 @@ class WorkOrder(Document):
|
|||||||
if self.skip_transfer:
|
if self.skip_transfer:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
transferred_items = self._material_transfer_qty_by_item(is_return=0)
|
||||||
|
|
||||||
|
row_wise_serial_batch = frappe._dict({})
|
||||||
|
if self.reserve_stock:
|
||||||
|
row_wise_serial_batch = get_row_wise_serial_batch(self.name)
|
||||||
|
|
||||||
|
for row in self.required_items:
|
||||||
|
transferred_qty = transferred_items.get(row.item_code) or 0.0
|
||||||
|
row.db_set("transferred_qty", transferred_qty, update_modified=False)
|
||||||
|
if self.reserve_stock:
|
||||||
|
self.update_qty_in_stock_reservation(row, transferred_qty, row_wise_serial_batch)
|
||||||
|
|
||||||
|
self.recompute_material_transferred_for_manufacturing(transferred_items)
|
||||||
|
|
||||||
|
def refresh_material_transferred_for_manufacturing(self):
|
||||||
|
"""Recompute material_transferred_for_manufacturing only, without touching per-row
|
||||||
|
transferred_qty or stock reservations. Used to get a status decision (Not Started vs
|
||||||
|
In Process) based on fresh data, ahead of the fuller update_required_items() pass.
|
||||||
|
"""
|
||||||
|
if self.skip_transfer:
|
||||||
|
return
|
||||||
|
transferred_items = self._material_transfer_qty_by_item(is_return=0)
|
||||||
|
self.recompute_material_transferred_for_manufacturing(transferred_items)
|
||||||
|
|
||||||
|
def _material_transfer_qty_by_item(self, is_return):
|
||||||
ste = frappe.qb.DocType("Stock Entry")
|
ste = frappe.qb.DocType("Stock Entry")
|
||||||
ste_child = frappe.qb.DocType("Stock Entry Detail")
|
ste_child = frappe.qb.DocType("Stock Entry Detail")
|
||||||
|
|
||||||
@@ -1688,25 +1719,13 @@ class WorkOrder(Document):
|
|||||||
(ste.docstatus == 1)
|
(ste.docstatus == 1)
|
||||||
& (ste.work_order == self.name)
|
& (ste.work_order == self.name)
|
||||||
& (ste.purpose == "Material Transfer for Manufacture")
|
& (ste.purpose == "Material Transfer for Manufacture")
|
||||||
& (ste.is_return == 0)
|
& (ste.is_return == is_return)
|
||||||
)
|
)
|
||||||
.groupby(ste_child.item_code)
|
.groupby(ste_child.item_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
data = query.run(as_dict=1) or []
|
data = query.run(as_dict=1) or []
|
||||||
transferred_items = frappe._dict({d.original_item or d.item_code: d.qty for d in data})
|
return frappe._dict({d.original_item or d.item_code: d.qty for d in data})
|
||||||
|
|
||||||
row_wise_serial_batch = frappe._dict({})
|
|
||||||
if self.reserve_stock:
|
|
||||||
row_wise_serial_batch = get_row_wise_serial_batch(self.name)
|
|
||||||
|
|
||||||
for row in self.required_items:
|
|
||||||
transferred_qty = transferred_items.get(row.item_code) or 0.0
|
|
||||||
row.db_set("transferred_qty", transferred_qty, update_modified=False)
|
|
||||||
if self.reserve_stock:
|
|
||||||
self.update_qty_in_stock_reservation(row, transferred_qty, row_wise_serial_batch)
|
|
||||||
|
|
||||||
self.recompute_material_transferred_for_manufacturing(transferred_items)
|
|
||||||
|
|
||||||
def recompute_material_transferred_for_manufacturing(self, transferred_items):
|
def recompute_material_transferred_for_manufacturing(self, transferred_items):
|
||||||
"""Set material_transferred_for_manufacturing based on actual item-level transfers, not fg_completed_qty."""
|
"""Set material_transferred_for_manufacturing based on actual item-level transfers, not fg_completed_qty."""
|
||||||
@@ -1777,29 +1796,7 @@ class WorkOrder(Document):
|
|||||||
doc.update_reserved_stock_in_bin()
|
doc.update_reserved_stock_in_bin()
|
||||||
|
|
||||||
def update_returned_qty(self):
|
def update_returned_qty(self):
|
||||||
ste = frappe.qb.DocType("Stock Entry")
|
returned_dict = self._material_transfer_qty_by_item(is_return=1)
|
||||||
ste_child = frappe.qb.DocType("Stock Entry Detail")
|
|
||||||
|
|
||||||
query = (
|
|
||||||
frappe.qb.from_(ste)
|
|
||||||
.inner_join(ste_child)
|
|
||||||
.on(ste_child.parent == ste.name)
|
|
||||||
.select(
|
|
||||||
ste_child.item_code,
|
|
||||||
ste_child.original_item,
|
|
||||||
fn.Sum(ste_child.transfer_qty).as_("qty"),
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
(ste.docstatus == 1)
|
|
||||||
& (ste.work_order == self.name)
|
|
||||||
& (ste.purpose == "Material Transfer for Manufacture")
|
|
||||||
& (ste.is_return == 1)
|
|
||||||
)
|
|
||||||
.groupby(ste_child.item_code)
|
|
||||||
)
|
|
||||||
|
|
||||||
data = query.run(as_dict=1) or []
|
|
||||||
returned_dict = frappe._dict({d.original_item or d.item_code: d.qty for d in data})
|
|
||||||
|
|
||||||
for row in self.required_items:
|
for row in self.required_items:
|
||||||
row.db_set("returned_qty", (returned_dict.get(row.item_code) or 0.0), update_modified=False)
|
row.db_set("returned_qty", (returned_dict.get(row.item_code) or 0.0), update_modified=False)
|
||||||
|
|||||||
Reference in New Issue
Block a user