From 1f13bc906bc4c8710b0cf9189ad5a4e3129c04b5 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 7 Nov 2022 09:43:28 -0600 Subject: [PATCH 1/5] fix: add translation functuinon to doctype name on message in bulk_transaction_processing --- erpnext/public/js/bulk_transaction_processing.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/bulk_transaction_processing.js b/erpnext/public/js/bulk_transaction_processing.js index 101f50c64a..0e42b477ea 100644 --- a/erpnext/public/js/bulk_transaction_processing.js +++ b/erpnext/public/js/bulk_transaction_processing.js @@ -11,7 +11,7 @@ $.extend(erpnext.bulk_transaction_processing, { }); let count_of_rows = checked_items.length; - frappe.confirm(__("Create {0} {1} ?", [count_of_rows, to_doctype]), ()=>{ + frappe.confirm(__("Create {0} {1} ?", [count_of_rows, __(to_doctype)]), ()=>{ if (doc_name.length == 0) { frappe.call({ method: "erpnext.utilities.bulk_transaction.transaction_processing", @@ -20,11 +20,11 @@ $.extend(erpnext.bulk_transaction_processing, { }); if (count_of_rows > 10) { - frappe.show_alert("Starting a background job to create {0} {1}", [count_of_rows, to_doctype]); + frappe.show_alert("Starting a background job to create {0} {1}", [count_of_rows, __(to_doctype)]); } } else { frappe.msgprint(__("Selected document must be in submitted state")); } }); } -}); \ No newline at end of file +}); From 105c2728169dbf2d186ba156a1cf4207c9c1411a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 8 Nov 2022 18:17:30 +0530 Subject: [PATCH 2/5] feat: Workstation Type for BOM --- .../doctype/bom_operation/bom_operation.json | 12 +- .../doctype/job_card/job_card.json | 22 ++- .../doctype/job_card/job_card.py | 23 ++- .../doctype/work_order/work_order.js | 1 - .../doctype/work_order/work_order.py | 15 +- .../work_order_operation.json | 10 +- .../doctype/workstation/workstation.json | 33 +++- .../doctype/workstation_type/__init__.py | 0 .../workstation_type/test_workstation_type.py | 9 ++ .../workstation_type/workstation_type.js | 8 + .../workstation_type/workstation_type.json | 146 ++++++++++++++++++ .../workstation_type/workstation_type.py | 15 ++ 12 files changed, 276 insertions(+), 18 deletions(-) create mode 100644 erpnext/manufacturing/doctype/workstation_type/__init__.py create mode 100644 erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py create mode 100644 erpnext/manufacturing/doctype/workstation_type/workstation_type.js create mode 100644 erpnext/manufacturing/doctype/workstation_type/workstation_type.json create mode 100644 erpnext/manufacturing/doctype/workstation_type/workstation_type.py diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index b965a435bf..5a734d8684 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -9,6 +9,7 @@ "sequence_id", "operation", "col_break1", + "workstation_type", "workstation", "time_in_mins", "fixed_time", @@ -40,9 +41,9 @@ "reqd": 1 }, { + "depends_on": "eval:!doc.workstation_type", "fieldname": "workstation", "fieldtype": "Link", - "in_list_view": 1, "label": "Workstation", "oldfieldname": "workstation", "oldfieldtype": "Link", @@ -180,13 +181,20 @@ "fieldname": "set_cost_based_on_bom_qty", "fieldtype": "Check", "label": "Set Operating Cost Based On BOM Quantity" + }, + { + "fieldname": "workstation_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Workstation Type", + "options": "Workstation Type" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-04-08 01:18:33.547481", + "modified": "2022-11-04 17:17:16.986941", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 5a071f1da6..85061113ce 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -27,11 +27,14 @@ "operation", "operation_row_number", "column_break_18", + "workstation_type", "workstation", "employee", "section_break_21", "sub_operations", "timing_detail", + "expected_start_date", + "expected_end_date", "time_logs", "section_break_13", "total_completed_qty", @@ -416,11 +419,27 @@ "fieldtype": "Link", "label": "Quality Inspection Template", "options": "Quality Inspection Template" + }, + { + "fieldname": "workstation_type", + "fieldtype": "Link", + "label": "Workstation Type", + "options": "Workstation Type" + }, + { + "fieldname": "expected_start_date", + "fieldtype": "Datetime", + "label": "Expected Start Date" + }, + { + "fieldname": "expected_end_date", + "fieldtype": "Datetime", + "label": "Expected End Date" } ], "is_submittable": 1, "links": [], - "modified": "2021-11-24 19:17:40.879235", + "modified": "2022-11-09 15:02:44.490731", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", @@ -475,6 +494,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "operation", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 17b77285c0..822647526f 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -2,6 +2,7 @@ # For license information, please see license.txt import datetime import json +from typing import Optional import frappe from frappe import _, bold @@ -26,6 +27,7 @@ from frappe.utils import ( from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import ( get_mins_between_operations, ) +from erpnext.manufacturing.doctype.workstation_type.workstation_type import get_workstations class OverlapError(frappe.ValidationError): @@ -129,7 +131,7 @@ class JobCard(Document): query = ( frappe.qb.from_(jctl) .from_(jc) - .select(jc.name.as_("name"), jctl.to_time) + .select(jc.name.as_("name"), jctl.to_time, jc.workstation, jc.workstation_type) .where( (jctl.parent == jc.name) & (Criterion.any(time_conditions)) @@ -140,6 +142,9 @@ class JobCard(Document): .orderby(jctl.to_time, order=frappe.qb.desc) ) + if self.workstation_type: + query = query.where(jc.workstation_type == self.workstation_type) + if self.workstation: production_capacity = ( frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1 @@ -156,8 +161,21 @@ class JobCard(Document): if existing and production_capacity > len(existing): return + if self.workstation_type: + if workstation := self.get_workstation_based_on_available_slot(existing): + self.workstation = workstation + return None + return existing[0] if existing else None + def get_workstation_based_on_available_slot(self, existing) -> Optional[str]: + workstations = get_workstations(self.workstation_type) + if workstations: + busy_workstations = [row.workstation for row in existing] + for workstation in workstations: + if workstation not in busy_workstations: + return workstation + def schedule_time_logs(self, row): row.remaining_time_in_mins = row.time_in_mins while row.remaining_time_in_mins > 0: @@ -170,6 +188,9 @@ class JobCard(Document): # get the last record based on the to time from the job card data = self.get_overlap_for(args, check_next_available_slot=True) if data: + if not self.workstation: + self.workstation = data.workstation + row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations()) def check_workstation_time(self, row): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 6247618248..4aff42cb73 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -446,7 +446,6 @@ frappe.ui.form.on("Work Order", { frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true); frm.toggle_reqd("transfer_material_against", frm.doc.operations && frm.doc.operations.length > 0); - frm.fields_dict.operations.grid.toggle_reqd("workstation", frm.doc.operations); }, set_sales_order: function(frm) { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 81673856f7..4dd95a1a33 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -87,11 +87,19 @@ class WorkOrder(Document): self.validate_transfer_against() self.validate_operation_time() self.status = self.get_status() + self.validate_workstation_type() validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"]) self.set_required_items(reset_only_qty=len(self.get("required_items"))) + def validate_workstation_type(self): + for row in self.operations: + if not row.workstation and not row.workstation_type: + frappe.throw( + _(f"Row {row.idx}: Workstation or Workstation Type is mandatory for {row.operation}") + ) + def validate_sales_order(self): if self.sales_order: self.check_sales_order_on_hold_or_close() @@ -491,11 +499,6 @@ class WorkOrder(Document): def prepare_data_for_job_card(self, row, index, plan_days, enable_capacity_planning): self.set_operation_start_end_time(index, row) - if not row.workstation: - frappe.throw( - _("Row {0}: select the workstation against the operation {1}").format(row.idx, row.operation) - ) - original_start_time = row.planned_start_time job_card_doc = create_job_card( self, row, auto_create=True, enable_capacity_planning=enable_capacity_planning @@ -662,6 +665,7 @@ class WorkOrder(Document): "description", "workstation", "idx", + "workstation_type", "base_hour_rate as hour_rate", "time_in_mins", "parent as bom", @@ -1398,6 +1402,7 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create doc.update( { "work_order": work_order.name, + "workstation_type": row.get("workstation_type"), "operation": row.get("operation"), "workstation": row.get("workstation"), "posting_date": nowdate(), diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json index 4e1a464cb0..31b920145e 100644 --- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json +++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json @@ -10,6 +10,7 @@ "completed_qty", "column_break_4", "bom", + "workstation_type", "workstation", "sequence_id", "section_break_10", @@ -196,12 +197,18 @@ { "fieldname": "section_break_10", "fieldtype": "Section Break" + }, + { + "fieldname": "workstation_type", + "fieldtype": "Link", + "label": "Workstation Type", + "options": "Workstation Type" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-11-29 16:37:18.824489", + "modified": "2022-11-09 01:37:56.563068", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Operation", @@ -209,5 +216,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index d130391cec..881cba0cce 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -1,26 +1,30 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:workstation_name", "creation": "2013-01-10 16:34:17", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "workstation_name", "production_capacity", "column_break_3", + "workstation_type", "over_heads", "hour_rate_electricity", "hour_rate_consumable", "column_break_11", "hour_rate_rent", "hour_rate_labour", + "section_break_11", "hour_rate", + "workstaion_description", + "description", "working_hours_section", "holiday_list", - "working_hours", - "workstaion_description", - "description" + "working_hours" ], "fields": [ { @@ -44,7 +48,7 @@ }, { "fieldname": "over_heads", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Operating Costs", "oldfieldtype": "Section Break" }, @@ -99,7 +103,7 @@ }, { "fieldname": "working_hours_section", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Working Hours" }, { @@ -128,16 +132,29 @@ { "collapsible": 1, "fieldname": "workstaion_description", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Description" + }, + { + "bold": 1, + "fieldname": "workstation_type", + "fieldtype": "Link", + "label": "Workstation Type", + "options": "Workstation Type" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" } ], "icon": "icon-wrench", "idx": 1, - "modified": "2019-11-26 12:39:19.742052", + "links": [], + "modified": "2022-11-04 17:39:01.549346", "modified_by": "Administrator", "module": "Manufacturing", "name": "Workstation", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { @@ -154,6 +171,8 @@ ], "quick_entry": 1, "show_name_in_global_search": 1, + "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation_type/__init__.py b/erpnext/manufacturing/doctype/workstation_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py b/erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py new file mode 100644 index 0000000000..9e7a54dbe3 --- /dev/null +++ b/erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestWorkstationType(FrappeTestCase): + pass diff --git a/erpnext/manufacturing/doctype/workstation_type/workstation_type.js b/erpnext/manufacturing/doctype/workstation_type/workstation_type.js new file mode 100644 index 0000000000..419fa6c10a --- /dev/null +++ b/erpnext/manufacturing/doctype/workstation_type/workstation_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Workstation Type', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/manufacturing/doctype/workstation_type/workstation_type.json b/erpnext/manufacturing/doctype/workstation_type/workstation_type.json new file mode 100644 index 0000000000..86321cf2ff --- /dev/null +++ b/erpnext/manufacturing/doctype/workstation_type/workstation_type.json @@ -0,0 +1,146 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:workstation_type", + "creation": "2022-11-04 17:03:23.334818", + "default_view": "List", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "workstation_type", + "over_heads", + "hour_rate_electricity", + "hour_rate_consumable", + "column_break_5", + "hour_rate_rent", + "hour_rate_labour", + "section_break_8", + "hour_rate", + "description_tab", + "description", + "holiday_tab", + "holiday_list" + ], + "fields": [ + { + "fieldname": "workstation_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Workstation Type", + "oldfieldname": "workstation_name", + "oldfieldtype": "Data", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "over_heads", + "fieldtype": "Section Break", + "label": "Operating Costs", + "oldfieldtype": "Section Break" + }, + { + "description": "per hour", + "fieldname": "hour_rate_electricity", + "fieldtype": "Currency", + "label": "Electricity Cost", + "oldfieldname": "hour_rate_electricity", + "oldfieldtype": "Currency" + }, + { + "description": "per hour", + "fieldname": "hour_rate_consumable", + "fieldtype": "Currency", + "label": "Consumable Cost", + "oldfieldname": "hour_rate_consumable", + "oldfieldtype": "Currency" + }, + { + "description": "per hour", + "fieldname": "hour_rate_rent", + "fieldtype": "Currency", + "label": "Rent Cost", + "oldfieldname": "hour_rate_rent", + "oldfieldtype": "Currency" + }, + { + "description": "Wages per hour", + "fieldname": "hour_rate_labour", + "fieldtype": "Currency", + "label": "Wages", + "oldfieldname": "hour_rate_labour", + "oldfieldtype": "Currency" + }, + { + "description": "per hour", + "fieldname": "hour_rate", + "fieldtype": "Currency", + "label": "Net Hour Rate", + "oldfieldname": "hour_rate", + "oldfieldtype": "Currency", + "read_only": 1 + }, + { + "fieldname": "holiday_list", + "fieldtype": "Link", + "label": "Holiday List", + "options": "Holiday List" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "width": "300px" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "fieldname": "description_tab", + "fieldtype": "Tab Break", + "label": "Description" + }, + { + "fieldname": "holiday_tab", + "fieldtype": "Tab Break", + "label": "Holiday" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" + } + ], + "icon": "icon-wrench", + "links": [], + "modified": "2022-11-04 17:30:33.397719", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Workstation Type", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "ASC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation_type/workstation_type.py b/erpnext/manufacturing/doctype/workstation_type/workstation_type.py new file mode 100644 index 0000000000..b097755abb --- /dev/null +++ b/erpnext/manufacturing/doctype/workstation_type/workstation_type.py @@ -0,0 +1,15 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + + +class WorkstationType(Document): + pass + + +def get_workstations(workstation_type): + workstations = frappe.get_all("Workstation", filters={"workstation_type": workstation_type}) + + return [workstation.name for workstation in workstations] From 33b61aef5acd10cf8d690e7aef342916f7d44e6f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Nov 2022 11:57:17 +0530 Subject: [PATCH 3/5] fix: Opening journal entry templates --- .../journal_entry_template.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js index cf5fbe12af..88f1c9069c 100644 --- a/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js +++ b/erpnext/accounts/doctype/journal_entry_template/journal_entry_template.js @@ -45,21 +45,6 @@ frappe.ui.form.on("Journal Entry Template", { frm.trigger("clear_child"); switch(frm.doc.voucher_type){ - case "Opening Entry": - frm.set_value("is_opening", "Yes"); - frappe.call({ - type:"GET", - method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts", - args: { - "company": frm.doc.company - }, - callback: function(r) { - if(r.message) { - add_accounts(frm.doc, r.message); - } - } - }); - break; case "Bank Entry": case "Cash Entry": frappe.call({ From 7bd06e6fbc30ebd92b18055983e3b88fa9545e2a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 14 Nov 2022 12:58:17 +0530 Subject: [PATCH 4/5] test: test case to check workstation type --- .../doctype/work_order/test_work_order.py | 162 ++++++++- .../doctype/workstation/test_workstation.py | 1 + .../doctype/workstation/workstation.js | 14 +- .../doctype/workstation/workstation.py | 30 +- .../workstation_type/test_workstation_type.py | 14 +- .../workstation_type/workstation_type.json | 17 +- .../workstation_type/workstation_type.py | 12 +- .../manufacturing/manufacturing.json | 336 +++++++++--------- 8 files changed, 403 insertions(+), 183 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 804f03dc51..694dc79d4a 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 6db985c8c2..1eb47ae577 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 5b9cedb6f9..f830b170ed 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 3c256221be..d5b6d37d67 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 9e7a54dbe3..aa7a3ee92f 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 86321cf2ff..7d9e36abb4 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 b097755abb..348f4f8a16 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 549f5afc70..c25f606060 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", From ffa30127e7e449de7ef88316f7d83339b91a98bb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 16 Nov 2022 23:23:06 +0530 Subject: [PATCH 5/5] fix: linters failing --- erpnext/manufacturing/doctype/work_order/work_order.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 4dd95a1a33..52753a092d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -96,9 +96,8 @@ class WorkOrder(Document): def validate_workstation_type(self): for row in self.operations: if not row.workstation and not row.workstation_type: - frappe.throw( - _(f"Row {row.idx}: Workstation or Workstation Type is mandatory for {row.operation}") - ) + msg = f"Row {row.idx}: Workstation or Workstation Type is mandatory for an operation {row.operation}" + frappe.throw(_(msg)) def validate_sales_order(self): if self.sales_order: