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>
This commit is contained in:
parent
e5e00700e5
commit
212eb4bc1a
@ -11,7 +11,7 @@ test_dependencies = ['Item']
|
||||
|
||||
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 @@ class TestClinicalProcedure(unittest.TestCase):
|
||||
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()
|
||||
|
@ -27,7 +27,7 @@ class TestFeeValidity(unittest.TestCase):
|
||||
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())
|
||||
|
@ -7,8 +7,8 @@ frappe.ui.form.on('Healthcare Service Unit', {
|
||||
|
||||
// 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 @@ frappe.ui.form.on('Healthcare Service Unit', {
|
||||
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 @@ frappe.ui.form.on('Healthcare Service Unit', {
|
||||
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', '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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": [
|
||||
{
|
||||
|
@ -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 @@ class HealthcareServiceUnit(NestedSet):
|
||||
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
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -68,8 +68,8 @@ let change_item_code = function(frm, doc) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -151,7 +151,7 @@ def get_healthcare_service_unit(unit_name=None):
|
||||
|
||||
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 @@ def get_healthcare_service_unit(unit_name=None):
|
||||
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 @@ def get_service_unit_type():
|
||||
|
||||
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
|
||||
|
@ -26,31 +26,39 @@ frappe.ui.form.on('Patient', {
|
||||
}
|
||||
|
||||
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 @@ frappe.ui.form.on('Patient', 'dob', function(frm) {
|
||||
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('');
|
||||
}
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -8,24 +8,27 @@ from frappe import _
|
||||
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 @@ class Patient(Document):
|
||||
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 @@ class Patient(Document):
|
||||
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 @@ class Patient(Document):
|
||||
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 @@ class Patient(Document):
|
||||
|
||||
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 @@ def make_invoice(patient, company):
|
||||
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 @@ def get_patient_detail(patient):
|
||||
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 @@ def get_timeline_data(doctype, name):
|
||||
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
|
||||
|
@ -6,22 +6,33 @@ def get_data():
|
||||
'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']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.set_query('patient', function () {
|
||||
frm.set_query('patient', function() {
|
||||
return {
|
||||
filters: {'status': 'Active'}
|
||||
filters: { 'status': 'Active' }
|
||||
};
|
||||
});
|
||||
|
||||
@ -64,7 +64,7 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
} 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 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
|
||||
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 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
});
|
||||
|
||||
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 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
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 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
},
|
||||
|
||||
practitioner: function(frm) {
|
||||
if (frm.doc.practitioner ) {
|
||||
if (frm.doc.practitioner) {
|
||||
frm.events.set_payment_details(frm);
|
||||
}
|
||||
},
|
||||
@ -230,7 +230,7 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
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 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
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 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
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 check_and_set_availability = function(frm) {
|
||||
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 check_and_set_availability = function(frm) {
|
||||
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 @@ let check_and_set_availability = function(frm) {
|
||||
}
|
||||
},
|
||||
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 get_prescribed_procedure = function(frm) {
|
||||
}
|
||||
};
|
||||
|
||||
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 @@ let show_procedure_templates = function(frm, result){
|
||||
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 @@ let show_procedure_templates = function(frm, result){
|
||||
});
|
||||
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 @@ let show_therapy_types = function(frm, result) {
|
||||
]
|
||||
});
|
||||
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 @@ let show_therapy_types = function(frm, result) {
|
||||
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 @@ let create_vital_signs = function(frm) {
|
||||
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();
|
||||
|
@ -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",
|
||||
|
@ -15,6 +15,11 @@ from erpnext.hr.doctype.employee.employee import is_holiday
|
||||
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 @@ class PatientAppointment(Document):
|
||||
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 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)
|
||||
|
||||
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'))
|
||||
|
||||
def validate_service_unit(self):
|
||||
if self.inpatient_record and self.service_unit:
|
||||
@ -325,6 +353,8 @@ def get_available_slots(practitioner_doc, date):
|
||||
|
||||
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 @@ def get_available_slots(practitioner_doc, date):
|
||||
}
|
||||
|
||||
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 @@ def get_available_slots(practitioner_doc, date):
|
||||
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
|
||||
|
||||
|
@ -16,9 +16,11 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
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)
|
||||
|
||||
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_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
|
||||
|
||||
def create_encounter(appointment):
|
||||
if appointment:
|
||||
@ -270,8 +334,10 @@ def create_encounter(appointment):
|
||||
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 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
|
||||
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 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
|
||||
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 @@ def create_healthcare_service_items():
|
||||
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 @@ def create_clinical_procedure_template():
|
||||
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 @@ def create_user(email=None, roles=None):
|
||||
"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
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
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 @@ class TestPatientMedicalRecord(unittest.TestCase):
|
||||
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)
|
||||
|
||||
|
@ -8,11 +8,13 @@ import unittest
|
||||
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 @@ class TestTherapyPlan(unittest.TestCase):
|
||||
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()
|
||||
|
@ -34,7 +34,8 @@ def create_therapy_type():
|
||||
})
|
||||
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 @@ def create_exercise_type():
|
||||
'description': 'Squat and Rise'
|
||||
})
|
||||
exercise_type.save()
|
||||
else:
|
||||
exercise_type = frappe.get_doc('Exercise Type', exercise_type)
|
||||
|
||||
return exercise_type
|
||||
|
@ -25,7 +25,7 @@ class TestInpatientMedicationOrders(unittest.TestCase):
|
||||
'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 @@ class TestInpatientMedicationOrders(unittest.TestCase):
|
||||
'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 @@ class TestInpatientMedicationOrders(unittest.TestCase):
|
||||
'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 @@ class TestInpatientMedicationOrders(unittest.TestCase):
|
||||
'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 @@ class TestInpatientMedicationOrders(unittest.TestCase):
|
||||
'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 @@ def create_records(patient):
|
||||
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)
|
||||
|
@ -543,58 +543,43 @@ def get_drugs_to_invoice(encounter):
|
||||
|
||||
|
||||
@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 @@ def render_doc_as_html(doctype, docname, exclude_fields = []):
|
||||
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)
|
||||
|
@ -290,7 +290,12 @@ doc_events = {
|
||||
"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 @@ doc_events = {
|
||||
"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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user