diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 804f03dc519..694dc79d4a9 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -5,7 +5,7 @@ import copy import frappe from frappe.tests.utils import FrappeTestCase, change_settings, timeout -from frappe.utils import add_days, add_months, cint, flt, now, today +from frappe.utils import add_days, add_months, add_to_date, cint, flt, now, today from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom @@ -1480,6 +1480,166 @@ class TestWorkOrder(FrappeTestCase): for row in return_ste_doc.items: self.assertEqual(row.qty, 2) + def test_workstation_type_for_work_order(self): + prepare_data_for_workstation_type_check() + + workstation_types = ["Workstation Type 1", "Workstation Type 2", "Workstation Type 3"] + planned_start_date = "2022-11-14 10:00:00" + + wo_order = make_wo_order_test_record( + item="Test FG Item For Workstation Type", planned_start_date=planned_start_date, qty=2 + ) + + job_cards = frappe.get_all( + "Job Card", + fields=[ + "`tabJob Card`.`name`", + "`tabJob Card`.`workstation_type`", + "`tabJob Card`.`workstation`", + "`tabJob Card Time Log`.`from_time`", + "`tabJob Card Time Log`.`to_time`", + "`tabJob Card Time Log`.`time_in_mins`", + ], + filters=[ + ["Job Card", "work_order", "=", wo_order.name], + ["Job Card Time Log", "docstatus", "=", 1], + ], + order_by="`tabJob Card`.`creation` desc", + ) + + workstations_to_check = ["Workstation 1", "Workstation 3", "Workstation 5"] + for index, row in enumerate(job_cards): + if index != 0: + planned_start_date = add_to_date(planned_start_date, minutes=40) + + self.assertEqual(row.workstation_type, workstation_types[index]) + self.assertEqual(row.from_time, planned_start_date) + self.assertEqual(row.to_time, add_to_date(planned_start_date, minutes=30)) + self.assertEqual(row.workstation, workstations_to_check[index]) + + planned_start_date = "2022-11-14 10:00:00" + + wo_order = make_wo_order_test_record( + item="Test FG Item For Workstation Type", planned_start_date=planned_start_date, qty=2 + ) + + job_cards = frappe.get_all( + "Job Card", + fields=[ + "`tabJob Card`.`name`", + "`tabJob Card`.`workstation_type`", + "`tabJob Card`.`workstation`", + "`tabJob Card Time Log`.`from_time`", + "`tabJob Card Time Log`.`to_time`", + "`tabJob Card Time Log`.`time_in_mins`", + ], + filters=[ + ["Job Card", "work_order", "=", wo_order.name], + ["Job Card Time Log", "docstatus", "=", 1], + ], + order_by="`tabJob Card`.`creation` desc", + ) + + workstations_to_check = ["Workstation 2", "Workstation 4", "Workstation 6"] + for index, row in enumerate(job_cards): + if index != 0: + planned_start_date = add_to_date(planned_start_date, minutes=40) + + self.assertEqual(row.workstation_type, workstation_types[index]) + self.assertEqual(row.from_time, planned_start_date) + self.assertEqual(row.to_time, add_to_date(planned_start_date, minutes=30)) + self.assertEqual(row.workstation, workstations_to_check[index]) + + +def prepare_data_for_workstation_type_check(): + from erpnext.manufacturing.doctype.operation.test_operation import make_operation + from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation + from erpnext.manufacturing.doctype.workstation_type.test_workstation_type import ( + create_workstation_type, + ) + + workstation_types = ["Workstation Type 1", "Workstation Type 2", "Workstation Type 3"] + for workstation_type in workstation_types: + create_workstation_type(workstation_type=workstation_type) + + operations = ["Cutting", "Sewing", "Packing"] + for operation in operations: + make_operation( + { + "operation": operation, + } + ) + + workstations = [ + { + "workstation": "Workstation 1", + "workstation_type": "Workstation Type 1", + }, + { + "workstation": "Workstation 2", + "workstation_type": "Workstation Type 1", + }, + { + "workstation": "Workstation 3", + "workstation_type": "Workstation Type 2", + }, + { + "workstation": "Workstation 4", + "workstation_type": "Workstation Type 2", + }, + { + "workstation": "Workstation 5", + "workstation_type": "Workstation Type 3", + }, + { + "workstation": "Workstation 6", + "workstation_type": "Workstation Type 3", + }, + ] + + for row in workstations: + make_workstation(row) + + fg_item = make_item( + "Test FG Item For Workstation Type", + { + "is_stock_item": 1, + }, + ) + + rm_item = make_item( + "Test RM Item For Workstation Type", + { + "is_stock_item": 1, + }, + ) + + if not frappe.db.exists("BOM", {"item": fg_item.name}): + bom_doc = make_bom( + item=fg_item.name, + source_warehouse="Stores - _TC", + raw_materials=[rm_item.name], + do_not_submit=True, + ) + + submit_bom = False + for index, operation in enumerate(operations): + if not frappe.db.exists("BOM Operation", {"parent": bom_doc.name, "operation": operation}): + bom_doc.append( + "operations", + { + "operation": operation, + "time_in_mins": 30, + "hour_rate": 100, + "workstation_type": workstation_types[index], + }, + ) + + submit_bom = True + + if submit_bom: + bom_doc.submit() + def prepare_data_for_backflush_based_on_materials_transferred(): batch_item_doc = make_item( diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py index 6db985c8c2e..1eb47ae577b 100644 --- a/erpnext/manufacturing/doctype/workstation/test_workstation.py +++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py @@ -107,6 +107,7 @@ def make_workstation(*args, **kwargs): doc = frappe.get_doc({"doctype": "Workstation", "workstation_name": workstation_name}) doc.hour_rate_rent = args.get("hour_rate_rent") doc.hour_rate_labour = args.get("hour_rate_labour") + doc.workstation_type = args.get("workstation_type") doc.insert() return doc diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js index 5b9cedb6f98..f830b170ed0 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.js +++ b/erpnext/manufacturing/doctype/workstation/workstation.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt frappe.ui.form.on("Workstation", { - onload: function(frm) { + onload(frm) { if(frm.is_new()) { frappe.call({ @@ -15,6 +15,18 @@ frappe.ui.form.on("Workstation", { } }) } + }, + + workstation_type(frm) { + if (frm.doc.workstation_type) { + frm.call({ + method: "set_data_based_on_workstation_type", + doc: frm.doc, + callback: function(r) { + frm.refresh_fields(); + } + }) + } } }); diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index 3c256221be5..d5b6d37d676 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -32,7 +32,11 @@ class OverlapError(frappe.ValidationError): class Workstation(Document): - def validate(self): + def before_save(self): + self.set_data_based_on_workstation_type() + self.set_hour_rate() + + def set_hour_rate(self): self.hour_rate = ( flt(self.hour_rate_labour) + flt(self.hour_rate_electricity) @@ -40,6 +44,30 @@ class Workstation(Document): + flt(self.hour_rate_rent) ) + @frappe.whitelist() + def set_data_based_on_workstation_type(self): + if self.workstation_type: + fields = [ + "hour_rate_labour", + "hour_rate_electricity", + "hour_rate_consumable", + "hour_rate_rent", + "hour_rate", + "description", + ] + + data = frappe.get_cached_value("Workstation Type", self.workstation_type, fields, as_dict=True) + + if not data: + return + + for field in fields: + if self.get(field): + continue + + if value := data.get(field): + self.set(field, value) + def on_update(self): self.validate_overlap_for_operation_timings() self.update_bom_operation() diff --git a/erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py b/erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py index 9e7a54dbe34..aa7a3ee92f6 100644 --- a/erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py +++ b/erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py @@ -1,9 +1,21 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import frappe from frappe.tests.utils import FrappeTestCase class TestWorkstationType(FrappeTestCase): pass + + +def create_workstation_type(**args): + args = frappe._dict(args) + + if workstation_type := frappe.db.exists("Workstation Type", args.workstation_type): + return frappe.get_doc("Workstation Type", workstation_type) + else: + doc = frappe.new_doc("Workstation Type") + doc.update(args) + doc.insert() + return doc diff --git a/erpnext/manufacturing/doctype/workstation_type/workstation_type.json b/erpnext/manufacturing/doctype/workstation_type/workstation_type.json index 86321cf2ffe..7d9e36abb45 100644 --- a/erpnext/manufacturing/doctype/workstation_type/workstation_type.json +++ b/erpnext/manufacturing/doctype/workstation_type/workstation_type.json @@ -19,9 +19,7 @@ "section_break_8", "hour_rate", "description_tab", - "description", - "holiday_tab", - "holiday_list" + "description" ], "fields": [ { @@ -81,12 +79,6 @@ "oldfieldtype": "Currency", "read_only": 1 }, - { - "fieldname": "holiday_list", - "fieldtype": "Link", - "label": "Holiday List", - "options": "Holiday List" - }, { "fieldname": "description", "fieldtype": "Small Text", @@ -106,11 +98,6 @@ "fieldtype": "Tab Break", "label": "Description" }, - { - "fieldname": "holiday_tab", - "fieldtype": "Tab Break", - "label": "Holiday" - }, { "fieldname": "section_break_8", "fieldtype": "Section Break" @@ -118,7 +105,7 @@ ], "icon": "icon-wrench", "links": [], - "modified": "2022-11-04 17:30:33.397719", + "modified": "2022-11-16 23:11:36.224249", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation Type", diff --git a/erpnext/manufacturing/doctype/workstation_type/workstation_type.py b/erpnext/manufacturing/doctype/workstation_type/workstation_type.py index b097755abb3..348f4f8a161 100644 --- a/erpnext/manufacturing/doctype/workstation_type/workstation_type.py +++ b/erpnext/manufacturing/doctype/workstation_type/workstation_type.py @@ -3,10 +3,20 @@ import frappe from frappe.model.document import Document +from frappe.utils import flt class WorkstationType(Document): - pass + def before_save(self): + self.set_hour_rate() + + def set_hour_rate(self): + self.hour_rate = ( + flt(self.hour_rate_labour) + + flt(self.hour_rate_electricity) + + flt(self.hour_rate_consumable) + + flt(self.hour_rate_rent) + ) def get_workstations(workstation_type): diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index 549f5afc707..c25f606060f 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -73,168 +73,6 @@ "onboard": 0, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Bill of Materials", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Item", - "link_count": 0, - "link_to": "Item", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "Item", - "hidden": 0, - "is_query_report": 0, - "label": "Bill of Materials", - "link_count": 0, - "link_to": "BOM", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workstation", - "link_count": 0, - "link_to": "Workstation", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Operation", - "link_count": 0, - "link_to": "Operation", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Routing", - "link_count": 0, - "link_to": "Routing", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Work Order", - "hidden": 0, - "is_query_report": 1, - "label": "Production Planning Report", - "link_count": 0, - "link_to": "Production Planning Report", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Work Order", - "hidden": 0, - "is_query_report": 1, - "label": "Work Order Summary", - "link_count": 0, - "link_to": "Work Order Summary", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Quality Inspection", - "hidden": 0, - "is_query_report": 1, - "label": "Quality Inspection Summary", - "link_count": 0, - "link_to": "Quality Inspection Summary", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Downtime Entry", - "hidden": 0, - "is_query_report": 1, - "label": "Downtime Analysis", - "link_count": 0, - "link_to": "Downtime Analysis", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Job Card", - "hidden": 0, - "is_query_report": 1, - "label": "Job Card Summary", - "link_count": 0, - "link_to": "Job Card Summary", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "BOM", - "hidden": 0, - "is_query_report": 1, - "label": "BOM Search", - "link_count": 0, - "link_to": "BOM Search", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "BOM", - "hidden": 0, - "is_query_report": 1, - "label": "BOM Stock Report", - "link_count": 0, - "link_to": "BOM Stock Report", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Work Order", - "hidden": 0, - "is_query_report": 1, - "label": "Production Analytics", - "link_count": 0, - "link_to": "Production Analytics", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "BOM", - "hidden": 0, - "is_query_report": 1, - "label": "BOM Operations Time", - "link_count": 0, - "link_to": "BOM Operations Time", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -400,9 +238,181 @@ "link_type": "Report", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Bill of Materials", + "link_count": 15, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item", + "link_count": 0, + "link_to": "Item", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Bill of Materials", + "link_count": 0, + "link_to": "BOM", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workstation Type", + "link_count": 0, + "link_to": "Workstation Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workstation", + "link_count": 0, + "link_to": "Workstation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Operation", + "link_count": 0, + "link_to": "Operation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Routing", + "link_count": 0, + "link_to": "Routing", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Production Planning Report", + "link_count": 0, + "link_to": "Production Planning Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Quality Inspection", + "hidden": 0, + "is_query_report": 1, + "label": "Work Order Summary", + "link_count": 0, + "link_to": "Work Order Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Downtime Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Quality Inspection Summary", + "link_count": 0, + "link_to": "Quality Inspection Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Job Card", + "hidden": 0, + "is_query_report": 1, + "label": "Downtime Analysis", + "link_count": 0, + "link_to": "Downtime Analysis", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "Job Card Summary", + "link_count": 0, + "link_to": "Job Card Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Search", + "link_count": 0, + "link_to": "BOM Search", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Stock Report", + "link_count": 0, + "link_to": "BOM Stock Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "Production Analytics", + "link_count": 0, + "link_to": "Production Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "BOM Operations Time", + "link_count": 0, + "link_to": "BOM Operations Time", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2022-06-15 15:18:57.062935", + "modified": "2022-11-14 14:53:34.616862", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing",