fix: work order with multi level, fetch operting cost from sub-assembly (#38992) (cherry picked from commit 70abedc57ad2147719c23537ab9aaf348d6e9182) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
This commit is contained in:
parent
f3254c2010
commit
3a7506ecbc
@ -1486,3 +1486,47 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
|
||||
)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def get_op_cost_from_sub_assemblies(bom_no, op_cost=0):
|
||||
# Get operating cost from sub-assemblies
|
||||
|
||||
bom_items = frappe.get_all(
|
||||
"BOM Item", filters={"parent": bom_no, "docstatus": 1}, fields=["bom_no"], order_by="idx asc"
|
||||
)
|
||||
|
||||
for row in bom_items:
|
||||
if not row.bom_no:
|
||||
continue
|
||||
|
||||
if cost := frappe.get_cached_value("BOM", row.bom_no, "operating_cost_per_bom_quantity"):
|
||||
op_cost += flt(cost)
|
||||
get_op_cost_from_sub_assemblies(row.bom_no, op_cost)
|
||||
|
||||
return op_cost
|
||||
|
||||
|
||||
def get_scrap_items_from_sub_assemblies(bom_no, company, qty, scrap_items=None):
|
||||
if not scrap_items:
|
||||
scrap_items = {}
|
||||
|
||||
bom_items = frappe.get_all(
|
||||
"BOM Item",
|
||||
filters={"parent": bom_no, "docstatus": 1},
|
||||
fields=["bom_no", "qty"],
|
||||
order_by="idx asc",
|
||||
)
|
||||
|
||||
for row in bom_items:
|
||||
if not row.bom_no:
|
||||
continue
|
||||
|
||||
qty = flt(row.qty) * flt(qty)
|
||||
items = get_bom_items_as_dict(
|
||||
row.bom_no, company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||
)
|
||||
scrap_items.update(items)
|
||||
|
||||
get_scrap_items_from_sub_assemblies(row.bom_no, company, qty, scrap_items)
|
||||
|
||||
return scrap_items
|
||||
|
@ -31,6 +31,7 @@
|
||||
"job_card_excess_transfer",
|
||||
"other_settings_section",
|
||||
"update_bom_costs_automatically",
|
||||
"set_op_cost_and_scrape_from_sub_assemblies",
|
||||
"column_break_23",
|
||||
"make_serial_no_batch_from_work_order"
|
||||
],
|
||||
@ -194,13 +195,20 @@
|
||||
"fieldname": "job_card_excess_transfer",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Excess Material Transfer"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "In the case of 'Use Multi-Level BOM' in a work order, if the user wishes to add sub-assembly costs to Finished Goods items without using a job card as well the scrap items, then this option needs to be enable.",
|
||||
"fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Operating Cost / Scrape Items From Sub-assemblies"
|
||||
}
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-13 22:09:09.401559",
|
||||
"modified": "2023-12-28 16:37:44.874096",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
@ -216,5 +224,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -32,6 +32,7 @@ class ManufacturingSettings(Document):
|
||||
mins_between_operations: DF.Int
|
||||
overproduction_percentage_for_sales_order: DF.Percent
|
||||
overproduction_percentage_for_work_order: DF.Percent
|
||||
set_op_cost_and_scrape_from_sub_assemblies: DF.Check
|
||||
update_bom_costs_automatically: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
|
@ -1602,6 +1602,10 @@ def make_bom(**args):
|
||||
}
|
||||
)
|
||||
|
||||
if args.operating_cost_per_bom_quantity:
|
||||
bom.fg_based_operating_cost = 1
|
||||
bom.operating_cost_per_bom_quantity = args.operating_cost_per_bom_quantity
|
||||
|
||||
for item in args.raw_materials:
|
||||
item_doc = frappe.get_doc("Item", item)
|
||||
bom.append(
|
||||
|
@ -1731,6 +1731,93 @@ class TestWorkOrder(FrappeTestCase):
|
||||
job_card2.time_logs = []
|
||||
job_card2.save()
|
||||
|
||||
def test_op_cost_and_scrap_based_on_sub_assemblies(self):
|
||||
# Make Sub Assembly BOM 1
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 1
|
||||
)
|
||||
|
||||
items = {
|
||||
"Test Final FG Item": 0,
|
||||
"Test Final SF Item 1": 0,
|
||||
"Test Final SF Item 2": 0,
|
||||
"Test Final RM Item 1": 100,
|
||||
"Test Final RM Item 2": 200,
|
||||
"Test Final Scrap Item 1": 50,
|
||||
"Test Final Scrap Item 2": 60,
|
||||
}
|
||||
|
||||
for item in items:
|
||||
if not frappe.db.exists("Item", item):
|
||||
item_properties = {"is_stock_item": 1, "valuation_rate": items[item]}
|
||||
|
||||
make_item(item_code=item, properties=item_properties),
|
||||
|
||||
prepare_boms_for_sub_assembly_test()
|
||||
|
||||
wo_order = make_wo_order_test_record(
|
||||
production_item="Test Final FG Item",
|
||||
qty=10,
|
||||
use_multi_level_bom=1,
|
||||
skip_transfer=1,
|
||||
from_wip_warehouse=1,
|
||||
)
|
||||
|
||||
se_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||
se_doc.save()
|
||||
|
||||
self.assertTrue(se_doc.additional_costs)
|
||||
scrap_items = []
|
||||
for item in se_doc.items:
|
||||
if item.is_scrap_item:
|
||||
scrap_items.append(item.item_code)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(scrap_items), sorted(["Test Final Scrap Item 1", "Test Final Scrap Item 2"])
|
||||
)
|
||||
for row in se_doc.additional_costs:
|
||||
self.assertEqual(row.amount, 3000)
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 0
|
||||
)
|
||||
|
||||
|
||||
def prepare_boms_for_sub_assembly_test():
|
||||
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 1"}):
|
||||
bom = make_bom(
|
||||
item="Test Final SF Item 1",
|
||||
source_warehouse="Stores - _TC",
|
||||
raw_materials=["Test Final RM Item 1"],
|
||||
operating_cost_per_bom_quantity=100,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 1", "qty": 1})
|
||||
|
||||
bom.submit()
|
||||
|
||||
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 2"}):
|
||||
bom = make_bom(
|
||||
item="Test Final SF Item 2",
|
||||
source_warehouse="Stores - _TC",
|
||||
raw_materials=["Test Final RM Item 2"],
|
||||
operating_cost_per_bom_quantity=200,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 2", "qty": 1})
|
||||
|
||||
bom.submit()
|
||||
|
||||
if not frappe.db.exists("BOM", {"item": "Test Final FG Item"}):
|
||||
bom = make_bom(
|
||||
item="Test Final FG Item",
|
||||
source_warehouse="Stores - _TC",
|
||||
raw_materials=["Test Final SF Item 1", "Test Final SF Item 2"],
|
||||
)
|
||||
|
||||
|
||||
def prepare_data_for_workstation_type_check():
|
||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||
@ -1977,6 +2064,7 @@ def make_wo_order_test_record(**args):
|
||||
wo_order.sales_order = args.sales_order or None
|
||||
wo_order.planned_start_date = args.planned_start_date or now()
|
||||
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
|
||||
wo_order.from_wip_warehouse = args.from_wip_warehouse or 0
|
||||
|
||||
if args.source_warehouse:
|
||||
for item in wo_order.get("required_items"):
|
||||
|
@ -25,7 +25,12 @@ from frappe.utils import (
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
|
||||
from erpnext.manufacturing.doctype.bom.bom import add_additional_cost, validate_bom_no
|
||||
from erpnext.manufacturing.doctype.bom.bom import (
|
||||
add_additional_cost,
|
||||
get_op_cost_from_sub_assemblies,
|
||||
get_scrap_items_from_sub_assemblies,
|
||||
validate_bom_no,
|
||||
)
|
||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
@ -1898,11 +1903,22 @@ class StockEntry(StockController):
|
||||
def get_bom_scrap_material(self, qty):
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||
|
||||
# item dict = { item_code: {qty, description, stock_uom} }
|
||||
item_dict = (
|
||||
get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1)
|
||||
or {}
|
||||
)
|
||||
if (
|
||||
frappe.db.get_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
|
||||
)
|
||||
and self.work_order
|
||||
and frappe.get_cached_value("Work Order", self.work_order, "use_multi_level_bom")
|
||||
):
|
||||
item_dict = get_scrap_items_from_sub_assemblies(self.bom_no, self.company, qty)
|
||||
else:
|
||||
# item dict = { item_code: {qty, description, stock_uom} }
|
||||
item_dict = (
|
||||
get_bom_items_as_dict(
|
||||
self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||
)
|
||||
or {}
|
||||
)
|
||||
|
||||
for item in item_dict.values():
|
||||
item.from_warehouse = ""
|
||||
@ -2653,6 +2669,15 @@ def get_work_order_details(work_order, company):
|
||||
def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||
operating_cost_per_unit = 0
|
||||
if work_order:
|
||||
if (
|
||||
bom_no
|
||||
and frappe.db.get_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
|
||||
)
|
||||
and frappe.get_cached_value("Work Order", work_order, "use_multi_level_bom")
|
||||
):
|
||||
return get_op_cost_from_sub_assemblies(bom_no)
|
||||
|
||||
if not bom_no:
|
||||
bom_no = work_order.bom_no
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user