diff --git a/beam/beam/custom/bom_scrap_item.json b/beam/beam/custom/bom_secondary_item.json similarity index 88% rename from beam/beam/custom/bom_scrap_item.json rename to beam/beam/custom/bom_secondary_item.json index 4037dd3e..205b26d6 100644 --- a/beam/beam/custom/bom_scrap_item.json +++ b/beam/beam/custom/bom_secondary_item.json @@ -7,7 +7,7 @@ "collapsible": 0, "columns": 0, "default": null, - "dt": "BOM Scrap Item", + "dt": "BOM Secondary Item", "fetch_if_empty": 0, "fieldname": "create_handling_unit", "fieldtype": "Check", @@ -28,7 +28,7 @@ "label": "Create Handling Unit", "length": 0, "module": "BEAM", - "name": "BOM Scrap Item-create_handling_unit", + "name": "BOM Secondary Item-create_handling_unit", "no_copy": 0, "non_negative": 0, "permlevel": 0, @@ -43,7 +43,7 @@ "unique": 0 } ], - "doctype": "BOM Scrap Item", + "doctype": "BOM Secondary Item", "property_setters": [], "sync_on_migrate": 1 } diff --git a/beam/beam/handling_unit.py b/beam/beam/handling_unit.py index b73182fe..e398904f 100644 --- a/beam/beam/handling_unit.py +++ b/beam/beam/handling_unit.py @@ -54,9 +54,11 @@ def generate_handling_units(doc, method=None): handling_unit.save() row.handling_unit = handling_unit.name - if doc.doctype == "Stock Entry" and doc.purpose == "Manufacture" and row.is_scrap_item: + if doc.doctype == "Stock Entry" and doc.purpose == "Manufacture" and row.type == "Scrap": create_handling_unit = frappe.get_value( - "BOM Scrap Item", {"item_code": row.item_code, "parent": doc.bom_no}, "create_handling_unit" + "BOM Secondary Item", + {"item_code": row.item_code, "parent": doc.bom_no, "type": "Scrap"}, + "create_handling_unit", ) if bool(create_handling_unit): handling_unit = frappe.new_doc("Handling Unit") @@ -68,7 +70,7 @@ def generate_handling_units(doc, method=None): continue if doc.doctype == "Stock Entry" and not ( - any([row.is_finished_item, doc.purpose == "Material Receipt", row.is_scrap_item]) + any([row.is_finished_item, doc.purpose == "Material Receipt", row.type == "Scrap"]) ): continue @@ -119,7 +121,7 @@ def validate_handling_unit_overconsumption(doc, method=None): if ( abs(hu.stock_qty - row.get(qty_field)) > 0.0 and (hu.stock_qty - row.get(qty_field) > precision_denominator) - and not row.is_scrap_item + and row.type != "Scrap" ): error = True else: # incoming and transfer / same warehouse diff --git a/beam/beam/overrides/stock_entry.py b/beam/beam/overrides/stock_entry.py index b4627ff5..e7d8a736 100644 --- a/beam/beam/overrides/stock_entry.py +++ b/beam/beam/overrides/stock_entry.py @@ -172,15 +172,15 @@ def validate_items_with_handling_unit(doc, method=None): for row in doc.items: if not frappe.get_value("Item", row.item_code, "enable_handling_unit"): continue - elif row.is_scrap_item and not frappe.get_value( - "BOM Scrap Item", - {"item_code": row.item_code, "parent": doc.get("bom_no")}, + elif row.type == "Scrap" and not frappe.get_value( + "BOM Secondary Item", + {"item_code": row.item_code, "parent": doc.get("bom_no"), "type": "Scrap"}, "create_handling_unit", ): continue elif ( doc.stock_entry_type in ("Repack", "Manufacture") - and not (row.t_warehouse or row.is_finished_item or row.is_scrap_item) + and not (row.t_warehouse or row.is_finished_item or row.type == "Scrap") and not row.handling_unit ): frappe.throw(frappe._(f"Row #{row.idx}: Handling Unit is missing for item {row.item_code}")) diff --git a/beam/beam/scan/__init__.py b/beam/beam/scan/__init__.py index 7ee59668..9c810dd6 100644 --- a/beam/beam/scan/__init__.py +++ b/beam/beam/scan/__init__.py @@ -99,7 +99,7 @@ def get_handling_unit(handling_unit: str, parent_doctype: str | None = None) -> filters={"handling_unit": handling_unit, "is_cancelled": 0}, fields=[ "item_code", - "SUM(actual_qty) AS stock_qty", + {"SUM": "actual_qty", "as": "stock_qty"}, "company", "handling_unit", "voucher_no", diff --git a/beam/tests/setup.py b/beam/tests/setup.py index 4cf7dacb..0bc2fd51 100644 --- a/beam/tests/setup.py +++ b/beam/tests/setup.py @@ -355,7 +355,7 @@ def create_boms(settings): b.append("operations", {**operation, "hour_rate": 15.00}) if bom.get("scrap_items"): for scrap_item in bom.get("scrap_items"): - b.append("scrap_items", {**scrap_item}) + b.append("secondary_items", {**scrap_item, "type": "Scrap"}) b.save() b.submit() @@ -477,6 +477,7 @@ def create_production_plan(settings, prod_plan_from_doc): pp.get_mr_items() for item in pp.po_items: item.planned_start_date = settings.day + pp.skip_available_sub_assembly_item = 0 pp.get_sub_assembly_items() for item in pp.sub_assembly_items: item.schedule_date = settings.day @@ -484,7 +485,14 @@ def create_production_plan(settings, prod_plan_from_doc): raw_materials = get_items_for_material_requests( pp.as_dict(), warehouses=None, get_parent_warehouse_data=None ) + combined_raw_materials = {} for row in raw_materials: + item_code = row.get("item_code") + if item_code in combined_raw_materials: + combined_raw_materials[item_code]["quantity"] += row.get("quantity") + else: + combined_raw_materials[item_code] = row + for row in combined_raw_materials.values(): pp.append( "mr_items", { @@ -497,9 +505,23 @@ def create_production_plan(settings, prod_plan_from_doc): pp.save() pp.submit() - pp.make_material_request() - mr = frappe.get_last_doc("Material Request") + mr = frappe.new_doc("Material Request") + mr.company = settings.company + mr.material_request_type = "Purchase" mr.schedule_date = mr.transaction_date = settings.day + for row in combined_raw_materials.values(): + mr.append( + "items", + { + "item_code": row.get("item_code"), + "qty": row.get("quantity"), + "uom": row.get("uom") or row.get("stock_uom"), + "warehouse": frappe.get_value( + "Item Default", {"parent": row.get("item_code")}, "default_warehouse" + ), + "schedule_date": settings.day, + }, + ) mr.save() mr.submit() @@ -547,6 +569,8 @@ def create_production_plan(settings, prod_plan_from_doc): for wo in wos: wo = frappe.get_doc("Work Order", wo) wo.wip_warehouse = "Kitchen - APC" + if not wo.fg_warehouse: + wo.fg_warehouse = "Kitchen - APC" wo.actual_start_date = wo.planned_start_date = start_time wo.required_items = sorted(wo.required_items, key=lambda x: x.get("item_code")) for idx, w in enumerate(wo.required_items, start=1): diff --git a/beam/tests/test_handling_unit.py b/beam/tests/test_handling_unit.py index 76b5d9b0..278098a0 100644 --- a/beam/tests/test_handling_unit.py +++ b/beam/tests/test_handling_unit.py @@ -294,7 +294,7 @@ def test_stock_entry_for_manufacture(): ): continue if ( - row.is_finished_item or row.is_scrap_item + row.is_finished_item or row.type == "Scrap" ): # finished and scrap items' handling units will be generated and wouldn't be scanned continue hu = frappe.get_value( @@ -321,16 +321,18 @@ def test_stock_entry_for_manufacture(): sle = frappe.get_doc( "Stock Ledger Entry", {"voucher_detail_no": row.name, "handling_unit": row.handling_unit} ) - if not row.is_finished_item and not row.is_scrap_item: + if not row.is_finished_item and row.type != "Scrap": assert row.transfer_qty == -(sle.actual_qty) assert row.item_code == sle.item_code assert row.s_warehouse == sle.warehouse # source/ warehouse assert sle.handling_unit == row.handling_unit - elif row.is_scrap_item: + elif row.type == "Scrap": assert row.transfer_qty == sle.actual_qty assert row.item_code == sle.item_code create_handling_unit = frappe.get_value( - "BOM Scrap Item", {"item_code": row.item_code, "parent": _se.bom_no}, "create_handling_unit" + "BOM Secondary Item", + {"item_code": row.item_code, "parent": _se.bom_no, "type": "Scrap"}, + "create_handling_unit", ) if create_handling_unit: assert row.handling_unit == sle.handling_unit diff --git a/poetry.lock b/poetry.lock index a18c40cd..cd7d3b31 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.0 and should not be changed by hand. [[package]] name = "annotated-types" @@ -54,9 +54,7 @@ files = [ ] [package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.32.0)"] @@ -478,9 +476,6 @@ files = [ {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] @@ -542,7 +537,6 @@ files = [ [package.dependencies] cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""} -typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""} [package.extras] ssh = ["bcrypt (>=3.1.5)"] @@ -576,22 +570,6 @@ dev = ["pre-commit (>=2.16.0) ; python_version >= \"3.9\"", "pydoctor (>=25.4.0) docs = ["pydoctor (>=25.4.0)"] test = ["pytest"] -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version == \"3.10\"" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "h11" version = "0.16.0" @@ -1118,7 +1096,6 @@ files = [ librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" pathspec = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.15\""} [package.extras] @@ -1426,7 +1403,6 @@ files = [ [package.dependencies] cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} -typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] crypto = ["cryptography (>=3.4.0)"] @@ -1487,11 +1463,9 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -1684,19 +1658,6 @@ files = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_full_version <= \"3.11.0a6\"" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - [[package]] name = "tqdm" version = "4.68.3" @@ -1868,9 +1829,6 @@ files = [ {file = "vulture-2.16.tar.gz", hash = "sha256:f8d9f6e2af03011664a3c6c240c9765b3f392917d3135fddca6d6a68d359f717"}, ] -[package.dependencies] -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - [[package]] name = "zebra-zpl" version = "0.1.0" @@ -1889,5 +1847,5 @@ resolved_reference = "45ffc60638814df575d9fe11c7504b1a533e4ecb" [metadata] lock-version = "2.1" -python-versions = ">=3.10,<3.15" -content-hash = "c69661febdefcd14d5a01f31684a13a66b28ef20980ab2b3d2c9bb1b6e2f8b0a" +python-versions = ">=3.14,<3.15" +content-hash = "5b84327d44d5c770614c223d28894257dacd4a6f5973dce17d90104e9b504bf8"