fix: parent warehouse checks in the production plan for sub-assemblies (#40150)
This commit is contained in:
parent
0c52eb913b
commit
6f5815e44f
@ -518,6 +518,12 @@ frappe.ui.form.on("Production Plan Sales Order", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on("Production Plan Sub Assembly Item", {
|
||||||
|
fg_warehouse(frm, cdt, cdn) {
|
||||||
|
erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "sub_assembly_items", "fg_warehouse");
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
frappe.tour['Production Plan'] = [
|
frappe.tour['Production Plan'] = [
|
||||||
{
|
{
|
||||||
fieldname: "get_items_from",
|
fieldname: "get_items_from",
|
||||||
|
@ -421,9 +421,11 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "When a parent warehouse is chosen, the system conducts stock checks against the associated child warehouses",
|
||||||
"fieldname": "sub_assembly_warehouse",
|
"fieldname": "sub_assembly_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Sub Assembly Warehouse",
|
"label": "Sub Assembly Warehouse",
|
||||||
|
"mandatory_depends_on": "eval:doc.skip_available_sub_assembly_item === 1",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -437,7 +439,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-02-11 15:42:47.642481",
|
"modified": "2024-02-27 13:34:20.692211",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
@ -894,8 +894,8 @@ class ProductionPlan(Document):
|
|||||||
sub_assembly_items_store = [] # temporary store to process all subassembly items
|
sub_assembly_items_store = [] # temporary store to process all subassembly items
|
||||||
|
|
||||||
for row in self.po_items:
|
for row in self.po_items:
|
||||||
if self.skip_available_sub_assembly_item and not row.warehouse:
|
if self.skip_available_sub_assembly_item and not self.sub_assembly_warehouse:
|
||||||
frappe.throw(_("Row #{0}: Please select the FG Warehouse in Assembly Items").format(row.idx))
|
frappe.throw(_("Row #{0}: Please select the Sub Assembly Warehouse").format(row.idx))
|
||||||
|
|
||||||
if not row.item_code:
|
if not row.item_code:
|
||||||
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
|
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
|
||||||
@ -905,15 +905,24 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
bom_data = []
|
bom_data = []
|
||||||
|
|
||||||
warehouse = (
|
warehouse = (self.sub_assembly_warehouse) if self.skip_available_sub_assembly_item else None
|
||||||
(self.sub_assembly_warehouse or row.warehouse)
|
|
||||||
if self.skip_available_sub_assembly_item
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
|
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
|
||||||
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
|
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
|
||||||
sub_assembly_items_store.extend(bom_data)
|
sub_assembly_items_store.extend(bom_data)
|
||||||
|
|
||||||
|
if not sub_assembly_items_store and self.skip_available_sub_assembly_item:
|
||||||
|
message = (
|
||||||
|
_(
|
||||||
|
"As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}."
|
||||||
|
).format(self.sub_assembly_warehouse)
|
||||||
|
+ "<br><br>"
|
||||||
|
)
|
||||||
|
message += _(
|
||||||
|
"If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox."
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.msgprint(message, title=_("Note"))
|
||||||
|
|
||||||
if self.combine_sub_items:
|
if self.combine_sub_items:
|
||||||
# Combine subassembly items
|
# Combine subassembly items
|
||||||
sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
|
sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
|
||||||
@ -926,15 +935,19 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
|
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
|
||||||
"Modify bom_data, set additional details."
|
"Modify bom_data, set additional details."
|
||||||
|
is_group_warehouse = frappe.db.get_value("Warehouse", self.sub_assembly_warehouse, "is_group")
|
||||||
|
|
||||||
for data in bom_data:
|
for data in bom_data:
|
||||||
data.qty = data.stock_qty
|
data.qty = data.stock_qty
|
||||||
data.production_plan_item = row.name
|
data.production_plan_item = row.name
|
||||||
data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse
|
|
||||||
data.schedule_date = row.planned_start_date
|
data.schedule_date = row.planned_start_date
|
||||||
data.type_of_manufacturing = manufacturing_type or (
|
data.type_of_manufacturing = manufacturing_type or (
|
||||||
"Subcontract" if data.is_sub_contracted_item else "In House"
|
"Subcontract" if data.is_sub_contracted_item else "In House"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not is_group_warehouse:
|
||||||
|
data.fg_warehouse = self.sub_assembly_warehouse
|
||||||
|
|
||||||
def set_default_supplier_for_subcontracting_order(self):
|
def set_default_supplier_for_subcontracting_order(self):
|
||||||
items = [
|
items = [
|
||||||
d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract"
|
d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract"
|
||||||
@ -1478,7 +1491,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
|
|||||||
so_item_details = frappe._dict()
|
so_item_details = frappe._dict()
|
||||||
|
|
||||||
sub_assembly_items = {}
|
sub_assembly_items = {}
|
||||||
if doc.get("skip_available_sub_assembly_item"):
|
if doc.get("skip_available_sub_assembly_item") and doc.get("sub_assembly_items"):
|
||||||
for d in doc.get("sub_assembly_items"):
|
for d in doc.get("sub_assembly_items"):
|
||||||
sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
|
sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
|
||||||
|
|
||||||
@ -1690,34 +1703,37 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=
|
|||||||
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
|
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
|
||||||
|
|
||||||
if warehouse:
|
if warehouse:
|
||||||
bin_dict = get_bin_details(d, company, for_warehouse=warehouse)
|
bin_details = get_bin_details(d, company, for_warehouse=warehouse)
|
||||||
|
|
||||||
if bin_dict and bin_dict[0].projected_qty > 0:
|
for _bin_dict in bin_details:
|
||||||
if bin_dict[0].projected_qty > stock_qty:
|
if _bin_dict.projected_qty > 0:
|
||||||
continue
|
if _bin_dict.projected_qty > stock_qty:
|
||||||
else:
|
stock_qty = 0
|
||||||
stock_qty = stock_qty - bin_dict[0].projected_qty
|
continue
|
||||||
|
else:
|
||||||
|
stock_qty = stock_qty - _bin_dict.projected_qty
|
||||||
|
|
||||||
bom_data.append(
|
if stock_qty > 0:
|
||||||
frappe._dict(
|
bom_data.append(
|
||||||
{
|
frappe._dict(
|
||||||
"parent_item_code": parent_item_code,
|
{
|
||||||
"description": d.description,
|
"parent_item_code": parent_item_code,
|
||||||
"production_item": d.item_code,
|
"description": d.description,
|
||||||
"item_name": d.item_name,
|
"production_item": d.item_code,
|
||||||
"stock_uom": d.stock_uom,
|
"item_name": d.item_name,
|
||||||
"uom": d.stock_uom,
|
"stock_uom": d.stock_uom,
|
||||||
"bom_no": d.value,
|
"uom": d.stock_uom,
|
||||||
"is_sub_contracted_item": d.is_sub_contracted_item,
|
"bom_no": d.value,
|
||||||
"bom_level": indent,
|
"is_sub_contracted_item": d.is_sub_contracted_item,
|
||||||
"indent": indent,
|
"bom_level": indent,
|
||||||
"stock_qty": stock_qty,
|
"indent": indent,
|
||||||
}
|
"stock_qty": stock_qty,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if d.value:
|
if d.value:
|
||||||
get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
|
get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
|
||||||
|
|
||||||
|
|
||||||
def set_default_warehouses(row, default_warehouses):
|
def set_default_warehouses(row, default_warehouses):
|
||||||
|
@ -1205,6 +1205,7 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
ignore_existing_ordered_qty=1,
|
ignore_existing_ordered_qty=1,
|
||||||
do_not_submit=1,
|
do_not_submit=1,
|
||||||
skip_available_sub_assembly_item=1,
|
skip_available_sub_assembly_item=1,
|
||||||
|
sub_assembly_warehouse="_Test Warehouse - _TC",
|
||||||
warehouse="_Test Warehouse - _TC",
|
warehouse="_Test Warehouse - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1338,6 +1339,7 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
ignore_existing_ordered_qty=1,
|
ignore_existing_ordered_qty=1,
|
||||||
do_not_submit=1,
|
do_not_submit=1,
|
||||||
skip_available_sub_assembly_item=1,
|
skip_available_sub_assembly_item=1,
|
||||||
|
sub_assembly_warehouse="_Test Warehouse - _TC",
|
||||||
warehouse="_Test Warehouse - _TC",
|
warehouse="_Test Warehouse - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1590,6 +1592,48 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
for row in work_orders:
|
for row in work_orders:
|
||||||
self.assertEqual(row.qty, wo_qty[row.name])
|
self.assertEqual(row.qty, wo_qty[row.name])
|
||||||
|
|
||||||
|
def test_parent_warehouse_for_sub_assembly_items(self):
|
||||||
|
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
|
parent_warehouse = "_Test Warehouse Group - _TC"
|
||||||
|
sub_warehouse = create_warehouse("Sub Warehouse", company="_Test Company")
|
||||||
|
|
||||||
|
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
sf_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
rm_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
|
||||||
|
bom_tree = {fg_item: {sf_item: {rm_item: {}}}}
|
||||||
|
create_nested_bom(bom_tree, prefix="")
|
||||||
|
|
||||||
|
pln = create_production_plan(
|
||||||
|
item_code=fg_item,
|
||||||
|
planned_qty=10,
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
sub_assembly_warehouse=parent_warehouse,
|
||||||
|
skip_available_sub_assembly_item=1,
|
||||||
|
do_not_submit=1,
|
||||||
|
skip_getting_mr_items=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
pln.get_sub_assembly_items()
|
||||||
|
|
||||||
|
for row in pln.sub_assembly_items:
|
||||||
|
self.assertFalse(row.fg_warehouse)
|
||||||
|
self.assertEqual(row.production_item, sf_item)
|
||||||
|
self.assertEqual(row.qty, 10.0)
|
||||||
|
|
||||||
|
make_stock_entry(item_code=sf_item, qty=5, target=sub_warehouse, rate=100)
|
||||||
|
|
||||||
|
pln.sub_assembly_items = []
|
||||||
|
pln.get_sub_assembly_items()
|
||||||
|
|
||||||
|
self.assertEqual(pln.sub_assembly_warehouse, parent_warehouse)
|
||||||
|
for row in pln.sub_assembly_items:
|
||||||
|
self.assertFalse(row.fg_warehouse)
|
||||||
|
self.assertEqual(row.production_item, sf_item)
|
||||||
|
self.assertEqual(row.qty, 5.0)
|
||||||
|
|
||||||
|
|
||||||
def create_production_plan(**args):
|
def create_production_plan(**args):
|
||||||
"""
|
"""
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"bom_no",
|
"bom_no",
|
||||||
"column_break_6",
|
"column_break_6",
|
||||||
"planned_qty",
|
"planned_qty",
|
||||||
|
"stock_uom",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"planned_start_date",
|
"planned_start_date",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
@ -18,7 +19,6 @@
|
|||||||
"ordered_qty",
|
"ordered_qty",
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"description",
|
"description",
|
||||||
"stock_uom",
|
|
||||||
"produced_qty",
|
"produced_qty",
|
||||||
"reference_section",
|
"reference_section",
|
||||||
"sales_order",
|
"sales_order",
|
||||||
@ -65,6 +65,7 @@
|
|||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 1,
|
||||||
"fieldname": "planned_qty",
|
"fieldname": "planned_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -80,6 +81,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -141,8 +143,10 @@
|
|||||||
"width": "200px"
|
"width": "200px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 1,
|
||||||
"fieldname": "stock_uom",
|
"fieldname": "stock_uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "UOM",
|
"label": "UOM",
|
||||||
"oldfieldname": "stock_uom",
|
"oldfieldname": "stock_uom",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
@ -216,7 +220,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-25 14:15:40.061514",
|
"modified": "2024-02-27 13:24:43.571844",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan Item",
|
"name": "Production Plan Item",
|
||||||
|
@ -101,7 +101,6 @@
|
|||||||
"columns": 1,
|
"columns": 1,
|
||||||
"fieldname": "bom_level",
|
"fieldname": "bom_level",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Level (BOM)",
|
"label": "Level (BOM)",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -149,8 +148,10 @@
|
|||||||
"label": "Indent"
|
"label": "Indent"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "fg_warehouse",
|
"fieldname": "fg_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Target Warehouse",
|
"label": "Target Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
@ -170,6 +171,7 @@
|
|||||||
"options": "Supplier"
|
"options": "Supplier"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 1,
|
||||||
"fieldname": "schedule_date",
|
"fieldname": "schedule_date",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -207,7 +209,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-03 13:33:42.959387",
|
"modified": "2024-02-27 13:45:17.422435",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan Sub Assembly Item",
|
"name": "Production Plan Sub Assembly Item",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user