mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 11:09:17 +00:00
fix: do not create repeat work orders
This commit is contained in:
@@ -126,7 +126,9 @@ frappe.ui.form.on("Production Plan", {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.po_items && frm.doc.status !== "Closed") {
|
let items = frm.events.get_items_for_work_order(frm);
|
||||||
|
|
||||||
|
if (items?.length && frm.doc.status !== "Closed") {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Work Order / Subcontract PO"),
|
__("Work Order / Subcontract PO"),
|
||||||
() => {
|
() => {
|
||||||
@@ -207,6 +209,24 @@ frappe.ui.form.on("Production Plan", {
|
|||||||
set_field_options("projected_qty_formula", projected_qty_formula);
|
set_field_options("projected_qty_formula", projected_qty_formula);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_items_for_work_order(frm) {
|
||||||
|
let items = frm.doc.po_items;
|
||||||
|
if (frm.doc.sub_assembly_items?.length) {
|
||||||
|
items = [...items, ...frm.doc.sub_assembly_items];
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_items =
|
||||||
|
items.filter((item) => {
|
||||||
|
if (item.pending_qty) {
|
||||||
|
return item.pending_qty > item.ordered_qty;
|
||||||
|
} else {
|
||||||
|
return item.qty > (item.received_qty || item.ordered_qty);
|
||||||
|
}
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
return has_items;
|
||||||
|
},
|
||||||
|
|
||||||
has_unreserved_stock(frm, table, qty_field = "required_qty") {
|
has_unreserved_stock(frm, table, qty_field = "required_qty") {
|
||||||
let has_unreserved_stock = frm.doc[table].some(
|
let has_unreserved_stock = frm.doc[table].some(
|
||||||
(item) => flt(item[qty_field]) > flt(item.stock_reserved_qty)
|
(item) => flt(item[qty_field]) > flt(item.stock_reserved_qty)
|
||||||
|
|||||||
@@ -778,7 +778,14 @@ class ProductionPlan(Document):
|
|||||||
"company": self.get("company"),
|
"company": self.get("company"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flt(row.qty) <= flt(row.ordered_qty):
|
||||||
|
continue
|
||||||
|
|
||||||
self.prepare_data_for_sub_assembly_items(row, work_order_data)
|
self.prepare_data_for_sub_assembly_items(row, work_order_data)
|
||||||
|
|
||||||
|
if work_order_data.get("qty") <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
work_order = self.create_work_order(work_order_data)
|
work_order = self.create_work_order(work_order_data)
|
||||||
if work_order:
|
if work_order:
|
||||||
wo_list.append(work_order)
|
wo_list.append(work_order)
|
||||||
@@ -798,6 +805,8 @@ class ProductionPlan(Document):
|
|||||||
if row.get(field):
|
if row.get(field):
|
||||||
wo_data[field] = row.get(field)
|
wo_data[field] = row.get(field)
|
||||||
|
|
||||||
|
wo_data["qty"] = flt(row.get("qty")) - flt(row.get("ordered_qty"))
|
||||||
|
|
||||||
wo_data.update(
|
wo_data.update(
|
||||||
{
|
{
|
||||||
"use_multi_level_bom": 0,
|
"use_multi_level_bom": 0,
|
||||||
|
|||||||
@@ -2336,6 +2336,63 @@ class TestProductionPlan(IntegrationTestCase):
|
|||||||
self.assertTrue(len(reserved_entries) == 0)
|
self.assertTrue(len(reserved_entries) == 0)
|
||||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
||||||
|
|
||||||
|
def test_production_plan_for_partial_sub_assembly_items(self):
|
||||||
|
from erpnext.controllers.status_updater import OverAllowanceError
|
||||||
|
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||||
|
from erpnext.subcontracting.doctype.subcontracting_bom.test_subcontracting_bom import (
|
||||||
|
create_subcontracting_bom,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.flags.test_print = False
|
||||||
|
|
||||||
|
fg_wo_item = "Test Motherboard 11"
|
||||||
|
bom_tree_1 = {"Test Laptop 11": {fg_wo_item: {"Test Motherboard Wires 11": {}}}}
|
||||||
|
create_nested_bom(bom_tree_1, prefix="")
|
||||||
|
|
||||||
|
plan = create_production_plan(
|
||||||
|
item_code="Test Laptop 11",
|
||||||
|
planned_qty=10,
|
||||||
|
use_multi_level_bom=1,
|
||||||
|
do_not_submit=True,
|
||||||
|
company="_Test Company",
|
||||||
|
skip_getting_mr_items=True,
|
||||||
|
)
|
||||||
|
plan.get_sub_assembly_items()
|
||||||
|
plan.submit()
|
||||||
|
plan.make_work_order()
|
||||||
|
|
||||||
|
work_order = frappe.db.get_value("Work Order", {"production_plan": plan.name, "docstatus": 0}, "name")
|
||||||
|
wo_doc = frappe.get_doc("Work Order", work_order)
|
||||||
|
|
||||||
|
wo_doc.qty = 5.0
|
||||||
|
wo_doc.skip_transfer = 1
|
||||||
|
wo_doc.from_wip_warehouse = 1
|
||||||
|
wo_doc.wip_warehouse = "_Test Warehouse - _TC"
|
||||||
|
wo_doc.fg_warehouse = "_Test Warehouse - _TC"
|
||||||
|
wo_doc.submit()
|
||||||
|
|
||||||
|
plan.reload()
|
||||||
|
|
||||||
|
for row in plan.sub_assembly_items:
|
||||||
|
self.assertEqual(row.ordered_qty, 5.0)
|
||||||
|
|
||||||
|
plan.make_work_order()
|
||||||
|
|
||||||
|
work_order = frappe.db.get_value("Work Order", {"production_plan": plan.name, "docstatus": 0}, "name")
|
||||||
|
wo_doc = frappe.get_doc("Work Order", work_order)
|
||||||
|
self.assertEqual(wo_doc.qty, 5.0)
|
||||||
|
|
||||||
|
wo_doc.skip_transfer = 1
|
||||||
|
wo_doc.from_wip_warehouse = 1
|
||||||
|
wo_doc.wip_warehouse = "_Test Warehouse - _TC"
|
||||||
|
wo_doc.fg_warehouse = "_Test Warehouse - _TC"
|
||||||
|
wo_doc.submit()
|
||||||
|
|
||||||
|
plan.reload()
|
||||||
|
|
||||||
|
for row in plan.sub_assembly_items:
|
||||||
|
self.assertEqual(row.ordered_qty, 10.0)
|
||||||
|
|
||||||
|
|
||||||
def create_production_plan(**args):
|
def create_production_plan(**args):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"wo_produced_qty",
|
"wo_produced_qty",
|
||||||
"stock_reserved_qty",
|
"stock_reserved_qty",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
|
"ordered_qty",
|
||||||
"received_qty",
|
"received_qty",
|
||||||
"indent",
|
"indent",
|
||||||
"section_break_19",
|
"section_break_19",
|
||||||
@@ -231,13 +232,20 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ordered_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Ordered Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-01 14:28:35.979941",
|
"modified": "2025-06-10 13:36:24.759101",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan Sub Assembly Item",
|
"name": "Production Plan Sub Assembly Item",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ProductionPlanSubAssemblyItem(Document):
|
|||||||
fg_warehouse: DF.Link | None
|
fg_warehouse: DF.Link | None
|
||||||
indent: DF.Int
|
indent: DF.Int
|
||||||
item_name: DF.Data | None
|
item_name: DF.Data | None
|
||||||
|
ordered_qty: DF.Float
|
||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parent_item_code: DF.Link | None
|
parent_item_code: DF.Link | None
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
|
|||||||
@@ -845,22 +845,34 @@ class WorkOrder(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update_ordered_qty(self):
|
def update_ordered_qty(self):
|
||||||
if self.production_plan and self.production_plan_item and not self.production_plan_sub_assembly_item:
|
if self.production_plan and (self.production_plan_item or self.production_plan_sub_assembly_item):
|
||||||
table = frappe.qb.DocType("Work Order")
|
table = frappe.qb.DocType("Work Order")
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(table)
|
frappe.qb.from_(table)
|
||||||
.select(Sum(table.qty))
|
.select(Sum(table.qty))
|
||||||
.where(
|
.where((table.production_plan == self.production_plan) & (table.docstatus == 1))
|
||||||
(table.production_plan == self.production_plan)
|
)
|
||||||
& (table.production_plan_item == self.production_plan_item)
|
|
||||||
& (table.docstatus == 1)
|
|
||||||
)
|
|
||||||
).run()
|
|
||||||
|
|
||||||
|
if self.production_plan_item:
|
||||||
|
query = query.where(table.production_plan_item == self.production_plan_item)
|
||||||
|
elif self.production_plan_sub_assembly_item:
|
||||||
|
query = query.where(
|
||||||
|
table.production_plan_sub_assembly_item == self.production_plan_sub_assembly_item
|
||||||
|
)
|
||||||
|
|
||||||
|
query = query.run()
|
||||||
qty = flt(query[0][0]) if query else 0
|
qty = flt(query[0][0]) if query else 0
|
||||||
|
|
||||||
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
|
if self.production_plan_item:
|
||||||
|
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
|
||||||
|
elif self.production_plan_sub_assembly_item:
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Production Plan Sub Assembly Item",
|
||||||
|
self.production_plan_sub_assembly_item,
|
||||||
|
"ordered_qty",
|
||||||
|
qty,
|
||||||
|
)
|
||||||
|
|
||||||
doc = frappe.get_doc("Production Plan", self.production_plan)
|
doc = frappe.get_doc("Production Plan", self.production_plan)
|
||||||
doc.set_status()
|
doc.set_status()
|
||||||
|
|||||||
Reference in New Issue
Block a user