refactor: separate table added to track scheduling in the job card
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 5d912fa..0f01704 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -9,39 +9,40 @@
   "naming_series",
   "work_order",
   "bom_no",
+  "production_item",
+  "employee",
   "column_break_4",
   "posting_date",
   "company",
-  "production_section",
-  "production_item",
-  "item_name",
   "for_quantity",
-  "serial_and_batch_bundle",
-  "serial_no",
-  "column_break_12",
-  "wip_warehouse",
-  "quality_inspection_template",
-  "quality_inspection",
-  "project",
-  "batch_no",
-  "operation_section_section",
-  "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",
   "process_loss_qty",
-  "column_break_15",
+  "scheduled_time_section",
+  "expected_start_date",
+  "time_required",
+  "column_break_jkir",
+  "expected_end_date",
+  "section_break_05am",
+  "scheduled_time_logs",
+  "timing_detail",
+  "time_logs",
+  "section_break_13",
+  "actual_start_date",
   "total_time_in_mins",
+  "column_break_15",
+  "actual_end_date",
+  "production_section",
+  "operation",
+  "wip_warehouse",
+  "column_break_12",
+  "workstation_type",
+  "workstation",
+  "quality_inspection_section",
+  "quality_inspection_template",
+  "column_break_fcmp",
+  "quality_inspection",
+  "section_break_21",
+  "sub_operations",
   "section_break_8",
   "items",
   "scrap_items_section",
@@ -53,18 +54,25 @@
   "hour_rate",
   "for_operation",
   "more_information",
-  "operation_id",
-  "sequence_id",
+  "project",
+  "item_name",
   "transferred_qty",
   "requested_qty",
   "status",
   "column_break_20",
+  "operation_row_number",
+  "operation_id",
+  "sequence_id",
   "remarks",
+  "serial_and_batch_bundle",
+  "batch_no",
+  "serial_no",
   "barcode",
   "job_started",
   "started_time",
   "current_time",
-  "amended_from"
+  "amended_from",
+  "connections_tab"
  ],
  "fields": [
   {
@@ -134,7 +142,7 @@
   {
    "fieldname": "timing_detail",
    "fieldtype": "Section Break",
-   "label": "Timing Detail"
+   "label": "Actual Time"
   },
   {
    "allow_bulk_edit": 1,
@@ -167,7 +175,7 @@
   },
   {
    "fieldname": "section_break_8",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Raw Materials"
   },
   {
@@ -179,7 +187,7 @@
   {
    "collapsible": 1,
    "fieldname": "more_information",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "More Information"
   },
   {
@@ -264,10 +272,9 @@
    "reqd": 1
   },
   {
-   "collapsible": 1,
    "fieldname": "production_section",
-   "fieldtype": "Section Break",
-   "label": "Production"
+   "fieldtype": "Tab Break",
+   "label": "Operation & Workstation"
   },
   {
    "fieldname": "column_break_12",
@@ -332,18 +339,10 @@
    "read_only": 1
   },
   {
-   "fieldname": "operation_section_section",
-   "fieldtype": "Section Break",
-   "label": "Operation Section"
-  },
-  {
-   "fieldname": "column_break_18",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "section_break_21",
-   "fieldtype": "Section Break",
-   "hide_border": 1
+   "fieldtype": "Tab Break",
+   "hide_border": 1,
+   "label": "Sub Operations"
   },
   {
    "depends_on": "is_corrective_job_card",
@@ -355,7 +354,7 @@
    "collapsible": 1,
    "depends_on": "is_corrective_job_card",
    "fieldname": "corrective_operation_section",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Corrective Operation"
   },
   {
@@ -408,7 +407,7 @@
   {
    "collapsible": 1,
    "fieldname": "scrap_items_section",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Scrap Items"
   },
   {
@@ -451,15 +450,68 @@
    "print_hide": 1
   },
   {
+   "depends_on": "process_loss_qty",
    "fieldname": "process_loss_qty",
    "fieldtype": "Float",
    "label": "Process Loss Qty",
    "read_only": 1
+  },
+  {
+   "fieldname": "connections_tab",
+   "fieldtype": "Tab Break",
+   "label": "Connections",
+   "show_dashboard": 1
+  },
+  {
+   "fieldname": "scheduled_time_section",
+   "fieldtype": "Section Break",
+   "label": "Scheduled Time"
+  },
+  {
+   "fieldname": "column_break_jkir",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "time_required",
+   "fieldtype": "Float",
+   "label": "Expected Time Required (In Mins)"
+  },
+  {
+   "fieldname": "section_break_05am",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "scheduled_time_logs",
+   "fieldtype": "Table",
+   "label": "Scheduled Time Logs",
+   "options": "Job Card Scheduled Time",
+   "read_only": 1
+  },
+  {
+   "fieldname": "actual_start_date",
+   "fieldtype": "Datetime",
+   "label": "Actual Start Date",
+   "read_only": 1
+  },
+  {
+   "fieldname": "actual_end_date",
+   "fieldtype": "Datetime",
+   "label": "Actual End Date",
+   "read_only": 1
+  },
+  {
+   "fieldname": "quality_inspection_section",
+   "fieldtype": "Section Break",
+   "label": "Quality Inspection"
+  },
+  {
+   "fieldname": "column_break_fcmp",
+   "fieldtype": "Column Break"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2023-06-09 12:04:55.534264",
+ "modified": "2023-06-28 19:23:14.345214",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 2c17568..80bdfd5 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -2,7 +2,7 @@
 # For license information, please see license.txt
 import datetime
 import json
-from typing import Optional
+from collections import OrderedDict
 
 import frappe
 from frappe import _, bold
@@ -164,10 +164,40 @@
 			self.total_completed_qty += row.completed_qty
 
 	def get_overlap_for(self, args, check_next_available_slot=False):
-		production_capacity = 1
+		time_logs = []
 
+		time_logs.extend(self.get_time_logs(args, "Job Card Time Log", check_next_available_slot))
+
+		time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time", check_next_available_slot))
+
+		if not time_logs:
+			return {}
+
+		time_logs = sorted(time_logs, key=lambda x: x.get("to_time"))
+
+		production_capacity = 1
+		if self.workstation:
+			production_capacity = (
+				frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
+			)
+
+		if args.get("employee"):
+			# override capacity for employee
+			production_capacity = 1
+
+		if time_logs and production_capacity > len(time_logs):
+			return {}
+
+		if self.workstation_type and time_logs:
+			if workstation_time := self.get_workstation_based_on_available_slot(time_logs):
+				self.workstation = workstation_time.get("workstation")
+				return workstation_time
+
+		return time_logs[-1]
+
+	def get_time_logs(self, args, doctype, check_next_available_slot=False):
 		jc = frappe.qb.DocType("Job Card")
-		jctl = frappe.qb.DocType("Job Card Time Log")
+		jctl = frappe.qb.DocType(doctype)
 
 		time_conditions = [
 			((jctl.from_time < args.from_time) & (jctl.to_time > args.from_time)),
@@ -181,7 +211,7 @@
 		query = (
 			frappe.qb.from_(jctl)
 			.from_(jc)
-			.select(jc.name.as_("name"), jctl.to_time, jc.workstation, jc.workstation_type)
+			.select(jc.name.as_("name"), jctl.from_time, jctl.to_time, jc.workstation, jc.workstation_type)
 			.where(
 				(jctl.parent == jc.name)
 				& (Criterion.any(time_conditions))
@@ -189,42 +219,51 @@
 				& (jc.name != f"{args.parent or 'No Name'}")
 				& (jc.docstatus < 2)
 			)
-			.orderby(jctl.to_time, order=frappe.qb.desc)
+			.orderby(jctl.to_time)
 		)
 
 		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
-			)
 			query = query.where(jc.workstation == self.workstation)
 
-		if args.get("employee"):
-			# override capacity for employee
-			production_capacity = 1
+		if args.get("employee") and doctype == "Job Card Time Log":
 			query = query.where(jctl.employee == args.get("employee"))
 
-		existing = query.run(as_dict=True)
+		if doctype != "Job Card Time Log":
+			query = query.where(jc.total_time_in_mins == 0)
 
-		if existing and production_capacity > len(existing):
-			return
+		time_logs = query.run(as_dict=True)
 
-		if self.workstation_type:
-			if workstation := self.get_workstation_based_on_available_slot(existing):
-				self.workstation = workstation
-				return None
+		return time_logs
 
-		return existing[0] if existing else None
-
-	def get_workstation_based_on_available_slot(self, existing) -> Optional[str]:
+	def get_workstation_based_on_available_slot(self, existing_time_logs) -> dict:
 		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
+			busy_workstations = self.time_slot_wise_busy_workstations(existing_time_logs)
+			for time_slot in busy_workstations:
+				available_workstations = sorted(list(set(workstations) - set(busy_workstations[time_slot])))
+				if available_workstations:
+					return frappe._dict(
+						{
+							"workstation": available_workstations[0],
+							"planned_start_time": get_datetime(time_slot[0]),
+							"to_time": get_datetime(time_slot[1]),
+						}
+					)
+
+		return frappe._dict({})
+
+	@staticmethod
+	def time_slot_wise_busy_workstations(existing_time_logs) -> dict:
+		time_slot = OrderedDict()
+		for row in existing_time_logs:
+			from_time = get_datetime(row.from_time).strftime("%Y-%m-%d %H:%M")
+			to_time = get_datetime(row.to_time).strftime("%Y-%m-%d %H:%M")
+			time_slot.setdefault((from_time, to_time), []).append(row.workstation)
+
+		return time_slot
 
 	def schedule_time_logs(self, row):
 		row.remaining_time_in_mins = row.time_in_mins
@@ -237,11 +276,17 @@
 	def validate_overlap_for_workstation(self, args, row):
 		# 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
+		if not self.workstation:
+			workstations = get_workstations(self.workstation_type)
+			if workstations:
+				# Get the first workstation
+				self.workstation = workstations[0]
 
-			row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
+		if data:
+			if data.get("planned_start_time"):
+				row.planned_start_time = get_datetime(data.planned_start_time)
+			else:
+				row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
 
 	def check_workstation_time(self, row):
 		workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
@@ -410,7 +455,7 @@
 
 	def update_time_logs(self, row):
 		self.append(
-			"time_logs",
+			"scheduled_time_logs",
 			{
 				"from_time": row.planned_start_time,
 				"to_time": row.planned_end_time,
@@ -452,6 +497,7 @@
 				)
 
 	def before_save(self):
+		self.set_expected_and_actual_time()
 		self.set_process_loss()
 
 	def on_submit(self):
@@ -510,6 +556,32 @@
 				)
 			)
 
+	def set_expected_and_actual_time(self):
+		for child_table, start_field, end_field, time_required in [
+			("scheduled_time_logs", "expected_start_date", "expected_end_date", "time_required"),
+			("time_logs", "actual_start_date", "actual_end_date", "total_time_in_mins"),
+		]:
+			if not self.get(child_table):
+				continue
+
+			time_list = []
+			time_in_mins = 0.0
+			for row in self.get(child_table):
+				time_in_mins += flt(row.get("time_in_mins"))
+				for field in ["from_time", "to_time"]:
+					if row.get(field):
+						time_list.append(get_datetime(row.get(field)))
+
+			if time_list:
+				self.set(start_field, min(time_list))
+				if end_field == "actual_end_date" and not self.time_logs[-1].to_time:
+					self.set(end_field, "")
+					return
+
+				self.set(end_field, max(time_list))
+
+			self.set(time_required, time_in_mins)
+
 	def set_process_loss(self):
 		precision = self.precision("total_completed_qty")
 
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index e7fbcda..bde0548 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -541,6 +541,16 @@
 		)[0].name
 
 		jc = frappe.get_doc("Job Card", first_job_card)
+		for row in jc.scheduled_time_logs:
+			jc.append(
+				"time_logs",
+				{
+					"from_time": row.from_time,
+					"to_time": row.to_time,
+					"time_in_mins": row.time_in_mins,
+				},
+			)
+
 		jc.time_logs[0].completed_qty = 8
 		jc.save()
 		jc.submit()
@@ -557,11 +567,30 @@
 		)[0].name
 
 		jc2 = frappe.get_doc("Job Card", second_job_card)
+		for row in jc2.scheduled_time_logs:
+			jc2.append(
+				"time_logs",
+				{
+					"from_time": row.from_time,
+					"to_time": row.to_time,
+					"time_in_mins": row.time_in_mins,
+				},
+			)
 		jc2.time_logs[0].completed_qty = 10
 
 		self.assertRaises(frappe.ValidationError, jc2.save)
 
 		jc2.load_from_db()
+		for row in jc2.scheduled_time_logs:
+			jc2.append(
+				"time_logs",
+				{
+					"from_time": row.from_time,
+					"to_time": row.to_time,
+					"time_in_mins": row.time_in_mins,
+				},
+			)
+
 		jc2.time_logs[0].completed_qty = 8
 		jc2.save()
 		jc2.submit()
diff --git a/erpnext/manufacturing/doctype/job_card_scheduled_time/__init__.py b/erpnext/manufacturing/doctype/job_card_scheduled_time/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_scheduled_time/__init__.py
diff --git a/erpnext/manufacturing/doctype/job_card_scheduled_time/job_card_scheduled_time.json b/erpnext/manufacturing/doctype/job_card_scheduled_time/job_card_scheduled_time.json
new file mode 100644
index 0000000..522cfa3
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_scheduled_time/job_card_scheduled_time.json
@@ -0,0 +1,45 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-06-14 15:23:54.673262",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "from_time",
+  "to_time",
+  "time_in_mins"
+ ],
+ "fields": [
+  {
+   "fieldname": "from_time",
+   "fieldtype": "Datetime",
+   "in_list_view": 1,
+   "label": "From Time"
+  },
+  {
+   "fieldname": "to_time",
+   "fieldtype": "Datetime",
+   "in_list_view": 1,
+   "label": "To Time"
+  },
+  {
+   "fieldname": "time_in_mins",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Time (In Mins)"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-06-14 15:27:03.203045",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Scheduled Time",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card_scheduled_time/job_card_scheduled_time.py b/erpnext/manufacturing/doctype/job_card_scheduled_time/job_card_scheduled_time.py
new file mode 100644
index 0000000..e50b153
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_scheduled_time/job_card_scheduled_time.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class JobCardScheduledTime(Document):
+	pass
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index a37ff28..e069aea 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -38,6 +38,16 @@
 			"Job Card", filters={"work_order": wo_doc.name}, order_by="sequence_id desc"
 		):
 			job_card_doc = frappe.get_doc("Job Card", data.name)
+			for row in job_card_doc.scheduled_time_logs:
+				job_card_doc.append(
+					"time_logs",
+					{
+						"from_time": row.from_time,
+						"to_time": row.to_time,
+						"time_in_mins": row.time_in_mins,
+					},
+				)
+
 			job_card_doc.time_logs[0].completed_qty = 10
 			if job_card_doc.sequence_id != 1:
 				self.assertRaises(OperationSequenceError, job_card_doc.save)
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 690fe47..c828c87 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -487,6 +487,16 @@
 
 		for i, job_card in enumerate(job_cards):
 			doc = frappe.get_doc("Job Card", job_card)
+			for row in doc.scheduled_time_logs:
+				doc.append(
+					"time_logs",
+					{
+						"from_time": row.from_time,
+						"to_time": row.to_time,
+						"time_in_mins": row.time_in_mins,
+					},
+				)
+
 			doc.time_logs[0].completed_qty = 1
 			doc.submit()
 
@@ -957,7 +967,7 @@
 			item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1
 		)
 		job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
-		update_job_card(job_card, 10)
+		update_job_card(job_card, 10, 1)
 
 		stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
 		for row in stock_entry.items:
@@ -975,7 +985,7 @@
 
 		make_job_card(wo_order.name, operations)
 		job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name, "docstatus": 0}, "name")
-		update_job_card(job_card, 10)
+		update_job_card(job_card, 10, 2)
 
 		stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
 		for row in stock_entry.items:
@@ -1671,9 +1681,32 @@
 		)
 		job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
 		job_card_doc = frappe.get_doc("Job Card", job_card)
+		for row in job_card_doc.scheduled_time_logs:
+			job_card_doc.append(
+				"time_logs",
+				{
+					"from_time": row.from_time,
+					"to_time": row.to_time,
+					"time_in_mins": row.time_in_mins,
+					"completed_qty": 20,
+				},
+			)
+
+		job_card_doc.save()
 
 		# Make another Job Card for the same Work Order
 		job_card2 = frappe.copy_doc(job_card_doc)
+		job_card2.append(
+			"time_logs",
+			{
+				"from_time": row.from_time,
+				"to_time": row.to_time,
+				"time_in_mins": row.time_in_mins,
+			},
+		)
+
+		job_card2.time_logs[0].completed_qty = 20
+
 		self.assertRaises(frappe.ValidationError, job_card2.save)
 
 		frappe.db.set_single_value(
@@ -1841,7 +1874,7 @@
 	make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_batch_item_doc.name])
 
 
-def update_job_card(job_card, jc_qty=None):
+def update_job_card(job_card, jc_qty=None, days=None):
 	employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
 	job_card_doc = frappe.get_doc("Job Card", job_card)
 	job_card_doc.set(
@@ -1855,15 +1888,32 @@
 	if jc_qty:
 		job_card_doc.for_quantity = jc_qty
 
-	job_card_doc.append(
-		"time_logs",
-		{
-			"from_time": now(),
-			"employee": employee,
-			"time_in_mins": 60,
-			"completed_qty": job_card_doc.for_quantity,
-		},
-	)
+	for row in job_card_doc.scheduled_time_logs:
+		job_card_doc.append(
+			"time_logs",
+			{
+				"from_time": row.from_time,
+				"to_time": row.to_time,
+				"employee": employee,
+				"time_in_mins": 60,
+				"completed_qty": 0.0,
+			},
+		)
+
+	if not job_card_doc.time_logs and days:
+		planned_start_time = add_days(now(), days=days)
+		job_card_doc.append(
+			"time_logs",
+			{
+				"from_time": planned_start_time,
+				"to_time": add_to_date(planned_start_time, minutes=60),
+				"employee": employee,
+				"time_in_mins": 60,
+				"completed_qty": 0.0,
+			},
+		)
+
+	job_card_doc.time_logs[0].completed_qty = job_card_doc.for_quantity
 
 	job_card_doc.submit()
 
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index bfdcf61..79b1e79 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -519,8 +519,8 @@
 		)
 
 		if enable_capacity_planning and job_card_doc:
-			row.planned_start_time = job_card_doc.time_logs[-1].from_time
-			row.planned_end_time = job_card_doc.time_logs[-1].to_time
+			row.planned_start_time = job_card_doc.scheduled_time_logs[-1].from_time
+			row.planned_end_time = job_card_doc.scheduled_time_logs[-1].to_time
 
 			if date_diff(row.planned_start_time, original_start_time) > plan_days:
 				frappe.message_log.pop()
diff --git a/erpnext/manufacturing/doctype/workstation_type/workstation_type.py b/erpnext/manufacturing/doctype/workstation_type/workstation_type.py
index 348f4f8..8c1e230 100644
--- a/erpnext/manufacturing/doctype/workstation_type/workstation_type.py
+++ b/erpnext/manufacturing/doctype/workstation_type/workstation_type.py
@@ -20,6 +20,8 @@
 
 
 def get_workstations(workstation_type):
-	workstations = frappe.get_all("Workstation", filters={"workstation_type": workstation_type})
+	workstations = frappe.get_all(
+		"Workstation", filters={"workstation_type": workstation_type}, order_by="creation"
+	)
 
 	return [workstation.name for workstation in workstations]