feat(Healthcare): Capacity for Service Unit, concurrent appointments based on capacity, Patient Appointments (#27219)

* feat(Healthcare): Capacity for Service Unit, concurrent appointments based on Capacity, Patient enhancements

* fix: appointment test

Co-authored-by: Anoop <3326959+akurungadam@users.noreply.github.com>
diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
index 81a3982..0326e5e 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
@@ -11,7 +11,7 @@
 
 class TestClinicalProcedure(unittest.TestCase):
 	def test_procedure_template_item(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		procedure_template = create_clinical_procedure_template()
 		self.assertTrue(frappe.db.exists('Item', procedure_template.item))
 
@@ -20,7 +20,7 @@
 		self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
 
 	def test_consumables(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		procedure_template = create_clinical_procedure_template()
 		procedure_template.allow_stock_consumption = 1
 		consumable = create_consumable()
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index 4a17872..957f852 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -27,7 +27,7 @@
 		healthcare_settings.automate_appointment_invoicing = 1
 		healthcare_settings.op_consulting_charge_item = item
 		healthcare_settings.save(ignore_permissions=True)
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 
 		# For first appointment, invoice is generated. First appointment not considered in fee validity
 		appointment = create_appointment(patient, practitioner, nowdate())
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js
index 2cdd550..2d1caf7 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js
@@ -7,8 +7,8 @@
 
 		// get query select healthcare service unit
 		frm.fields_dict['parent_healthcare_service_unit'].get_query = function(doc) {
-			return{
-				filters:[
+			return {
+				filters: [
 					['Healthcare Service Unit', 'is_group', '=', 1],
 					['Healthcare Service Unit', 'name', '!=', doc.healthcare_service_unit_name]
 				]
@@ -21,6 +21,14 @@
 		frm.add_custom_button(__('Healthcare Service Unit Tree'), function() {
 			frappe.set_route('Tree', 'Healthcare Service Unit');
 		});
+
+		frm.set_query('warehouse', function() {
+			return {
+				filters: {
+					'company': frm.doc.company
+				}
+			};
+		});
 	},
 	set_root_readonly: function(frm) {
 		// read-only for root healthcare service unit
@@ -43,5 +51,10 @@
 		else {
 			frm.set_df_property('service_unit_type', 'reqd', 1);
 		}
+	},
+	overlap_appointments: function(frm) {
+		if (frm.doc.overlap_appointments == 0) {
+			frm.set_value('service_unit_capacity', '');
+		}
 	}
 });
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
index 9ee865a..8935ec7 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
@@ -16,6 +16,7 @@
   "service_unit_type",
   "allow_appointments",
   "overlap_appointments",
+  "service_unit_capacity",
   "inpatient_occupancy",
   "occupancy_status",
   "column_break_9",
@@ -31,6 +32,8 @@
   {
    "fieldname": "healthcare_service_unit_name",
    "fieldtype": "Data",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_global_search": 1,
    "in_list_view": 1,
    "label": "Service Unit",
@@ -41,6 +44,8 @@
    "bold": 1,
    "fieldname": "parent_healthcare_service_unit",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "ignore_user_permissions": 1,
    "in_list_view": 1,
    "label": "Parent Service Unit",
@@ -52,6 +57,8 @@
    "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1",
    "fieldname": "is_group",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Is Group"
   },
   {
@@ -59,6 +66,8 @@
    "depends_on": "eval:doc.is_group != 1",
    "fieldname": "service_unit_type",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Service Unit Type",
    "options": "Healthcare Service Unit Type"
   },
@@ -68,6 +77,8 @@
    "fetch_from": "service_unit_type.allow_appointments",
    "fieldname": "allow_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_list_view": 1,
    "label": "Allow Appointments",
    "no_copy": 1,
@@ -79,6 +90,8 @@
    "fetch_from": "service_unit_type.overlap_appointments",
    "fieldname": "overlap_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Allow Overlap",
    "no_copy": 1,
    "read_only": 1
@@ -90,6 +103,8 @@
    "fetch_from": "service_unit_type.inpatient_occupancy",
    "fieldname": "inpatient_occupancy",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_list_view": 1,
    "label": "Inpatient Occupancy",
    "no_copy": 1,
@@ -100,6 +115,8 @@
    "depends_on": "eval:doc.inpatient_occupancy == 1",
    "fieldname": "occupancy_status",
    "fieldtype": "Select",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Occupancy Status",
    "no_copy": 1,
    "options": "Vacant\nOccupied",
@@ -107,13 +124,17 @@
   },
   {
    "fieldname": "column_break_9",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "hide_days": 1,
+   "hide_seconds": 1
   },
   {
    "bold": 1,
    "depends_on": "eval:doc.is_group != 1",
    "fieldname": "warehouse",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Warehouse",
    "no_copy": 1,
    "options": "Warehouse"
@@ -121,6 +142,8 @@
   {
    "fieldname": "company",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "ignore_user_permissions": 1,
    "in_list_view": 1,
    "in_standard_filter": 1,
@@ -134,6 +157,8 @@
    "fieldname": "lft",
    "fieldtype": "Int",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "lft",
    "no_copy": 1,
    "print_hide": 1,
@@ -143,6 +168,8 @@
    "fieldname": "rgt",
    "fieldtype": "Int",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "rgt",
    "no_copy": 1,
    "print_hide": 1,
@@ -152,6 +179,8 @@
    "fieldname": "old_parent",
    "fieldtype": "Link",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "ignore_user_permissions": 1,
    "label": "Old Parent",
    "no_copy": 1,
@@ -163,14 +192,26 @@
    "collapsible": 1,
    "fieldname": "tree_details_section",
    "fieldtype": "Section Break",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Tree Details"
+  },
+  {
+   "depends_on": "eval:doc.overlap_appointments == 1",
+   "fieldname": "service_unit_capacity",
+   "fieldtype": "Int",
+   "label": "Service Unit Capacity",
+   "mandatory_depends_on": "eval:doc.overlap_appointments == 1",
+   "non_negative": 1
   }
  ],
+ "is_tree": 1,
  "links": [],
- "modified": "2020-05-20 18:26:56.065543",
+ "modified": "2021-08-19 14:09:11.643464",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Service Unit",
+ "nsm_parent_field": "parent_healthcare_service_unit",
  "owner": "Administrator",
  "permissions": [
   {
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
index 9e0417a..989d426 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
@@ -5,14 +5,21 @@
 from __future__ import unicode_literals
 
 from frappe.utils.nestedset import NestedSet
+from frappe.utils import cint, cstr
 import frappe
+from frappe import _
+import json
+
 
 class HealthcareServiceUnit(NestedSet):
 	nsm_parent_field = 'parent_healthcare_service_unit'
 
+	def validate(self):
+		self.set_service_unit_properties()
+
 	def autoname(self):
 		if self.company:
-			suffix = " - " + frappe.get_cached_value('Company',  self.company,  "abbr")
+			suffix = " - " + frappe.get_cached_value('Company', self.company, 'abbr')
 			if not self.healthcare_service_unit_name.endswith(suffix):
 				self.name = self.healthcare_service_unit_name + suffix
 		else:
@@ -22,16 +29,86 @@
 		super(HealthcareServiceUnit, self).on_update()
 		self.validate_one_root()
 
-	def after_insert(self):
+	def set_service_unit_properties(self):
 		if self.is_group:
-			self.allow_appointments = 0
-			self.overlap_appointments = 0
-			self.inpatient_occupancy = 0
-		elif self.service_unit_type:
+			self.allow_appointments = False
+			self.overlap_appointments = False
+			self.inpatient_occupancy = False
+			self.service_unit_capacity = 0
+			self.occupancy_status = ''
+			self.service_unit_type = ''
+		elif self.service_unit_type != '':
 			service_unit_type = frappe.get_doc('Healthcare Service Unit Type', self.service_unit_type)
 			self.allow_appointments = service_unit_type.allow_appointments
-			self.overlap_appointments = service_unit_type.overlap_appointments
 			self.inpatient_occupancy = service_unit_type.inpatient_occupancy
-			if self.inpatient_occupancy:
+
+			if self.inpatient_occupancy and self.occupancy_status != '':
 				self.occupancy_status = 'Vacant'
-				self.overlap_appointments = 0
+
+			if service_unit_type.overlap_appointments:
+				self.overlap_appointments = True
+			else:
+				self.overlap_appointments = False
+				self.service_unit_capacity = 0
+
+		if self.overlap_appointments:
+			if not self.service_unit_capacity:
+				frappe.throw(_('Please set a valid Service Unit Capacity to enable Overlapping Appointments'),
+					title=_('Mandatory'))
+
+
+@frappe.whitelist()
+def add_multiple_service_units(parent, data):
+	'''
+	parent - parent service unit under which the service units are to be created
+	data (dict) - company, healthcare_service_unit_name, count, service_unit_type, warehouse, service_unit_capacity
+	'''
+	if not parent or not data:
+		return
+
+	data = json.loads(data)
+	company = data.get('company') or \
+		frappe.defaults.get_defaults().get('company') or \
+		frappe.db.get_single_value('Global Defaults', 'default_company')
+
+	if not data.get('healthcare_service_unit_name') or not company:
+		frappe.throw(_('Service Unit Name and Company are mandatory to create Healthcare Service Units'),
+			title=_('Missing Required Fields'))
+
+	count = cint(data.get('count') or 0)
+	if count <= 0:
+		frappe.throw(_('Number of Service Units to be created should at least be 1'),
+			title=_('Invalid Number of Service Units'))
+
+	capacity = cint(data.get('service_unit_capacity') or 1)
+
+	service_unit = {
+		'doctype': 'Healthcare Service Unit',
+		'parent_healthcare_service_unit': parent,
+		'service_unit_type': data.get('service_unit_type') or None,
+		'service_unit_capacity': capacity if capacity > 0 else 1,
+		'warehouse': data.get('warehouse') or None,
+		'company': company
+	}
+
+	service_unit_name = '{}'.format(data.get('healthcare_service_unit_name').strip(' -'))
+
+	last_suffix = frappe.db.sql("""SELECT
+		IFNULL(MAX(CAST(SUBSTRING(name FROM %(start)s FOR 4) AS UNSIGNED)), 0)
+		FROM `tabHealthcare Service Unit`
+		WHERE name like %(prefix)s AND company=%(company)s""",
+		{'start': len(service_unit_name)+2, 'prefix': '{}-%'.format(service_unit_name), 'company': company},
+		as_list=1)[0][0]
+	start_suffix = cint(last_suffix) + 1
+
+	failed_list = []
+	for i in range(start_suffix, count + start_suffix):
+		# name to be in the form WARD-####
+		service_unit['healthcare_service_unit_name'] = '{}-{}'.format(service_unit_name, cstr('%0*d' % (4, i)))
+		service_unit_doc = frappe.get_doc(service_unit)
+		try:
+			service_unit_doc.insert()
+		except Exception:
+			failed_list.append(service_unit['healthcare_service_unit_name'])
+
+	return failed_list
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
index b75f271..ea3fea6 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
@@ -1,35 +1,185 @@
-frappe.treeview_settings["Healthcare Service Unit"] = {
-	breadcrumbs: "Healthcare Service Unit",
-	title: __("Healthcare Service Unit"),
+frappe.provide("frappe.treeview_settings");
+
+frappe.treeview_settings['Healthcare Service Unit'] = {
+	breadcrumbs: 'Healthcare Service Unit',
+	title: __('Service Unit Tree'),
 	get_tree_root: false,
-	filters: [{
-		fieldname: "company",
-		fieldtype: "Select",
-		options: erpnext.utils.get_tree_options("company"),
-		label: __("Company"),
-		default: erpnext.utils.get_tree_default("company")
-	}],
 	get_tree_nodes: 'erpnext.healthcare.utils.get_children',
-	ignore_fields:["parent_healthcare_service_unit"],
-	onrender: function(node) {
-		if (node.data.occupied_out_of_vacant!==undefined) {
-			$('<span class="balance-area pull-right">'
-				+ " " + node.data.occupied_out_of_vacant
+	filters: [{
+		fieldname: 'company',
+		fieldtype: 'Select',
+		options: erpnext.utils.get_tree_options('company'),
+		label: __('Company'),
+		default: erpnext.utils.get_tree_default('company')
+	}],
+	fields: [
+		{
+			fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('New Service Unit Name'),
+			reqd: true
+		},
+		{
+			fieldtype: 'Check', fieldname: 'is_group', label: __('Is Group'),
+			description: __("Child nodes can be only created under 'Group' type nodes")
+		},
+		{
+			fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'),
+			options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'),
+			depends_on: 'eval:!doc.is_group', default: '',
+			onchange: () => {
+				if (cur_dialog) {
+					if (cur_dialog.fields_dict.service_unit_type.value) {
+						frappe.db.get_value('Healthcare Service Unit Type',
+							cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments')
+							.then(r => {
+								if (r.message.overlap_appointments) {
+									cur_dialog.set_df_property('service_unit_capacity', 'hidden', false);
+									cur_dialog.set_df_property('service_unit_capacity', 'reqd', true);
+								} else {
+									cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+									cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+								}
+							});
+					} else {
+						cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+						cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+					}
+				}
+			}
+		},
+		{
+			fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'),
+			description: __('Sets the number of concurrent appointments allowed'), reqd: false,
+			depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true
+		},
+		{
+			fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse',
+			description: __('Optional, if you want to manage stock separately for this Service Unit'),
+			depends_on: 'eval:!doc.is_group'
+		},
+		{
+			fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true,
+			default: () => {
+				return cur_page.page.page.fields_dict.company.value;
+			}
+		}
+	],
+	ignore_fields: ['parent_healthcare_service_unit'],
+	onrender: function (node) {
+		if (node.data.occupied_of_available !== undefined) {
+			$("<span class='balance-area pull-right text-muted small'>"
+				+ ' ' + node.data.occupied_of_available
 				+ '</span>').insertBefore(node.$ul);
 		}
-		if (node.data && node.data.inpatient_occupancy!==undefined) {
+		if (node.data && node.data.inpatient_occupancy !== undefined) {
 			if (node.data.inpatient_occupancy == 1) {
-				if (node.data.occupancy_status == "Occupied") {
-					$('<span class="balance-area pull-right">'
-						+ " " + node.data.occupancy_status
+				if (node.data.occupancy_status == 'Occupied') {
+					$("<span class='balance-area pull-right small'>"
+						+ ' ' + node.data.occupancy_status
 						+ '</span>').insertBefore(node.$ul);
 				}
-				if (node.data.occupancy_status == "Vacant") {
-					$('<span class="balance-area pull-right">'
-						+ " " + node.data.occupancy_status
+				if (node.data.occupancy_status == 'Vacant') {
+					$("<span class='balance-area pull-right text-muted small'>"
+						+ ' ' + node.data.occupancy_status
 						+ '</span>').insertBefore(node.$ul);
 				}
 			}
 		}
 	},
+	post_render: function (treeview) {
+		frappe.treeview_settings['Healthcare Service Unit'].treeview = {};
+		$.extend(frappe.treeview_settings['Healthcare Service Unit'].treeview, treeview);
+	},
+	toolbar: [
+		{
+			label: __('Add Multiple'),
+			condition: function (node) {
+				return node.expandable;
+			},
+			click: function (node) {
+				const dialog = new frappe.ui.Dialog({
+					title: __('Add Multiple Service Units'),
+					fields: [
+						{
+							fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('Service Unit Name'),
+							reqd: true, description: __("Will be serially suffixed to maintain uniquness. Example: 'Ward' will be named as 'Ward-####'"),
+						},
+						{
+							fieldtype: 'Int', fieldname: 'count', label: __('Number of Service Units'),
+							reqd: true
+						},
+						{
+							fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'),
+							options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'),
+							depends_on: 'eval:!doc.is_group', default: '', reqd: true,
+							onchange: () => {
+								if (cur_dialog) {
+									if (cur_dialog.fields_dict.service_unit_type.value) {
+										frappe.db.get_value('Healthcare Service Unit Type',
+											cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments')
+											.then(r => {
+												if (r.message.overlap_appointments) {
+													cur_dialog.set_df_property('service_unit_capacity', 'hidden', false);
+													cur_dialog.set_df_property('service_unit_capacity', 'reqd', true);
+												} else {
+													cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+													cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+												}
+											});
+									} else {
+										cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+										cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+									}
+								}
+							}
+						},
+						{
+							fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'),
+							description: __('Sets the number of concurrent appointments allowed'), reqd: false,
+							depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true
+						},
+						{
+							fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse',
+							description: __('Optional, if you want to manage stock separately for this Service Unit'),
+						},
+						{
+							fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true,
+							default: () => {
+								return cur_page.page.page.fields_dict.company.get_value();
+							}
+						}
+					],
+					primary_action: () => {
+						dialog.hide();
+						let vals = dialog.get_values();
+						if (!vals) return;
+
+						return frappe.call({
+							method: 'erpnext.healthcare.doctype.healthcare_service_unit.healthcare_service_unit.add_multiple_service_units',
+							args: {
+								parent: node.data.value,
+								data: vals
+							},
+							callback: function (r) {
+								if (!r.exc && r.message) {
+									frappe.treeview_settings['Healthcare Service Unit'].treeview.tree.load_children(node, true);
+
+									frappe.show_alert({
+										message: __('{0} Service Units created', [vals.count - r.message.length]),
+										indicator: 'green'
+									});
+								} else {
+									frappe.msgprint(__('Could not create Service Units'));
+								}
+							},
+							freeze: true,
+							freeze_message: __('Creating {0} Service Units', [vals.count])
+						});
+					},
+					primary_action_label: __('Create')
+				});
+				dialog.show();
+			}
+		}
+	],
+	extend_toolbar: true
 };
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
index eb33ab6..ecf4aa1 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
@@ -68,8 +68,8 @@
 			if (values) {
 				frappe.call({
 					"method": "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.change_item_code",
-					"args": {item: doc.item, item_code: values['item_code'], doc_name: doc.name},
-					callback: function () {
+					"args": { item: doc.item, item_code: values['item_code'], doc_name: doc.name },
+					callback: function() {
 						frm.reload_doc();
 					}
 				});
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
index 4b8503d..9c81c65 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
@@ -29,6 +29,8 @@
   {
    "fieldname": "service_unit_type",
    "fieldtype": "Data",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_list_view": 1,
    "label": "Service Unit Type",
    "no_copy": 1,
@@ -41,6 +43,8 @@
    "depends_on": "eval:doc.inpatient_occupancy != 1",
    "fieldname": "allow_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Allow Appointments"
   },
   {
@@ -49,6 +53,8 @@
    "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1",
    "fieldname": "overlap_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Allow Overlap"
   },
   {
@@ -57,6 +63,8 @@
    "depends_on": "eval:doc.allow_appointments != 1",
    "fieldname": "inpatient_occupancy",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Inpatient Occupancy"
   },
   {
@@ -65,17 +73,23 @@
    "depends_on": "eval:doc.inpatient_occupancy == 1 && doc.allow_appointments != 1",
    "fieldname": "is_billable",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Is Billable"
   },
   {
    "depends_on": "is_billable",
    "fieldname": "item_details",
    "fieldtype": "Section Break",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item Details"
   },
   {
    "fieldname": "item",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item",
    "no_copy": 1,
    "options": "Item",
@@ -84,6 +98,8 @@
   {
    "fieldname": "item_code",
    "fieldtype": "Data",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item Code",
    "mandatory_depends_on": "eval: doc.is_billable == 1",
    "no_copy": 1
@@ -91,6 +107,8 @@
   {
    "fieldname": "item_group",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item Group",
    "mandatory_depends_on": "eval: doc.is_billable == 1",
    "options": "Item Group"
@@ -98,6 +116,8 @@
   {
    "fieldname": "uom",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "UOM",
    "mandatory_depends_on": "eval: doc.is_billable == 1",
    "options": "UOM"
@@ -105,28 +125,38 @@
   {
    "fieldname": "no_of_hours",
    "fieldtype": "Int",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "UOM Conversion in Hours",
    "mandatory_depends_on": "eval: doc.is_billable == 1"
   },
   {
    "fieldname": "column_break_11",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "hide_days": 1,
+   "hide_seconds": 1
   },
   {
    "fieldname": "rate",
    "fieldtype": "Currency",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Rate / UOM"
   },
   {
    "default": "0",
    "fieldname": "disabled",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Disabled",
    "no_copy": 1
   },
   {
    "fieldname": "description",
    "fieldtype": "Small Text",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Description"
   },
   {
@@ -134,11 +164,13 @@
    "fieldname": "change_in_item",
    "fieldtype": "Check",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Change in Item"
   }
  ],
  "links": [],
- "modified": "2020-05-20 15:31:09.627516",
+ "modified": "2021-08-19 17:52:30.266667",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Service Unit Type",
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index a8c7720..b4a9612 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -151,7 +151,7 @@
 
 	if not service_unit:
 		service_unit = frappe.new_doc("Healthcare Service Unit")
-		service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
+		service_unit.healthcare_service_unit_name = unit_name or "_Test Service Unit Ip Occupancy"
 		service_unit.company = "_Test Company"
 		service_unit.service_unit_type = get_service_unit_type()
 		service_unit.inpatient_occupancy = 1
@@ -159,12 +159,12 @@
 		service_unit.is_group = 0
 		service_unit_parent_name = frappe.db.exists({
 				"doctype": "Healthcare Service Unit",
-				"healthcare_service_unit_name": "All Healthcare Service Units",
+				"healthcare_service_unit_name": "_Test All Healthcare Service Units",
 				"is_group": 1
 				})
 		if not service_unit_parent_name:
 			parent_service_unit = frappe.new_doc("Healthcare Service Unit")
-			parent_service_unit.healthcare_service_unit_name = "All Healthcare Service Units"
+			parent_service_unit.healthcare_service_unit_name = "_Test All Healthcare Service Units"
 			parent_service_unit.is_group = 1
 			parent_service_unit.save(ignore_permissions = True)
 			service_unit.parent_healthcare_service_unit = parent_service_unit.name
@@ -180,7 +180,7 @@
 
 	if not service_unit_type:
 		service_unit_type = frappe.new_doc("Healthcare Service Unit Type")
-		service_unit_type.service_unit_type = "Test Service Unit Type Ip Occupancy"
+		service_unit_type.service_unit_type = "_Test Service Unit Type Ip Occupancy"
 		service_unit_type.inpatient_occupancy = 1
 		service_unit_type.save(ignore_permissions = True)
 		return service_unit_type.name
diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js
index bce42e5..9266467 100644
--- a/erpnext/healthcare/doctype/patient/patient.js
+++ b/erpnext/healthcare/doctype/patient/patient.js
@@ -26,31 +26,39 @@
 		}
 
 		if (frm.doc.patient_name && frappe.user.has_role('Physician')) {
+			frm.add_custom_button(__('Patient Progress'), function() {
+				frappe.route_options = {'patient': frm.doc.name};
+				frappe.set_route('patient-progress');
+			}, __('View'));
+
 			frm.add_custom_button(__('Patient History'), function() {
 				frappe.route_options = {'patient': frm.doc.name};
 				frappe.set_route('patient_history');
-			},'View');
+			}, __('View'));
 		}
 
-		if (!frm.doc.__islocal && (frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) {
-			frm.add_custom_button(__('Vital Signs'), function () {
-				create_vital_signs(frm);
-			}, 'Create');
-			frm.add_custom_button(__('Medical Record'), function () {
-				create_medical_record(frm);
-			}, 'Create');
-			frm.add_custom_button(__('Patient Encounter'), function () {
-				create_encounter(frm);
-			}, 'Create');
-			frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option
+		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Patient'};
+		frm.toggle_display(['address_html', 'contact_html'], !frm.is_new());
+
+		if (!frm.is_new()) {
+			if ((frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) {
+				frm.add_custom_button(__('Medical Record'), function () {
+					create_medical_record(frm);
+				}, 'Create');
+				frm.toggle_enable(['customer'], 0);
+			}
+			frappe.contacts.render_address_and_contact(frm);
+			erpnext.utils.set_party_dashboard_indicators(frm);
+		} else {
+			frappe.contacts.clear_address_and_contact(frm);
 		}
 	},
+
 	onload: function (frm) {
-		if (!frm.doc.dob) {
-			$(frm.fields_dict['age_html'].wrapper).html('');
-		}
 		if (frm.doc.dob) {
 			$(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`);
+		} else {
+			$(frm.fields_dict['age_html'].wrapper).html('');
 		}
 	}
 });
@@ -59,16 +67,14 @@
 	if (frm.doc.dob) {
 		let today = new Date();
 		let birthDate = new Date(frm.doc.dob);
-		if (today < birthDate){
+		if (today < birthDate) {
 			frappe.msgprint(__('Please select a valid Date'));
 			frappe.model.set_value(frm.doctype,frm.docname, 'dob', '');
-		}
-		else {
+		} else {
 			let age_str = get_age(frm.doc.dob);
 			$(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`);
 		}
-	}
-	else {
+	} else {
 		$(frm.fields_dict['age_html'].wrapper).html('');
 	}
 });
diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json
index 8af1a9c..4092a6a 100644
--- a/erpnext/healthcare/doctype/patient/patient.json
+++ b/erpnext/healthcare/doctype/patient/patient.json
@@ -1,6 +1,6 @@
 {
  "actions": [],
- "allow_copy": 1,
+ "allow_events_in_timeline": 1,
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "naming_series:",
@@ -24,12 +24,19 @@
   "image",
   "column_break_14",
   "status",
+  "uid",
   "inpatient_record",
   "inpatient_status",
   "report_preference",
   "mobile",
-  "email",
   "phone",
+  "email",
+  "invite_user",
+  "user_id",
+  "address_contacts",
+  "address_html",
+  "column_break_22",
+  "contact_html",
   "customer_details_section",
   "customer",
   "customer_group",
@@ -74,6 +81,7 @@
    "fieldtype": "Select",
    "in_preview": 1,
    "label": "Inpatient Status",
+   "no_copy": 1,
    "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled",
    "read_only": 1
   },
@@ -81,6 +89,7 @@
    "fieldname": "inpatient_record",
    "fieldtype": "Link",
    "label": "Inpatient Record",
+   "no_copy": 1,
    "options": "Inpatient Record",
    "read_only": 1
   },
@@ -101,6 +110,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Full Name",
+   "no_copy": 1,
    "read_only": 1,
    "search_index": 1
   },
@@ -118,6 +128,7 @@
    "fieldtype": "Select",
    "in_preview": 1,
    "label": "Blood Group",
+   "no_copy": 1,
    "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative"
   },
   {
@@ -125,7 +136,8 @@
    "fieldname": "dob",
    "fieldtype": "Date",
    "in_preview": 1,
-   "label": "Date of birth"
+   "label": "Date of birth",
+   "no_copy": 1
   },
   {
    "fieldname": "age_html",
@@ -167,6 +179,7 @@
    "fieldtype": "Link",
    "ignore_user_permissions": 1,
    "label": "Customer",
+   "no_copy": 1,
    "options": "Customer",
    "set_only_once": 1
   },
@@ -183,6 +196,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Mobile",
+   "no_copy": 1,
    "options": "Phone"
   },
   {
@@ -192,6 +206,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Email",
+   "no_copy": 1,
    "options": "Email"
   },
   {
@@ -199,6 +214,7 @@
    "fieldtype": "Data",
    "in_filter": 1,
    "label": "Phone",
+   "no_copy": 1,
    "options": "Phone"
   },
   {
@@ -230,7 +246,8 @@
    "fieldname": "medication",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Medication"
+   "label": "Medication",
+   "no_copy": 1
   },
   {
    "fieldname": "column_break_20",
@@ -240,13 +257,15 @@
    "fieldname": "medical_history",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Medical History"
+   "label": "Medical History",
+   "no_copy": 1
   },
   {
    "fieldname": "surgical_history",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Surgical History"
+   "label": "Surgical History",
+   "no_copy": 1
   },
   {
    "collapsible": 1,
@@ -258,8 +277,8 @@
    "fieldname": "occupation",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "in_standard_filter": 1,
-   "label": "Occupation"
+   "label": "Occupation",
+   "no_copy": 1
   },
   {
    "fieldname": "column_break_25",
@@ -269,6 +288,7 @@
    "fieldname": "marital_status",
    "fieldtype": "Select",
    "label": "Marital Status",
+   "no_copy": 1,
    "options": "\nSingle\nMarried\nDivorced\nWidow"
   },
   {
@@ -281,25 +301,29 @@
    "fieldname": "tobacco_past_use",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "label": "Tobacco Consumption (Past)"
+   "label": "Tobacco Consumption (Past)",
+   "no_copy": 1
   },
   {
    "fieldname": "tobacco_current_use",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "label": "Tobacco Consumption (Present)"
+   "label": "Tobacco Consumption (Present)",
+   "no_copy": 1
   },
   {
    "fieldname": "alcohol_past_use",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "label": "Alcohol Consumption (Past)"
+   "label": "Alcohol Consumption (Past)",
+   "no_copy": 1
   },
   {
    "fieldname": "alcohol_current_use",
    "fieldtype": "Data",
    "ignore_user_permissions": 1,
-   "label": "Alcohol Consumption (Present)"
+   "label": "Alcohol Consumption (Present)",
+   "no_copy": 1
   },
   {
    "fieldname": "column_break_32",
@@ -309,13 +333,15 @@
    "fieldname": "surrounding_factors",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Occupational Hazards and Environmental Factors"
+   "label": "Occupational Hazards and Environmental Factors",
+   "no_copy": 1
   },
   {
    "fieldname": "other_risk_factors",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Other Risk Factors"
+   "label": "Other Risk Factors",
+   "no_copy": 1
   },
   {
    "collapsible": 1,
@@ -331,7 +357,8 @@
    "fieldname": "patient_details",
    "fieldtype": "Text",
    "ignore_xss_filter": 1,
-   "label": "Patient Details"
+   "label": "Patient Details",
+   "no_copy": 1
   },
   {
    "fieldname": "default_currency",
@@ -342,19 +369,22 @@
   {
    "fieldname": "last_name",
    "fieldtype": "Data",
-   "label": "Last Name"
+   "label": "Last Name",
+   "no_copy": 1
   },
   {
    "fieldname": "first_name",
    "fieldtype": "Data",
    "label": "First Name",
+   "no_copy": 1,
    "oldfieldtype": "Data",
    "reqd": 1
   },
   {
    "fieldname": "middle_name",
    "fieldtype": "Data",
-   "label": "Middle Name (optional)"
+   "label": "Middle Name (optional)",
+   "no_copy": 1
   },
   {
    "collapsible": 1,
@@ -389,13 +419,63 @@
    "fieldtype": "Link",
    "label": "Print Language",
    "options": "Language"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "address_contacts",
+   "fieldtype": "Section Break",
+   "label": "Address and Contact",
+   "options": "fa fa-map-marker"
+  },
+  {
+   "fieldname": "address_html",
+   "fieldtype": "HTML",
+   "label": "Address HTML",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_22",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "contact_html",
+   "fieldtype": "HTML",
+   "label": "Contact HTML",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "allow_in_quick_entry": 1,
+   "default": "1",
+   "fieldname": "invite_user",
+   "fieldtype": "Check",
+   "label": "Invite as User",
+   "no_copy": 1,
+   "read_only_depends_on": "eval: doc.user_id"
+  },
+  {
+   "fieldname": "user_id",
+   "fieldtype": "Read Only",
+   "label": "User ID",
+   "no_copy": 1,
+   "options": "User"
+  },
+  {
+   "allow_in_quick_entry": 1,
+   "bold": 1,
+   "fieldname": "uid",
+   "fieldtype": "Data",
+   "in_standard_filter": 1,
+   "label": "Identification Number (UID)",
+   "unique": 1
   }
  ],
  "icon": "fa fa-user",
  "image_field": "image",
  "links": [],
  "max_attachments": 50,
- "modified": "2020-04-25 17:24:32.146415",
+ "modified": "2021-03-14 13:21:09.759906",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient",
@@ -453,7 +533,7 @@
  ],
  "quick_entry": 1,
  "restrict_to_domain": "Healthcare",
- "search_fields": "patient_name,mobile,email,phone",
+ "search_fields": "patient_name,mobile,email,phone,uid",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index f77ad70..9dae1f6 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -8,24 +8,27 @@
 from frappe.model.document import Document
 from frappe.utils import cint, cstr, getdate
 import dateutil
+from frappe.contacts.address_and_contact import load_address_and_contact
+from frappe.contacts.doctype.contact.contact import get_default_contact
 from frappe.model.naming import set_name_by_naming_series
 from frappe.utils.nestedset import get_root_of
 from erpnext import get_default_currency
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms
+from erpnext.accounts.party import get_dashboard_info
 
 class Patient(Document):
+	def onload(self):
+		'''Load address and contacts in `__onload`'''
+		load_address_and_contact(self)
+		self.load_dashboard_info()
+
 	def validate(self):
 		self.set_full_name()
-		self.add_as_website_user()
 
 	def before_insert(self):
 		self.set_missing_customer_details()
 
 	def after_insert(self):
-		self.add_as_website_user()
-		self.reload()
-		if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient') and not self.customer:
-			create_customer(self)
 		if frappe.db.get_single_value('Healthcare Settings', 'collect_registration_fee'):
 			frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
 		else:
@@ -49,6 +52,16 @@
 			else:
 				create_customer(self)
 
+		self.set_contact() # add or update contact
+
+		if not self.user_id and self.email and self.invite_user:
+			self.create_website_user()
+
+	def load_dashboard_info(self):
+		if self.customer:
+			info = get_dashboard_info('Customer', self.customer, None)
+			self.set_onload('dashboard_info', info)
+
 	def set_full_name(self):
 		if self.last_name:
 			self.patient_name = ' '.join(filter(None, [self.first_name, self.last_name]))
@@ -71,18 +84,24 @@
 		if not self.language:
 			self.language = frappe.db.get_single_value('System Settings', 'language')
 
-	def add_as_website_user(self):
-		if self.email:
-			if not frappe.db.exists ('User', self.email):
-				user = frappe.get_doc({
-					'doctype': 'User',
-					'first_name': self.first_name,
-					'last_name': self.last_name,
-					'email': self.email,
-					'user_type': 'Website User'
-				})
-				user.flags.ignore_permissions = True
-				user.add_roles('Patient')
+	def create_website_user(self):
+		if self.email and not frappe.db.exists('User', self.email):
+			user = frappe.get_doc({
+				'doctype': 'User',
+				'first_name': self.first_name,
+				'last_name': self.last_name,
+				'email': self.email,
+				'user_type': 'Website User',
+				'gender': self.sex,
+				'phone': self.phone,
+				'mobile_no': self.mobile,
+				'birth_date': self.dob
+			})
+			user.flags.ignore_permissions = True
+			user.enabled = True
+			user.send_welcome_email = True
+			user.add_roles('Patient')
+			frappe.db.set_value(self.doctype, self.name, 'user_id', user.name)
 
 	def autoname(self):
 		patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by')
@@ -114,7 +133,7 @@
 		age = self.age
 		if not age:
 			return
-		age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
+		age_str = f'{str(age.years)} {_("Years(s)")} {str(age.months)} {_("Month(s)")} {str(age.days)} {_("Day(s)")}'
 		return age_str
 
 	@frappe.whitelist()
@@ -131,6 +150,58 @@
 
 			return {'invoice': sales_invoice.name}
 
+	def set_contact(self):
+		if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':self.name}):
+			old_doc = self.get_doc_before_save()
+			if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone:
+				self.update_contact()
+		else:
+			self.reload()
+			if self.email or self.mobile or self.phone:
+				contact = frappe.get_doc({
+					'doctype': 'Contact',
+					'first_name': self.first_name,
+					'middle_name': self.middle_name,
+					'last_name': self.last_name,
+					'gender': self.sex,
+					'is_primary_contact': 1
+				})
+				contact.append('links', dict(link_doctype='Patient', link_name=self.name))
+				if self.customer:
+					contact.append('links', dict(link_doctype='Customer', link_name=self.customer))
+
+				contact.insert(ignore_permissions=True)
+				self.update_contact(contact) # update email, mobile and phone
+
+	def update_contact(self, contact=None):
+		if not contact:
+			contact_name = get_default_contact(self.doctype, self.name)
+			if contact_name:
+				contact = frappe.get_doc('Contact', contact_name)
+
+		if contact:
+			if self.email and self.email != contact.email_id:
+				for email in contact.email_ids:
+					email.is_primary = True if email.email_id == self.email else False
+				contact.add_email(self.email, is_primary=True)
+				contact.set_primary_email()
+
+			if self.mobile and self.mobile != contact.mobile_no:
+				for mobile in contact.phone_nos:
+					mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False
+				contact.add_phone(self.mobile, is_primary_mobile_no=True)
+				contact.set_primary('mobile_no')
+
+			if self.phone and self.phone != contact.phone:
+				for phone in contact.phone_nos:
+					phone.is_primary_phone = True if phone.phone == self.phone else False
+				contact.add_phone(self.phone, is_primary_phone=True)
+				contact.set_primary('phone')
+
+		contact.flags.ignore_validate = True # disable hook TODO: safe?
+		contact.save(ignore_permissions=True)
+
+
 def create_customer(doc):
 	customer = frappe.get_doc({
 		'doctype': 'Customer',
@@ -156,8 +227,8 @@
 	sales_invoice.debit_to = get_receivable_account(company)
 
 	item_line = sales_invoice.append('items')
-	item_line.item_name = 'Registeration Fee'
-	item_line.description = 'Registeration Fee'
+	item_line.item_name = 'Registration Fee'
+	item_line.description = 'Registration Fee'
 	item_line.qty = 1
 	item_line.uom = uom
 	item_line.conversion_factor = 1
@@ -181,8 +252,11 @@
 	return details
 
 def get_timeline_data(doctype, name):
-	"""Return timeline data from medical records"""
-	return dict(frappe.db.sql('''
+	'''
+	Return Patient's timeline data from medical records
+	Also include the associated Customer timeline data
+	'''
+	patient_timeline_data = dict(frappe.db.sql('''
 		SELECT
 			unix_timestamp(communication_date), count(*)
 		FROM
@@ -191,3 +265,11 @@
 			patient=%s
 			and `communication_date` > date_sub(curdate(), interval 1 year)
 		GROUP BY communication_date''', name))
+
+	customer = frappe.db.get_value(doctype, name, 'customer')
+	if customer:
+		from erpnext.accounts.party import get_timeline_data
+		customer_timeline_data = get_timeline_data('Customer', customer)
+		patient_timeline_data.update(customer_timeline_data)
+
+	return patient_timeline_data
diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py
index 39603f7..7f7cfa8 100644
--- a/erpnext/healthcare/doctype/patient/patient_dashboard.py
+++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py
@@ -6,22 +6,33 @@
 		'heatmap': True,
 		'heatmap_message': _('This is based on transactions against this Patient. See timeline below for details'),
 		'fieldname': 'patient',
+		'non_standard_fieldnames': {
+			'Payment Entry': 'party'
+		},
 		'transactions': [
 			{
-				'label': _('Appointments and Patient Encounters'),
-				'items': ['Patient Appointment', 'Patient Encounter']
+				'label': _('Appointments and Encounters'),
+				'items': ['Patient Appointment', 'Vital Signs', 'Patient Encounter']
 			},
 			{
 				'label': _('Lab Tests and Vital Signs'),
- 				'items': ['Lab Test', 'Sample Collection', 'Vital Signs']
+				'items': ['Lab Test', 'Sample Collection']
 			},
 			{
-				'label': _('Billing'),
-				'items': ['Sales Invoice']
+				'label': _('Rehab and Physiotherapy'),
+				'items': ['Patient Assessment', 'Therapy Session', 'Therapy Plan']
 			},
 			{
-				'label': _('Orders'),
-				'items': ['Inpatient Medication Order']
+				'label': _('Surgery'),
+				'items': ['Clinical Procedure']
+			},
+			{
+				'label': _('Admissions'),
+				'items': ['Inpatient Record', 'Inpatient Medication Order']
+			},
+			{
+				'label': _('Billing and Payments'),
+				'items': ['Sales Invoice', 'Payment Entry']
 			}
 		]
 	}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index c6e489e..49847d5 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -17,9 +17,9 @@
 	},
 
 	refresh: function(frm) {
-		frm.set_query('patient', function () {
+		frm.set_query('patient', function() {
 			return {
-				filters: {'status': 'Active'}
+				filters: { 'status': 'Active' }
 			};
 		});
 
@@ -64,7 +64,7 @@
 				} else {
 					frappe.call({
 						method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd',
-						args: {'patient': frm.doc.patient},
+						args: { 'patient': frm.doc.patient },
 						callback: function(data) {
 							if (data.message == true) {
 								if (frm.doc.mode_of_payment && frm.doc.paid_amount) {
@@ -97,7 +97,7 @@
 
 		if (frm.doc.patient) {
 			frm.add_custom_button(__('Patient History'), function() {
-				frappe.route_options = {'patient': frm.doc.patient};
+				frappe.route_options = { 'patient': frm.doc.patient };
 				frappe.set_route('patient_history');
 			}, __('View'));
 		}
@@ -111,14 +111,14 @@
 			});
 
 			if (frm.doc.procedure_template) {
-				frm.add_custom_button(__('Clinical Procedure'), function(){
+				frm.add_custom_button(__('Clinical Procedure'), function() {
 					frappe.model.open_mapped_doc({
 						method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.make_procedure',
 						frm: frm,
 					});
 				}, __('Create'));
 			} else if (frm.doc.therapy_type) {
-				frm.add_custom_button(__('Therapy Session'),function(){
+				frm.add_custom_button(__('Therapy Session'), function() {
 					frappe.model.open_mapped_doc({
 						method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.create_therapy_session',
 						frm: frm,
@@ -148,7 +148,7 @@
 					doctype: 'Patient',
 					name: frm.doc.patient
 				},
-				callback: function (data) {
+				callback: function(data) {
 					let age = null;
 					if (data.message.dob) {
 						age = calculate_age(data.message.dob);
@@ -165,7 +165,7 @@
 	},
 
 	practitioner: function(frm) {
-		if (frm.doc.practitioner ) {
+		if (frm.doc.practitioner) {
 			frm.events.set_payment_details(frm);
 		}
 	},
@@ -230,7 +230,7 @@
 	toggle_payment_fields: function(frm) {
 		frappe.call({
 			method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd',
-			args: {'patient': frm.doc.patient},
+			args: { 'patient': frm.doc.patient },
 			callback: function(data) {
 				if (data.message.fee_validity) {
 					// if fee validity exists and automated appointment invoicing is enabled,
@@ -254,7 +254,7 @@
 					frm.toggle_display('paid_amount', data.message ? 1 : 0);
 					frm.toggle_display('billing_item', data.message ? 1 : 0);
 					frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0);
-					frm.toggle_reqd('paid_amount', data.message ? 1 :0);
+					frm.toggle_reqd('paid_amount', data.message ? 1 : 0);
 					frm.toggle_reqd('billing_item', data.message ? 1 : 0);
 				}
 			}
@@ -265,7 +265,7 @@
 		if (frm.doc.patient) {
 			frappe.call({
 				method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_prescribed_therapies",
-				args: {patient: frm.doc.patient},
+				args: { patient: frm.doc.patient },
 				callback: function(r) {
 					if (r.message) {
 						show_therapy_types(frm, r.message);
@@ -302,13 +302,13 @@
 		let d = new frappe.ui.Dialog({
 			title: __('Available slots'),
 			fields: [
-				{ fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department'},
-				{ fieldtype: 'Column Break'},
-				{ fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner'},
-				{ fieldtype: 'Column Break'},
-				{ fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date'},
-				{ fieldtype: 'Section Break'},
-				{ fieldtype: 'HTML', fieldname: 'available_slots'}
+				{ fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department' },
+				{ fieldtype: 'Column Break' },
+				{ fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner' },
+				{ fieldtype: 'Column Break' },
+				{ fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date' },
+				{ fieldtype: 'Section Break' },
+				{ fieldtype: 'HTML', fieldname: 'available_slots' }
 
 			],
 			primary_action_label: __('Book'),
@@ -386,59 +386,22 @@
 						let $wrapper = d.fields_dict.available_slots.$wrapper;
 
 						// make buttons for each slot
-						let slot_details = data.slot_details;
-						let slot_html = '';
-						for (let i = 0; i < slot_details.length; i++) {
-							slot_html = slot_html + `<label>${slot_details[i].slot_name}</label>`;
-							slot_html = slot_html + `<br/>` + slot_details[i].avail_slot.map(slot => {
-								let disabled = '';
-								let start_str = slot.from_time;
-								let slot_start_time = moment(slot.from_time, 'HH:mm:ss');
-								let slot_to_time = moment(slot.to_time, 'HH:mm:ss');
-								let interval = (slot_to_time - slot_start_time)/60000 | 0;
-								// iterate in all booked appointments, update the start time and duration
-								slot_details[i].appointments.forEach(function(booked) {
-									let booked_moment = moment(booked.appointment_time, 'HH:mm:ss');
-									let end_time = booked_moment.clone().add(booked.duration, 'minutes');
-									// Deal with 0 duration appointments
-									if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_to_time)) {
-										if(booked.duration == 0){
-											disabled = 'disabled="disabled"';
-											return false;
-										}
-									}
-									// Check for overlaps considering appointment duration
-									if (slot_start_time.isBefore(end_time) && slot_to_time.isAfter(booked_moment)) {
-										// There is an overlap
-										disabled = 'disabled="disabled"';
-										return false;
-									}
-								});
-								return `<button class="btn btn-default"
-									data-name=${start_str}
-									data-duration=${interval}
-									data-service-unit="${slot_details[i].service_unit || ''}"
-									style="margin: 0 10px 10px 0; width: 72px;" ${disabled}>
-									${start_str.substring(0, start_str.length - 3)}
-								</button>`;
-							}).join("");
-							slot_html = slot_html + `<br/>`;
-						}
+						let slot_html = get_slots(data.slot_details);
 
 						$wrapper
 							.css('margin-bottom', 0)
 							.addClass('text-center')
 							.html(slot_html);
 
-						// blue button when clicked
+						// highlight button when clicked
 						$wrapper.on('click', 'button', function() {
 							let $btn = $(this);
-							$wrapper.find('button').removeClass('btn-primary');
-							$btn.addClass('btn-primary');
+							$wrapper.find('button').removeClass('btn-outline-primary');
+							$btn.addClass('btn-outline-primary');
 							selected_slot = $btn.attr('data-name');
 							service_unit = $btn.attr('data-service-unit');
 							duration = $btn.attr('data-duration');
-							// enable dialog action
+							// enable primary action 'Book'
 							d.get_primary_btn().attr('disabled', null);
 						});
 
@@ -448,19 +411,102 @@
 					}
 				},
 				freeze: true,
-				freeze_message: __('Fetching records......')
+				freeze_message: __('Fetching Schedule...')
 			});
 		} else {
 			fd.available_slots.html(__('Appointment date and Healthcare Practitioner are Mandatory').bold());
 		}
 	}
+
+	function get_slots(slot_details) {
+		let slot_html = '';
+		let appointment_count = 0;
+		let disabled = false;
+		let start_str, slot_start_time, slot_end_time, interval, count, count_class, tool_tip, available_slots;
+
+		slot_details.forEach((slot_info) => {
+			slot_html += `<div class="slot-info">
+				<span> <b> ${__('Practitioner Schedule:')} </b> ${slot_info.slot_name} </span><br>
+				<span> <b> ${__('Service Unit:')} </b> ${slot_info.service_unit} </span>`;
+
+			if (slot_info.service_unit_capacity) {
+				slot_html += `<br><span> <b> ${__('Maximum Capacity:')} </b> ${slot_info.service_unit_capacity} </span>`;
+			}
+
+			slot_html += '</div><br><br>';
+
+			slot_html += slot_info.avail_slot.map(slot => {
+				appointment_count = 0;
+				disabled = false;
+				start_str = slot.from_time;
+				slot_start_time = moment(slot.from_time, 'HH:mm:ss');
+				slot_end_time = moment(slot.to_time, 'HH:mm:ss');
+				interval = (slot_end_time - slot_start_time) / 60000 | 0;
+
+				// iterate in all booked appointments, update the start time and duration
+				slot_info.appointments.forEach((booked) => {
+					let booked_moment = moment(booked.appointment_time, 'HH:mm:ss');
+					let end_time = booked_moment.clone().add(booked.duration, 'minutes');
+
+					// Deal with 0 duration appointments
+					if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_end_time)) {
+						if (booked.duration == 0) {
+							disabled = true;
+							return false;
+						}
+					}
+
+					// Check for overlaps considering appointment duration
+					if (slot_info.allow_overlap != 1) {
+						if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) {
+							// There is an overlap
+							disabled = true;
+							return false;
+						}
+					} else {
+						if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) {
+							appointment_count++;
+						}
+						if (appointment_count >= slot_info.service_unit_capacity) {
+							// There is an overlap
+							disabled = true;
+							return false;
+						}
+					}
+				});
+
+				if (slot_info.allow_overlap == 1 && slot_info.service_unit_capacity > 1) {
+					available_slots = slot_info.service_unit_capacity - appointment_count;
+					count = `${(available_slots > 0 ? available_slots : __('Full'))}`;
+					count_class = `${(available_slots > 0 ? 'badge-success' : 'badge-danger')}`;
+					tool_tip =`${available_slots} ${__('slots available for booking')}`;
+				}
+				return `
+					<button class="btn btn-secondary" data-name=${start_str}
+						data-duration=${interval}
+						data-service-unit="${slot_info.service_unit || ''}"
+						style="margin: 0 10px 10px 0; width: auto;" ${disabled ? 'disabled="disabled"' : ""}
+						data-toggle="tooltip" title="${tool_tip}">
+						${start_str.substring(0, start_str.length - 3)}<br>
+						<span class='badge ${count_class}'> ${count} </span>
+					</button>`;
+			}).join("");
+
+			if (slot_info.service_unit_capacity) {
+				slot_html += `<br/><small>${__('Each slot indicates the capacity currently available for booking')}</small>`;
+			}
+			slot_html += `<br/><br/>`;
+		});
+
+		return slot_html;
+	}
 };
 
 let get_prescribed_procedure = function(frm) {
 	if (frm.doc.patient) {
 		frappe.call({
 			method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_procedure_prescribed',
-			args: {patient: frm.doc.patient},
+			args: { patient: frm.doc.patient },
 			callback: function(r) {
 				if (r.message && r.message.length) {
 					show_procedure_templates(frm, r.message);
@@ -480,7 +526,7 @@
 	}
 };
 
-let show_procedure_templates = function(frm, result){
+let show_procedure_templates = function(frm, result) {
 	let d = new frappe.ui.Dialog({
 		title: __('Prescribed Procedures'),
 		fields: [
@@ -500,9 +546,11 @@
 		data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
 		data-date="%(date)s"  data-department="%(department)s">\
 		<button class="btn btn-default btn-xs">Add\
-		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {name:y[0], procedure_template: y[1],
-				encounter:y[2], consulting_practitioner:y[3], encounter_date:y[4],
-				practitioner:y[5]? y[5]:'', date: y[6]? y[6]:'', department: y[7]? y[7]:''})).appendTo(html_field);
+		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {
+			name: y[0], procedure_template: y[1],
+			encounter: y[2], consulting_practitioner: y[3], encounter_date: y[4],
+			practitioner: y[5] ? y[5] : '', date: y[6] ? y[6] : '', department: y[7] ? y[7] : ''
+		})).appendTo(html_field);
 		row.find("a").click(function() {
 			frm.doc.procedure_template = $(this).attr('data-procedure-template');
 			frm.doc.procedure_prescription = $(this).attr('data-name');
@@ -520,7 +568,7 @@
 	});
 	if (!result) {
 		let msg = __('There are no procedure prescribed for ') + frm.doc.patient;
-		$(repl('<div class="col-xs-12" style="padding-top:20px;" >%(msg)s</div></div>', {msg: msg})).appendTo(html_field);
+		$(repl('<div class="col-xs-12" style="padding-top:20px;" >%(msg)s</div></div>', { msg: msg })).appendTo(html_field);
 	}
 	d.show();
 };
@@ -535,7 +583,7 @@
 		]
 	});
 	var html_field = d.fields_dict.therapy_type.$wrapper;
-	$.each(result, function(x, y){
+	$.each(result, function(x, y) {
 		var row = $(repl('<div class="col-xs-12" style="padding-top:12px; text-align:center;" >\
 		<div class="col-xs-5"> %(encounter)s <br> %(practitioner)s <br> %(date)s </div>\
 		<div class="col-xs-5"> %(therapy)s </div>\
@@ -544,9 +592,11 @@
 		data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
 		data-date="%(date)s"  data-department="%(department)s">\
 		<button class="btn btn-default btn-xs">Add\
-		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {therapy:y[0],
-		name: y[1], encounter:y[2], practitioner:y[3], date:y[4],
-		department:y[6]? y[6]:'', therapy_plan:y[5]})).appendTo(html_field);
+		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {
+			therapy: y[0],
+			name: y[1], encounter: y[2], practitioner: y[3], date: y[4],
+			department: y[6] ? y[6] : '', therapy_plan: y[5]
+		})).appendTo(html_field);
 
 		row.find("a").click(function() {
 			frm.doc.therapy_type = $(this).attr("data-therapy");
@@ -581,13 +631,13 @@
 	frappe.new_doc('Vital Signs');
 };
 
-let update_status = function(frm, status){
+let update_status = function(frm, status) {
 	let doc = frm.doc;
 	frappe.confirm(__('Are you sure you want to cancel this appointment?'),
 		function() {
 			frappe.call({
 				method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status',
-				args: {appointment_id: doc.name, status:status},
+				args: { appointment_id: doc.name, status: status },
 				callback: function(data) {
 					if (!data.exc) {
 						frm.reload_doc();
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index 73ec3bc..28d3a6d 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -131,7 +131,7 @@
    "fieldtype": "Link",
    "label": "Service Unit",
    "options": "Healthcare Service Unit",
-   "set_only_once": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:doc.practitioner;",
@@ -349,7 +349,7 @@
   }
  ],
  "links": [],
- "modified": "2021-06-16 00:40:26.841794",
+ "modified": "2021-08-30 09:00:41.329387",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient Appointment",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 7db4fa6..36047c4 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -15,6 +15,11 @@
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
 from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity
 
+class MaximumCapacityError(frappe.ValidationError):
+	pass
+class OverlapError(frappe.ValidationError):
+	pass
+
 class PatientAppointment(Document):
 	def validate(self):
 		self.validate_overlaps()
@@ -49,26 +54,49 @@
 		end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \
 			 + datetime.timedelta(minutes=flt(self.duration))
 
-		overlaps = frappe.db.sql("""
-		select
-			name, practitioner, patient, appointment_time, duration
-		from
-			`tabPatient Appointment`
-		where
-			appointment_date=%s and name!=%s and status NOT IN ("Closed", "Cancelled")
-			and (practitioner=%s or patient=%s) and
-			((appointment_time<%s and appointment_time + INTERVAL duration MINUTE>%s) or
-			(appointment_time>%s and appointment_time<%s) or
-			(appointment_time=%s))
-		""", (self.appointment_date, self.name, self.practitioner, self.patient,
-		self.appointment_time, end_time.time(), self.appointment_time, end_time.time(), self.appointment_time))
+		# all appointments for both patient and practitioner overlapping the duration of this appointment
+		overlapping_appointments = frappe.db.sql("""
+			SELECT
+				name, practitioner, patient, appointment_time, duration, service_unit
+			FROM
+				`tabPatient Appointment`
+			WHERE
+				appointment_date=%(appointment_date)s AND name!=%(name)s AND status NOT IN ("Closed", "Cancelled") AND
+				(practitioner=%(practitioner)s OR patient=%(patient)s) AND
+				((appointment_time<%(appointment_time)s AND appointment_time + INTERVAL duration MINUTE>%(appointment_time)s) OR
+				(appointment_time>%(appointment_time)s AND appointment_time<%(end_time)s) OR
+				(appointment_time=%(appointment_time)s))
+			""",
+			{
+				'appointment_date': self.appointment_date,
+				'name': self.name,
+				'practitioner': self.practitioner,
+				'patient': self.patient,
+				'appointment_time': self.appointment_time,
+				'end_time':end_time.time()
+			},
+			as_dict = True
+		)
 
-		if overlaps:
-			overlapping_details = _('Appointment overlaps with ')
-			overlapping_details += "<b><a href='/app/Form/Patient Appointment/{0}'>{0}</a></b><br>".format(overlaps[0][0])
-			overlapping_details += _('{0} has appointment scheduled with {1} at {2} having {3} minute(s) duration.').format(
-				overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
-			frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
+		if not overlapping_appointments:
+			return # No overlaps, nothing to validate!
+
+		if self.service_unit: # validate service unit capacity if overlap enabled
+			allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', self.service_unit,
+				['overlap_appointments', 'service_unit_capacity'])
+			if allow_overlap:
+				service_unit_appointments = list(filter(lambda appointment: appointment['service_unit'] == self.service_unit and
+					appointment['patient'] != self.patient, overlapping_appointments)) # if same patient already booked, it should be an overlap
+				if len(service_unit_appointments) >= (service_unit_capacity or 1):
+					frappe.throw(_("Not allowed, {} cannot exceed maximum capacity {}")
+						.format(frappe.bold(self.service_unit), frappe.bold(service_unit_capacity or 1)), MaximumCapacityError)
+				else: # service_unit_appointments within capacity, remove from overlapping_appointments
+					overlapping_appointments = [appointment for appointment in overlapping_appointments if appointment not in service_unit_appointments]
+
+		if overlapping_appointments:
+			frappe.throw(_("Not allowed, cannot overlap appointment {}")
+				.format(frappe.bold(', '.join([appointment['name'] for appointment in overlapping_appointments]))), OverlapError)
+
 
 	def validate_service_unit(self):
 		if self.inpatient_record and self.service_unit:
@@ -325,6 +353,8 @@
 
 			if available_slots:
 				appointments = []
+				allow_overlap = 0
+				service_unit_capacity = 0
 				# fetch all appointments to practitioner by service unit
 				filters = {
 					'practitioner': practitioner,
@@ -334,8 +364,8 @@
 				}
 
 				if schedule_entry.service_unit:
-					slot_name  = schedule_entry.schedule + ' - ' + schedule_entry.service_unit
-					allow_overlap = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, 'overlap_appointments')
+					slot_name = f'{schedule_entry.schedule}'
+					allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, ['overlap_appointments', 'service_unit_capacity'])
 					if not allow_overlap:
 						# fetch all appointments to service unit
 						filters.pop('practitioner')
@@ -350,8 +380,8 @@
 					filters=filters,
 					fields=['name', 'appointment_time', 'duration', 'status'])
 
-				slot_details.append({'slot_name':slot_name, 'service_unit':schedule_entry.service_unit,
-					'avail_slot':available_slots, 'appointments': appointments})
+				slot_details.append({'slot_name': slot_name, 'service_unit': schedule_entry.service_unit, 'avail_slot': available_slots,
+					'appointments': appointments,  'allow_overlap': allow_overlap, 'service_unit_capacity': service_unit_capacity})
 
 	return slot_details
 
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 157b3e1..f5477c0 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -16,9 +16,11 @@
 		frappe.db.sql("""delete from `tabFee Validity`""")
 		frappe.db.sql("""delete from `tabPatient Encounter`""")
 		make_pos_profile()
+		frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test %'""")
+		frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test Service Unit Type%'""")
 
 	def test_status(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
 		appointment = create_appointment(patient, practitioner, nowdate())
 		self.assertEqual(appointment.status, 'Open')
@@ -30,7 +32,7 @@
 		self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
 
 	def test_start_encounter(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
 		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1)
 		appointment.reload()
@@ -44,7 +46,7 @@
 		self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'))
 
 	def test_auto_invoicing(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
 		appointment = create_appointment(patient, practitioner, nowdate())
@@ -60,13 +62,14 @@
 		self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
 
 	def test_auto_invoicing_based_on_department(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
+		medical_department = create_medical_department()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
 		appointment_type = create_appointment_type()
 
 		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2),
-			invoice=1, appointment_type=appointment_type.name, department='_Test Medical Department')
+			invoice=1, appointment_type=appointment_type.name, department=medical_department)
 		appointment.reload()
 
 		self.assertEqual(appointment.invoiced, 1)
@@ -78,7 +81,7 @@
 		self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
 
 	def test_auto_invoicing_according_to_appointment_type_charge(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
 
@@ -104,7 +107,7 @@
 		self.assertTrue(sales_invoice_name)
 
 	def test_appointment_cancel(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
 		appointment = create_appointment(patient, practitioner, nowdate())
 		fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner})
@@ -112,7 +115,7 @@
 		self.assertTrue(fee_validity)
 
 		# first follow up appointment
-		appointment = create_appointment(patient, practitioner, nowdate())
+		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1))
 		self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1)
 
 		update_status(appointment.name, 'Cancelled')
@@ -121,7 +124,7 @@
 
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
-		appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
+		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1), invoice=1)
 		update_status(appointment.name, 'Cancelled')
 		# check invoice cancelled
 		sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
@@ -133,7 +136,7 @@
 			create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
 
 		frappe.db.sql("""delete from `tabInpatient Record`""")
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		patient = create_patient()
 		# Schedule Admission
 		ip_record = create_inpatient(patient)
@@ -141,7 +144,7 @@
 		ip_record.save(ignore_permissions = True)
 
 		# Admit
-		service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+		service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy')
 		admit_patient(ip_record, service_unit, now_datetime())
 
 		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
@@ -159,7 +162,7 @@
 			create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
 
 		frappe.db.sql("""delete from `tabInpatient Record`""")
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		patient = create_patient()
 		# Schedule Admission
 		ip_record = create_inpatient(patient)
@@ -167,10 +170,10 @@
 		ip_record.save(ignore_permissions = True)
 
 		# Admit
-		service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+		service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy')
 		admit_patient(ip_record, service_unit, now_datetime())
 
-		appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
+		appointment_service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy for Appointment')
 		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
 		self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
 
@@ -192,7 +195,7 @@
 		assert payment_required is True
 
 	def test_sales_invoice_should_be_generated_for_new_patient_appointment(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
 		invoice_count = frappe.db.count('Sales Invoice')
 
@@ -203,10 +206,10 @@
 		assert new_invoice_count == invoice_count + 1
 
 	def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		create_appointment(patient, practitioner, nowdate())
 
-		patient, medical_department, new_practitioner = create_healthcare_docs(practitioner_name='Dr. John')
+		patient, new_practitioner = create_healthcare_docs(id=5)
 		create_appointment(patient, new_practitioner, nowdate())
 
 		roles = [{"doctype": "Has Role", "role": "Physician"}]
@@ -223,41 +226,102 @@
 		appointments = frappe.get_list('Patient Appointment')
 		assert len(appointments) == 2
 
-def create_healthcare_docs(practitioner_name=None):
-	if not practitioner_name:
-		practitioner_name = '_Test Healthcare Practitioner'
+	def test_overlap_appointment(self):
+		from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError
+		patient, practitioner = create_healthcare_docs(id=1)
+		patient_1, practitioner_1 = create_healthcare_docs(id=2)
+		service_unit = create_service_unit(id=0)
+		service_unit_1 = create_service_unit(id=1)
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) # valid
 
-	patient = create_patient()
-	practitioner = frappe.db.exists('Healthcare Practitioner', practitioner_name)
-	medical_department = frappe.db.exists('Medical Department', '_Test Medical Department')
+		# patient and practitioner cannot have overlapping appointments
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit_1, save=0) # diff service unit
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner, nowdate(), save=0) # with no service unit link
+		self.assertRaises(OverlapError, appointment.save)
 
-	if not medical_department:
-		medical_department = frappe.new_doc('Medical Department')
-		medical_department.department = '_Test Medical Department'
-		medical_department.save(ignore_permissions=True)
-		medical_department = medical_department.name
+		# patient cannot have overlapping appointments with other practitioners
+		appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner_1, nowdate(), save=0)
+		self.assertRaises(OverlapError, appointment.save)
 
-	if not practitioner:
-		practitioner = frappe.new_doc('Healthcare Practitioner')
-		practitioner.first_name = practitioner_name
-		practitioner.gender = 'Female'
-		practitioner.department = medical_department
-		practitioner.op_consulting_charge = 500
-		practitioner.inpatient_visit_charge = 500
-		practitioner.save(ignore_permissions=True)
-		practitioner = practitioner.name
+		# practitioner cannot have overlapping appointments with other patients
+		appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient_1, practitioner, nowdate(), save=0)
+		self.assertRaises(OverlapError, appointment.save)
 
-	return patient, medical_department, practitioner
+	def test_service_unit_capacity(self):
+		from erpnext.healthcare.doctype.patient_appointment.patient_appointment import MaximumCapacityError, OverlapError
+		practitioner = create_practitioner()
+		capacity = 3
+		overlap_service_unit_type = create_service_unit_type(id=10, allow_appointments=1, overlap_appointments=1)
+		overlap_service_unit = create_service_unit(id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity)
 
-def create_patient():
-	patient = frappe.db.exists('Patient', '_Test Patient')
-	if not patient:
-		patient = frappe.new_doc('Patient')
-		patient.first_name = '_Test Patient'
-		patient.sex = 'Female'
-		patient.save(ignore_permissions=True)
-		patient = patient.name
-	return patient
+		for i in range(0, capacity):
+			patient = create_patient(id=i)
+			create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid
+			appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) # overlap
+			self.assertRaises(OverlapError, appointment.save)
+
+		patient = create_patient(id=capacity)
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0)
+		self.assertRaises(MaximumCapacityError, appointment.save)
+
+
+def create_healthcare_docs(id=0):
+	patient = create_patient(id)
+	practitioner = create_practitioner(id)
+
+	return patient, practitioner
+
+
+def create_patient(id=0):
+	if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}):
+		patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name'])
+		return patient
+
+	patient = frappe.new_doc('Patient')
+	patient.first_name = f'_Test Patient {str(id)}'
+	patient.sex = 'Female'
+	patient.save(ignore_permissions=True)
+
+	return patient.name
+
+
+def create_medical_department(id=0):
+	if frappe.db.exists('Medical Department', f'_Test Medical Department {str(id)}'):
+		return f'_Test Medical Department {str(id)}'
+
+	medical_department = frappe.new_doc('Medical Department')
+	medical_department.department = f'_Test Medical Department {str(id)}'
+	medical_department.save(ignore_permissions=True)
+
+	return medical_department.name
+
+
+def create_practitioner(id=0, medical_department=None):
+	if frappe.db.exists('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}):
+		practitioner = frappe.db.get_value('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}, ['name'])
+		return practitioner
+
+	practitioner = frappe.new_doc('Healthcare Practitioner')
+	practitioner.first_name = f'_Test Healthcare Practitioner {str(id)}'
+	practitioner.gender = 'Female'
+	practitioner.department = medical_department or create_medical_department(id)
+	practitioner.op_consulting_charge = 500
+	practitioner.inpatient_visit_charge = 500
+	practitioner.save(ignore_permissions=True)
+
+	return practitioner.name
+
 
 def create_encounter(appointment):
 	if appointment:
@@ -270,8 +334,10 @@
 		encounter.company = appointment.company
 		encounter.save()
 		encounter.submit()
+
 		return encounter
 
+
 def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0,
 	service_unit=None, appointment_type=None, save=1, department=None):
 	item = create_healthcare_service_items()
@@ -284,6 +350,7 @@
 	appointment.appointment_date = appointment_date
 	appointment.company = '_Test Company'
 	appointment.duration = 15
+
 	if service_unit:
 		appointment.service_unit = service_unit
 	if invoice:
@@ -294,11 +361,14 @@
 		appointment.procedure_template = create_clinical_procedure_template().get('name')
 	if save:
 		appointment.save(ignore_permissions=True)
+
 	return appointment
 
+
 def create_healthcare_service_items():
 	if frappe.db.exists('Item', 'HLC-SI-001'):
 		return 'HLC-SI-001'
+
 	item = frappe.new_doc('Item')
 	item.item_code = 'HLC-SI-001'
 	item.item_name = 'Consulting Charges'
@@ -306,11 +376,14 @@
 	item.is_stock_item = 0
 	item.stock_uom = 'Nos'
 	item.save()
+
 	return item.name
 
+
 def create_clinical_procedure_template():
 	if frappe.db.exists('Clinical Procedure Template', 'Knee Surgery and Rehab'):
 		return frappe.get_doc('Clinical Procedure Template', 'Knee Surgery and Rehab')
+
 	template = frappe.new_doc('Clinical Procedure Template')
 	template.template = 'Knee Surgery and Rehab'
 	template.item_code = 'Knee Surgery and Rehab'
@@ -319,8 +392,10 @@
 	template.description = 'Knee Surgery and Rehab'
 	template.rate = 50000
 	template.save()
+
 	return template
 
+
 def create_appointment_type(args=None):
 	if not args:
 		args =  frappe.local.form_dict
@@ -359,3 +434,30 @@
 			"roles": roles,
 		}).insert()
 	return user
+
+
+def create_service_unit_type(id=0, allow_appointments=1, overlap_appointments=0):
+	if frappe.db.exists('Healthcare Service Unit Type', f'_Test Service Unit Type {str(id)}'):
+		return f'_Test Service Unit Type {str(id)}'
+
+	service_unit_type = frappe.new_doc('Healthcare Service Unit Type')
+	service_unit_type.service_unit_type = f'_Test Service Unit Type {str(id)}'
+	service_unit_type.allow_appointments = allow_appointments
+	service_unit_type.overlap_appointments = overlap_appointments
+	service_unit_type.save(ignore_permissions=True)
+
+	return service_unit_type.name
+
+
+def create_service_unit(id=0, service_unit_type=None, service_unit_capacity=0):
+	if frappe.db.exists('Healthcare Service Unit', f'_Test Service Unit {str(id)}'):
+		return f'_Test service_unit {str(id)}'
+
+	service_unit = frappe.new_doc('Healthcare Service Unit')
+	service_unit.is_group = 0
+	service_unit.healthcare_service_unit_name= f'_Test Service Unit {str(id)}'
+	service_unit.service_unit_type = service_unit_type or create_service_unit_type(id)
+	service_unit.service_unit_capacity = service_unit_capacity
+	service_unit.save(ignore_permissions=True)
+
+	return service_unit.name
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
index f8ccc8a..5b7d8d6 100644
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
+++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
@@ -5,7 +5,7 @@
 import unittest
 import frappe
 from frappe.utils import nowdate
-from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment
+from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment, create_medical_department
 from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
 class TestPatientMedicalRecord(unittest.TestCase):
@@ -15,7 +15,8 @@
 		make_pos_profile()
 
 	def test_medical_record(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
+		medical_department = create_medical_department()
 		appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
 		encounter = create_encounter(appointment)
 
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index 113fa51..983fba9 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -8,11 +8,13 @@
 from frappe.utils import getdate, flt, nowdate
 from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
 from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
-from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient, create_appointment
+from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import \
+	create_healthcare_docs, create_patient, create_appointment, create_medical_department
 
 class TestTherapyPlan(unittest.TestCase):
 	def test_creation_on_encounter_submission(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
+		medical_department = create_medical_department()
 		encounter = create_encounter(patient, medical_department, practitioner)
 		self.assertTrue(frappe.db.exists('Therapy Plan', encounter.therapy_plan))
 
@@ -28,8 +30,9 @@
 		frappe.get_doc(session).submit()
 		self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
 
-		patient, medical_department, practitioner = create_healthcare_docs()
-		appointment = create_appointment(patient, practitioner, nowdate())
+		patient, practitioner = create_healthcare_docs()
+		appointment = create_appointment(patient, practitioner, nowdate())		
+
 		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
 		session = frappe.get_doc(session)
 		session.submit()
diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
index a5dad29..80fc83f 100644
--- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
+++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
@@ -34,7 +34,8 @@
 		})
 		therapy_type.save()
 	else:
-		therapy_type = frappe.get_doc('Therapy Type', 'Basic Rehab')
+		therapy_type = frappe.get_doc('Therapy Type', therapy_type)
+
 	return therapy_type
 
 def create_exercise_type():
@@ -47,4 +48,7 @@
 			'description': 'Squat and Rise'
 		})
 		exercise_type.save()
+	else:
+		exercise_type = frappe.get_doc('Exercise Type', exercise_type)
+
 	return exercise_type
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
index 4b461f1..fae5ece 100644
--- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
+++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
@@ -25,7 +25,7 @@
 			'from_date': getdate(),
 			'to_date': getdate(),
 			'patient': '_Test IPD Patient',
-			'service_unit': 'Test Service Unit Ip Occupancy - _TC'
+			'service_unit': '_Test Service Unit Ip Occupancy - _TC'
 		}
 
 		report = execute(filters)
@@ -42,7 +42,7 @@
 				'date': getdate(),
 				'time': datetime.timedelta(seconds=32400),
 				'is_completed': 0,
-				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+				'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
 			},
 			{
 				'patient': '_Test IPD Patient',
@@ -55,7 +55,7 @@
 				'date': getdate(),
 				'time': datetime.timedelta(seconds=50400),
 				'is_completed': 0,
-				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+				'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
 			},
 			{
 				'patient': '_Test IPD Patient',
@@ -68,7 +68,7 @@
 				'date': getdate(),
 				'time': datetime.timedelta(seconds=75600),
 				'is_completed': 0,
-				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+				'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
 			}
 		]
 
@@ -83,7 +83,7 @@
 			'from_date': getdate(),
 			'to_date': getdate(),
 			'patient': '_Test IPD Patient',
-			'service_unit': 'Test Service Unit Ip Occupancy - _TC',
+			'service_unit': '_Test Service Unit Ip Occupancy - _TC',
 			'show_completed_orders': 0
 		}
 
@@ -119,7 +119,7 @@
 	ip_record.expected_length_of_stay = 0
 	ip_record.save()
 	ip_record.reload()
-	service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+	service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy')
 	admit_patient(ip_record, service_unit, now_datetime())
 
 	ipmo = create_ipmo(patient)
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index d3d22c8..ffecf4d 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -543,58 +543,43 @@
 
 
 @frappe.whitelist()
-def get_children(doctype, parent, company, is_root=False):
-	parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")
+def get_children(doctype, parent=None, company=None, is_root=False):
+	parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
 	fields = [
-		"name as value",
-		"is_group as expandable",
-		"lft",
-		"rgt"
+		'name as value',
+		'is_group as expandable',
+		'lft',
+		'rgt'
 	]
-	# fields = [ "name", "is_group", "lft", "rgt" ]
-	filters = [["ifnull(`{0}`,'')".format(parent_fieldname), "=", "" if is_root else parent]]
+
+	filters = [["ifnull(`{0}`,'')".format(parent_fieldname),
+		'=', '' if is_root else parent]]
 
 	if is_root:
-		fields += ["service_unit_type"] if doctype == "Healthcare Service Unit" else []
-		filters.append(["company", "=", company])
-
+		fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else []
+		filters.append(['company', '=', company])
 	else:
-		fields += ["service_unit_type", "allow_appointments", "inpatient_occupancy", "occupancy_status"] if doctype == "Healthcare Service Unit" else []
-		fields += [parent_fieldname + " as parent"]
+		fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy',
+			'occupancy_status'] if doctype == 'Healthcare Service Unit' else []
+		fields += [parent_fieldname + ' as parent']
 
-	hc_service_units = frappe.get_list(doctype, fields=fields, filters=filters)
+	service_units = frappe.get_list(doctype, fields=fields, filters=filters)
+	for each in service_units:
+		if each['expandable'] == 1:  # group node
+			available_count = frappe.db.count('Healthcare Service Unit',  filters={
+				'parent_healthcare_service_unit': each['value'],
+				'inpatient_occupancy': 1})
 
-	if doctype == "Healthcare Service Unit":
-		for each in hc_service_units:
-			occupancy_msg = ""
-			if each["expandable"] == 1:
-				occupied = False
-				vacant = False
-				child_list = frappe.db.sql(
-					'''
-						SELECT
-							name, occupancy_status
-						FROM
-							`tabHealthcare Service Unit`
-						WHERE
-							inpatient_occupancy = 1
-							and lft > %s and rgt < %s
-					''', (each['lft'], each['rgt']))
+			if available_count > 0:
+				occupied_count = frappe.db.count('Healthcare Service Unit',  {
+					'parent_healthcare_service_unit': each['value'],
+					'inpatient_occupancy': 1,
+					'occupancy_status': 'Occupied'})
+				# set occupancy status of group node
+				each['occupied_of_available'] = str(
+					occupied_count) + ' Occupied of ' + str(available_count)
 
-				for child in child_list:
-					if not occupied:
-						occupied = 0
-					if child[1] == "Occupied":
-						occupied += 1
-					if not vacant:
-						vacant = 0
-					if child[1] == "Vacant":
-						vacant += 1
-				if vacant and occupied:
-					occupancy_total = vacant + occupied
-					occupancy_msg = str(occupied) + " Occupied out of " + str(occupancy_total)
-			each["occupied_out_of_vacant"] = occupancy_msg
-	return hc_service_units
+	return service_units
 
 
 @frappe.whitelist()
@@ -717,3 +702,40 @@
 		doc_html = "<div class='small'><div class='col-md-12 text-right'><a class='btn btn-default btn-xs' href='/app/Form/%s/%s'></a></div>" %(doctype, docname) + doc_html + '</div>'
 
 	return {'html': doc_html}
+
+
+def update_address_links(address, method):
+	'''
+	Hook validate Address
+	If Patient is linked in Address, also link the associated Customer
+	'''
+	if 'Healthcare' not in frappe.get_active_domains():
+		return
+
+	patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', address.links))
+
+	for link in patient_links:
+		customer = frappe.db.get_value('Patient', link.get('link_name'), 'customer')
+		if customer and not address.has_link('Customer', customer):
+			address.append('links', dict(link_doctype = 'Customer', link_name = customer))
+
+
+def update_patient_email_and_phone_numbers(contact, method):
+	'''
+	Hook validate Contact
+	Update linked Patients' primary mobile and phone numbers
+	'''
+	if 'Healthcare' not in frappe.get_active_domains():
+		return
+
+	if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone):
+		patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', contact.links))
+
+		for link in patient_links:
+			contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1)
+			if contact.email_id and contact.email_id != contact_details.get('email'):
+				frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id)
+			if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'):
+				frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no)
+			if contact.phone and contact.phone != contact_details.get('phone'):
+				frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone)
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 4854bfd..b1a64f9 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -290,7 +290,12 @@
 		"on_trash": "erpnext.regional.check_deletion_permission"
 	},
 	'Address': {
-		'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category']
+		'validate': [
+			'erpnext.regional.india.utils.validate_gstin_for_india',
+			'erpnext.regional.italy.utils.set_state_code',
+			'erpnext.regional.india.utils.update_gst_category',
+			'erpnext.healthcare.utils.update_address_links'
+		],
 	},
 	'Supplier': {
 		'validate': 'erpnext.regional.india.utils.validate_pan_for_india'
@@ -301,7 +306,7 @@
 	"Contact": {
 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
 		"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
-		"validate": "erpnext.crm.utils.update_lead_phone_numbers"
+		"validate": ["erpnext.crm.utils.update_lead_phone_numbers", "erpnext.healthcare.utils.update_patient_email_and_phone_numbers"]
 	},
 	"Email Unsubscribe": {
 		"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"