diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 76978017a6e..67c47efb5b5 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -201,6 +201,21 @@ class TestWorkOrder(ERPNextTestCase): self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production), cint(bin1_on_start_production.reserved_qty_for_production)) + def test_reserved_qty_for_production_closed(self): + + wo1 = make_wo_order_test_record(item="_Test FG Item", qty=2, + source_warehouse=self.warehouse) + item = wo1.required_items[0].item_code + bin_before = get_bin(item, self.warehouse) + bin_before.update_reserved_qty_for_production() + + make_wo_order_test_record(item="_Test FG Item", qty=2, + source_warehouse=self.warehouse) + close_work_order(wo1.name, "Closed") + + bin_after = get_bin(item, self.warehouse) + self.assertEqual(bin_before.reserved_qty_for_production, bin_after.reserved_qty_for_production) + def test_backflush_qty_for_overpduction_manufacture(self): cancel_stock_entry = [] allow_overproduction("overproduction_percentage_for_work_order", 30) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index a86edfa45f0..7315249512f 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -8,6 +8,8 @@ from dateutil.relativedelta import relativedelta from frappe import _ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from frappe.query_builder import Case +from frappe.query_builder.functions import Sum from frappe.utils import ( cint, date_diff, @@ -1175,3 +1177,27 @@ def create_pick_list(source_name, target_doc=None, for_qty=None): doc.set_item_locations() return doc + +def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float: + """Get total reserved quantity for any item in specified warehouse""" + wo = frappe.qb.DocType("Work Order") + wo_item = frappe.qb.DocType("Work Order Item") + + return ( + frappe.qb + .from_(wo) + .from_(wo_item) + .select(Sum(Case() + .when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) + .else_(wo_item.required_qty - wo_item.consumed_qty)) + ) + .where( + (wo_item.item_code == item_code) + & (wo_item.parent == wo.name) + & (wo.docstatus == 1) + & (wo_item.source_warehouse == warehouse) + & (wo.status.notin(["Stopped", "Completed", "Closed"])) + & ((wo_item.required_qty > wo_item.transferred_qty) + | (wo_item.required_qty > wo_item.consumed_qty)) + ) + ).run()[0][0] or 0.0 diff --git a/erpnext/patches.txt b/erpnext/patches.txt index feafecbc04f..d3003406710 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -350,3 +350,4 @@ erpnext.patches.v14_0.migrate_cost_center_allocations erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template erpnext.patches.v13_0.shopping_cart_to_ecommerce erpnext.patches.v13_0.update_disbursement_account +erpnext.patches.v13_0.update_reserved_qty_closed_wo diff --git a/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py b/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py new file mode 100644 index 00000000000..00926b09241 --- /dev/null +++ b/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py @@ -0,0 +1,28 @@ +import frappe + +from erpnext.stock.utils import get_bin + + +def execute(): + + wo = frappe.qb.DocType("Work Order") + wo_item = frappe.qb.DocType("Work Order Item") + + incorrect_item_wh = ( + frappe.qb + .from_(wo) + .join(wo_item).on(wo.name == wo_item.parent) + .select(wo_item.item_code, wo.source_warehouse).distinct() + .where( + (wo.status == "Closed") + & (wo.docstatus == 1) + & (wo.source_warehouse.notnull()) + ) + ).run() + + for item_code, warehouse in incorrect_item_wh: + if not (item_code and warehouse): + continue + + bin = get_bin(item_code, warehouse) + bin.update_reserved_qty_for_production() diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index c34e9d05cef..d2bae652390 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -35,28 +35,9 @@ class Bin(Document): def update_reserved_qty_for_production(self): '''Update qty reserved for production from Production Item tables in open work orders''' + from erpnext.manufacturing.doctype.work_order.work_order import get_reserved_qty_for_production - wo = frappe.qb.DocType("Work Order") - wo_item = frappe.qb.DocType("Work Order Item") - - self.reserved_qty_for_production = ( - frappe.qb - .from_(wo) - .from_(wo_item) - .select(Sum(Case() - .when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) - .else_(wo_item.required_qty - wo_item.consumed_qty)) - ) - .where( - (wo_item.item_code == self.item_code) - & (wo_item.parent == wo.name) - & (wo.docstatus == 1) - & (wo_item.source_warehouse == self.warehouse) - & (wo.status.notin(["Stopped", "Completed"])) - & ((wo_item.required_qty > wo_item.transferred_qty) - | (wo_item.required_qty > wo_item.consumed_qty)) - ) - ).run()[0][0] or 0.0 + self.reserved_qty_for_production = get_reserved_qty_for_production(self.item_code, self.warehouse) self.set_projected_qty()