diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 8334d6121d1de62baccb87ba4fd5d33358de2321..d75b76dc8ec4b0992784bfccf5a2af8afbfdf5e0 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -637,7 +637,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-12-26 19:34:08.159312", + "modified": "2024-03-01 11:06:45.832536", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index eeb0484aebee31303452a2f6f3c00a12ea960022..1ea750c2ee8a9dc8a8a8c60e689f5e76ae64195a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -103,6 +103,71 @@ class BOMTree: class BOM(WebsiteGenerator): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.manufacturing.doctype.bom_explosion_item.bom_explosion_item import BOMExplosionItem + from erpnext.manufacturing.doctype.bom_item.bom_item import BOMItem + from erpnext.manufacturing.doctype.bom_operation.bom_operation import BOMOperation + from erpnext.manufacturing.doctype.bom_scrap_item.bom_scrap_item import BOMScrapItem + + allow_alternative_item: DF.Check + amended_from: DF.Link | None + base_operating_cost: DF.Currency + base_raw_material_cost: DF.Currency + base_scrap_material_cost: DF.Currency + base_total_cost: DF.Currency + bom_creator: DF.Link | None + bom_creator_item: DF.Data | None + buying_price_list: DF.Link | None + company: DF.Link + conversion_rate: DF.Float + currency: DF.Link + description: DF.SmallText | None + exploded_items: DF.Table[BOMExplosionItem] + fg_based_operating_cost: DF.Check + has_variants: DF.Check + image: DF.AttachImage | None + inspection_required: DF.Check + is_active: DF.Check + is_default: DF.Check + item: DF.Link + item_name: DF.Data | None + items: DF.Table[BOMItem] + operating_cost: DF.Currency + operating_cost_per_bom_quantity: DF.Currency + operations: DF.Table[BOMOperation] + plc_conversion_rate: DF.Float + price_list_currency: DF.Link | None + process_loss_percentage: DF.Percent + process_loss_qty: DF.Float + project: DF.Link | None + quality_inspection_template: DF.Link | None + quantity: DF.Float + raw_material_cost: DF.Currency + rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List", "Manual"] + route: DF.SmallText | None + routing: DF.Link | None + scrap_items: DF.Table[BOMScrapItem] + scrap_material_cost: DF.Currency + set_rate_of_sub_assembly_item_based_on_bom: DF.Check + show_in_website: DF.Check + show_items: DF.Check + show_operations: DF.Check + thumbnail: DF.Data | None + total_cost: DF.Currency + transfer_material_against: DF.Literal["", "Work Order", "Job Card"] + uom: DF.Link | None + web_long_description: DF.TextEditor | None + website_image: DF.AttachImage | None + with_operations: DF.Check + # end: auto-generated types + website = frappe._dict( # page_title_field = "item_name", condition_field="show_in_website", @@ -1202,12 +1267,12 @@ def get_children(parent=None, is_root=False, **filters): def add_additional_cost(stock_entry, work_order): # Add non stock items cost in the additional cost stock_entry.additional_costs = [] - default_expense_account = frappe.get_cached_value( - "Company", work_order.company, "default_expense_account" + expenses_included_in_valuation = frappe.get_cached_value( + "Company", work_order.company, "expenses_included_in_valuation" ) - add_non_stock_items_cost(stock_entry, work_order, default_expense_account) - add_operations_cost(stock_entry, work_order, default_expense_account) + add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation) + add_operations_cost(stock_entry, work_order, expenses_included_in_valuation) def add_non_stock_items_cost(stock_entry, work_order, expense_account): @@ -1338,18 +1403,13 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "item_name", "item_group", "description"] fields.extend( - [field for field in searchfields if field not in ["name", "item_group", "description"]] + [field for field in searchfields if not field in ["name", "item_group", "description"]] ) searchfields = searchfields + [ field - for field in [ - searchfield or "name", - "item_code", - "item_group", - "item_name", - ] - if field not in searchfields + for field in [searchfield or "name", "item_code", "item_group", "item_name"] + if not field in searchfields ] query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 2c1252d8ccd9a64f2e5485ba87dea0be11c2b48b..2cb3989b6b55f4d2b83ea61fa8e09be813e7dbdd 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -24,28 +24,6 @@ test_dependencies = ["Item", "Quality Inspection Template"] class TestBOM(FrappeTestCase): - @timeout - def test_bom_qty(self): - from erpnext.stock.doctype.item.test_item import make_item - - # No error. - bom = frappe.new_doc("BOM") - item = make_item(properties={"is_stock_item": 1}) - bom.item = fg_item.item_code - bom.quantity = 1 - bom.append( - "items", - { - "item_code": bom_item.item_code, - "qty": 0, - "uom": bom_item.stock_uom, - "stock_uom": bom_item.stock_uom, - "rate": 100.0, - }, - ) - bom.save() - self.assertEqual(bom.items[0].qty, 0) - @timeout def test_get_items(self): from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict @@ -718,8 +696,6 @@ class TestBOM(FrappeTestCase): self.assertFalse(bom.flags.cost_updated) def test_bom_with_service_item_cost(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 1000.0}).name service_item = make_item(properties={"is_stock_item": 0}).name diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 4d71794bc35038a3b3a4550955d0f24a6a87d2b2..c77caef83b2a2bb62e3b0fcec686d1b47657932a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1520,19 +1520,17 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx"))) if bom_no: - if ( - data.get("include_exploded_items") - and doc.get("sub_assembly_items") - and doc.get("skip_available_sub_assembly_item") - ): - item_details = get_raw_materials_of_sub_assembly_items( - item_details, - company, - bom_no, - include_non_stock_items, - sub_assembly_items, - planned_qty=planned_qty, - ) + if data.get("include_exploded_items") and doc.get("skip_available_sub_assembly_item"): + item_details = {} + if doc.get("sub_assembly_items"): + item_details = get_raw_materials_of_sub_assembly_items( + item_details, + company, + bom_no, + include_non_stock_items, + sub_assembly_items, + planned_qty=planned_qty, + ) elif data.get("include_exploded_items") and include_subcontracted_items: # fetch exploded items from BOM @@ -1551,6 +1549,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d 1, planned_qty=planned_qty, ) + elif data.get("item_code"): item_master = frappe.get_doc("Item", data["item_code"]).as_dict() purchase_uom = item_master.purchase_uom or item_master.stock_uom diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 95f836e21048bdfb82df38819933ffa5da59c7e6..0bf370564f9cf4134da1d5bfee9ecd16c23b1fe4 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1233,6 +1233,35 @@ class TestProductionPlan(FrappeTestCase): if row.item_code == "SubAssembly2 For SUB Test": self.assertEqual(row.quantity, 10) + def test_sub_assembly_and_their_raw_materials_exists(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + + bom_tree = { + "FG1 For SUB Test": { + "SAB1 For SUB Test": {"CP1 For SUB Test": {}}, + "SAB2 For SUB Test": {}, + } + } + + parent_bom = create_nested_bom(bom_tree, prefix="") + for item in ["SAB1 For SUB Test", "SAB2 For SUB Test"]: + make_stock_entry(item_code=item, qty=10, rate=100, target="_Test Warehouse - _TC") + + plan = create_production_plan( + item_code=parent_bom.item, + planned_qty=10, + ignore_existing_ordered_qty=1, + do_not_submit=1, + skip_available_sub_assembly_item=1, + warehouse="_Test Warehouse - _TC", + ) + + items = get_items_for_material_requests( + plan.as_dict(), warehouses=[{"warehouse": "_Test Warehouse - _TC"}] + ) + + self.assertFalse(items) + def test_transfer_and_purchase_mrp_for_purchase_uom(self): from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index ec92cb37a02745f6dc6af1d6755439e3563ca808..df73b0d2ddcf6ba49bf19f86d4d26c27fb4a8de2 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -36,6 +36,7 @@ "default": "1", "fieldname": "include_exploded_items", "fieldtype": "Check", + "in_list_view": 1, "label": "Include Exploded Items" }, { @@ -80,6 +81,13 @@ "fieldname": "column_break_6", "fieldtype": "Column Break" }, + { + "default": "0", + "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", + "fieldname": "make_work_order_for_sub_assembly_items", + "fieldtype": "Check", + "label": "Make Work Order for Sub Assembly Items" + }, { "columns": 2, "fieldname": "warehouse",