feat: Workstation Type for BOM
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index b965a43..5a734d8 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 5a071f1..8506111 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 17b7728..8226475 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 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 @@
 		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 @@
 			.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 @@
 		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 @@
 		# 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 6247618..4aff42c 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -446,7 +446,6 @@
 		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 8167385..4dd95a1 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -87,11 +87,19 @@
 		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 @@
 	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 @@
 					"description",
 					"workstation",
 					"idx",
+					"workstation_type",
 					"base_hour_rate as hour_rate",
 					"time_in_mins",
 					"parent as bom",
@@ -1398,6 +1402,7 @@
 	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 4e1a464..31b9201 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 d130391..881cba0 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 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/workstation_type/__init__.py
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 0000000..9e7a54d
--- /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 0000000..419fa6c
--- /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 0000000..86321cf
--- /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 0000000..b097755
--- /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]