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/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)