From 24055e1552c34a444c670c618a66fe70ef94efb8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 24 Feb 2020 19:09:50 +0530 Subject: [PATCH] refactor: Patient Appointment --- .../clinical_procedure/clinical_procedure.py | 65 +-- .../healthcare_settings.json | 146 +++---- .../healthcare_settings.py | 4 +- .../patient_appointment.js | 56 +-- .../patient_appointment.py | 412 +++++++++--------- erpnext/healthcare/utils.py | 42 +- 6 files changed, 361 insertions(+), 364 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index 7c6f4d5999..510849fb93 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -174,35 +174,42 @@ def create_stock_entry(doc): stock_entry.insert(ignore_permissions = True) stock_entry.submit() -@frappe.whitelist() -def create_procedure(appointment): - appointment = frappe.get_doc("Patient Appointment",appointment) - procedure = frappe.new_doc("Clinical Procedure") - procedure.appointment = appointment.name - procedure.patient = appointment.patient - procedure.patient_age = appointment.patient_age - procedure.patient_sex = appointment.patient_sex - procedure.procedure_template = appointment.procedure_template - procedure.prescription = appointment.procedure_prescription - procedure.practitioner = appointment.practitioner - procedure.invoiced = appointment.invoiced - procedure.medical_department = appointment.department - procedure.start_date = appointment.appointment_date - procedure.start_time = appointment.appointment_time - procedure.notes = appointment.notes - procedure.service_unit = appointment.service_unit - procedure.company = appointment.company - consume_stock = frappe.db.get_value("Clinical Procedure Template", appointment.procedure_template, "consume_stock") - if consume_stock == 1: - procedure.consume_stock = True - warehouse = False - if appointment.service_unit: - warehouse = frappe.db.get_value("Healthcare Service Unit", appointment.service_unit, "warehouse") - if not warehouse: - warehouse = frappe.db.get_value("Stock Settings", None, "default_warehouse") - if warehouse: - procedure.warehouse = warehouse - return procedure.as_dict() +def make_procedure(source_name, target_doc=None): + def set_missing_values(source, target): + consume_stock = frappe.db.get_value("Clinical Procedure Template", source.procedure_template, "consume_stock") + if consume_stock: + target.consume_stock = 1 + warehouse = None + if source.service_unit: + warehouse = frappe.db.get_value("Healthcare Service Unit", source.service_unit, "warehouse") + if not warehouse: + warehouse = frappe.db.get_value("Stock Settings", None, "default_warehouse") + if warehouse: + target.warehouse = warehouse + + doc = get_mapped_doc('Patient Appointment', source_name, { + 'Patient Appointment': { + 'doctype': 'Clinical Procedure', + 'field_map': [ + ['appointment', 'name'], + ['patient', 'patient'], + ['patient_age', 'patient_age'], + ['patient_sex', 'patient_sex'], + ['procedure_template', 'procedure_template'], + ['prescription', 'procedure_prescription'], + ['practitioner', 'practitioner'], + ['medical_department', 'department'], + ['start_date', 'appointment_date'], + ['start_time', 'appointment_time'], + ['notes', 'notes'], + ['service_unit', 'service_unit'], + ['company', 'company'], + ['invoiced', 'invoiced'] + ] + } + }, target_doc, set_missing_values) + + return doc def insert_clinical_procedure_to_medical_record(doc): subject = cstr(doc.procedure_template) diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json index 6e3fdee79a..fafec22ed9 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json @@ -13,7 +13,7 @@ "column_break_9", "collect_registration_fee", "registration_fee", - "manage_appointment_invoice_automatically", + "automate_appointment_invoicing", "max_visit", "valid_days", "healthcare_service_items", @@ -32,15 +32,15 @@ "lab_test_approval_required", "custom_signature_in_print", "out_patient_sms_alerts", - "reg_msg", - "reg_sms", - "app_con", - "app_con_msg", - "no_con", + "send_registration_msg", + "registration_msg", + "send_appointment_confirmation", + "appointment_confirmation_msg", + "avoid_confirmation", "column_break_16", - "app_rem", - "app_rem_msg", - "rem_before", + "send_appointment_reminder", + "appointment_reminder_msg", + "remind_before", "laboratory_sms_alerts", "sms_printed", "column_break_28", @@ -77,13 +77,6 @@ "mandatory_depends_on": "eval:doc.collect_registration_fee == 1", "options": "Currency" }, - { - "default": "0", - "description": "Manage Appointment Invoice submit and cancel automatically for Patient Encounter", - "fieldname": "manage_appointment_invoice_automatically", - "fieldtype": "Check", - "label": "Invoice Appointments Automatically" - }, { "description": "The number of free follow ups (Patient Encounters in valid days) allowed", "fieldname": "max_visit", @@ -131,66 +124,10 @@ "fieldtype": "Section Break", "label": "Out Patient SMS Alerts" }, - { - "default": "0", - "fieldname": "reg_sms", - "fieldtype": "Check", - "label": "Patient Registration" - }, - { - "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.id}} . Please note this ID for future reference. \nThank You, Get well soon!", - "depends_on": "reg_sms", - "fieldname": "reg_msg", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Registration Message" - }, - { - "default": "0", - "fieldname": "app_con", - "fieldtype": "Check", - "label": "Appointment Confirmation" - }, - { - "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} by {{doc.start_dt}} at {{doc.company}}.\nThank you, Good day!", - "depends_on": "app_con", - "fieldname": "app_con_msg", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Confirmation Message" - }, - { - "default": "0", - "depends_on": "app_con", - "description": "Do not confirm if appointment is created for the same day", - "fieldname": "no_con", - "fieldtype": "Check", - "label": "Avoid Confirmation" - }, { "fieldname": "column_break_16", "fieldtype": "Column Break" }, - { - "default": "0", - "fieldname": "app_rem", - "fieldtype": "Check", - "label": "Appointment Reminder" - }, - { - "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_time}} at {{doc.company}}.\nThank you, Good day!\n", - "depends_on": "app_rem", - "fieldname": "app_rem_msg", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Reminder Message" - }, - { - "depends_on": "app_rem", - "fieldname": "rem_before", - "fieldtype": "Time", - "label": "Remind Before" - }, { "collapsible": 1, "fieldname": "sb_in_ac", @@ -291,11 +228,74 @@ "fieldtype": "Select", "label": "Patient Name By", "options": "Patient Name\nNaming Series" + }, + { + "default": "0", + "description": "Manage Appointment Invoice submit and cancel automatically for Patient Encounter", + "fieldname": "automate_appointment_invoicing", + "fieldtype": "Check", + "label": "Automate Appointment Invoicing" + }, + { + "default": "0", + "fieldname": "send_registration_msg", + "fieldtype": "Check", + "label": "Patient Registration" + }, + { + "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.id}} . Please note this ID for future reference. \nThank You, Get well soon!", + "depends_on": "send_registration_msg", + "fieldname": "registration_msg", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Registration Message" + }, + { + "default": "0", + "fieldname": "send_appointment_confirmation", + "fieldtype": "Check", + "label": "Appointment Confirmation" + }, + { + "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} by {{doc.start_dt}} at {{doc.company}}.\nThank you, Good day!", + "depends_on": "send_appointment_confirmation", + "fieldname": "appointment_confirmation_msg", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Confirmation Message" + }, + { + "default": "0", + "depends_on": "send_appointment_confirmation", + "description": "Do not confirm if appointment is created for the same day", + "fieldname": "avoid_confirmation", + "fieldtype": "Check", + "label": "Avoid Confirmation" + }, + { + "default": "0", + "fieldname": "send_appointment_reminder", + "fieldtype": "Check", + "label": "Appointment Reminder" + }, + { + "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_time}} at {{doc.company}}.\nThank you, Good day!\n", + "depends_on": "send_appointment_reminder", + "fieldname": "appointment_reminder_msg", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Reminder Message" + }, + { + "depends_on": "send_appointment_reminder", + "fieldname": "remind_before", + "fieldtype": "Time", + "label": "Remind Before" } ], "issingle": 1, "links": [], - "modified": "2020-01-29 14:31:56.983534", + "modified": "2020-02-24 10:51:23.015896", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Settings", diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py index c26ab0c8cb..a16fceb74d 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py @@ -46,12 +46,12 @@ def get_sms_text(doc): return sms_text def send_registration_sms(doc): - if frappe.db.get_single_value('Healthcare Settings', 'reg_sms'): + if frappe.db.get_single_value('Healthcare Settings', 'send_registration_msg'): if doc.mobile: context = {'doc': doc, 'alert': doc, 'comments': None} if doc.get('_comments'): context['comments'] = json.loads(doc.get('_comments')) - messages = frappe.db.get_single_value('Healthcare Settings', 'reg_msg') + messages = frappe.db.get_single_value('Healthcare Settings', 'registration_msg') messages = frappe.render_template(messages, context) number = [doc.mobile] send_sms(number,messages) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index a332d4c6e1..7c6b01166c 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -40,8 +40,8 @@ frappe.ui.form.on('Patient Appointment', { if (frm.is_new()) { frm.page.set_primary_action(__('Check Availability'), function() { - frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, 'manage_appointment_invoice_automatically', (settings) => { - if (settings.manage_appointment_invoice_automatically) { + frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, 'automate_appointment_invoicing', (settings) => { + if (settings.automate_appointment_invoicing) { if (!frm.doc.mode_of_payment) { frappe.msgprint({ title: __('Not Allowed'), @@ -76,7 +76,7 @@ frappe.ui.form.on('Patient Appointment', { frm.add_custom_button(__('Patient History'), function() { frappe.route_options = {'patient': frm.doc.patient}; frappe.set_route('patient_history'); - },__('View')); + }, __('View')); } if (frm.doc.status == 'Open' || (frm.doc.status == 'Scheduled' && !frm.doc.__islocal)) { @@ -88,18 +88,24 @@ frappe.ui.form.on('Patient Appointment', { }); if (frm.doc.procedure_template) { - frm.add_custom_button(__('Procedure'), function(){ - create_procedure(frm); - },'Create'); + frm.add_custom_button(__('Clinical Procedure'), function(){ + frappe.model.open_mapped_doc({ + method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.create_procedure', + frm: frm, + }); + }, __('Create')); } else { frm.add_custom_button(__('Patient Encounter'), function() { - create_encounter(frm); - },'Create'); + frappe.model.open_mapped_doc({ + method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.make_encounter', + frm: frm, + }); + }, __('Create')); } frm.add_custom_button(__('Vital Signs'), function() { create_vital_signs(frm); - },'Create'); + }, __('Create')); } if (frm.doc.status == 'Pending') { @@ -111,8 +117,8 @@ frappe.ui.form.on('Patient Appointment', { }); } - frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, 'manage_appointment_invoice_automatically', (settings) => { - if (settings.manage_appointment_invoice_automatically) { + frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, 'automate_appointment_invoicing', (settings) => { + if (settings.automate_appointment_invoicing) { frm.set_df_property('mode_of_payment', 'hidden', 0); frm.set_df_property('paid_amount', 'hidden', 0); frm.set_df_property('mode_of_payment', 'reqd', 1); @@ -373,34 +379,6 @@ let show_procedure_templates = function(frm, result){ d.show(); }; -let create_procedure = function(frm) { - let doc = frm.doc; - frappe.call({ - method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.create_procedure', - args: {appointment: doc.name}, - callback: function(data) { - if (!data.exc) { - let doclist = frappe.model.sync(data.message); - frappe.set_route('Form', doclist[0].doctype, doclist[0].name); - } - } - }); -}; - -let create_encounter = function(frm) { - let doc = frm.doc; - frappe.call({ - method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.create_encounter', - args: {appointment: doc.name}, - callback: function(data){ - if (!data.exc) { - let doclist = frappe.model.sync(data.message); - frappe.set_route('Form', doclist[0].doctype, doclist[0].name); - } - } - }); -}; - let create_vital_signs = function(frm) { if (!frm.doc.patient) { frappe.throw(__('Please select patient')); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 1a4b7df3c2..76feb22ada 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -7,25 +7,37 @@ import frappe from frappe.model.document import Document import json from frappe.utils import getdate, add_days, get_time +from frappe.model.mapper import get_mapped_doc from frappe import _ import datetime from frappe.core.doctype.sms_settings.sms_settings import send_sms 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 validity_exists, service_item_and_practitioner_charge +from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account +from erpnext.healthcare.utils import check_validity_exists, get_service_item_and_practitioner_charge class PatientAppointment(Document): + def validate(self): + self.validate_overlaps() + + def after_insert(self): + invoice_appointment(self) + self.update_prescription_details() + self.check_fee_validity() + send_confirmation_msg(self) + def on_update(self): - today = datetime.date.today() + today = getdate() appointment_date = getdate(self.appointment_date) - # If appointment created for today set as open + # If appointment is created for today set status as Open if today == appointment_date: - frappe.db.set_value("Patient Appointment", self.name, "status", "Open") + frappe.db.set_value('Patient Appointment', self.name, 'status', 'Open') self.reload() - def validate(self): - end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) + datetime.timedelta(minutes=float(self.duration)) + def validate_overlaps(self): + end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \ + + datetime.timedelta(minutes=float(self.duration)) + overlaps = frappe.db.sql(""" select name, practitioner, patient, appointment_time, duration @@ -41,133 +53,145 @@ class PatientAppointment(Document): self.appointment_time, end_time.time(), self.appointment_time, end_time.time(), self.appointment_time)) if overlaps: - frappe.throw(_("""Appointment overlaps with {0}.
{1} has appointment scheduled - with {2} at {3} having {4} minute(s) duration.""").format(overlaps[0][0], overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])) + overlapping_details = _('Appointment overlaps with {0}').format(overlaps[0][0]) + overlapping_details += '
' + 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)) - def after_insert(self): + def update_prescription_details(self): if self.procedure_prescription: - frappe.db.set_value("Procedure Prescription", self.procedure_prescription, "appointment_booked", True) + frappe.db.set_value('Procedure Prescription', self.procedure_prescription, 'appointment_booked', 1) if self.procedure_template: - comments = frappe.db.get_value("Procedure Prescription", self.procedure_prescription, "comments") + comments = frappe.db.get_value('Procedure Prescription', self.procedure_prescription, 'comments') if comments: - frappe.db.set_value("Patient Appointment", self.name, "notes", comments) + frappe.db.set_value('Patient Appointment', self.name, 'notes', comments) + + def check_fee_validity(self): # Check fee validity exists - appointment = self - validity_exist = validity_exists(appointment.practitioner, appointment.patient) - if validity_exist: - fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0]) + validity_exists = check_validity_exists(self.practitioner, self.patient) + if validity_exists: + fee_validity = frappe.get_doc('Fee Validity', validity_exists[0][0]) # Check if the validity is valid - appointment_date = getdate(appointment.appointment_date) + appointment_date = getdate(self.appointment_date) if (fee_validity.valid_till >= appointment_date) and (fee_validity.visited < fee_validity.max_visit): visited = fee_validity.visited + 1 - frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited) + frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited) if fee_validity.ref_invoice: - frappe.db.set_value("Patient Appointment", appointment.name, "invoiced", True) - frappe.msgprint(_("{0} has fee validity till {1}").format(appointment.patient, fee_validity.valid_till)) - confirm_sms(self) + frappe.db.set_value('Patient Appointment', self.name, 'invoiced', True) + frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till)) - if frappe.db.get_value("Healthcare Settings", None, "manage_appointment_invoice_automatically") == '1' and \ - frappe.db.get_value("Patient Appointment", self.name, "invoiced") != 1: - invoice_appointment(self) -@frappe.whitelist() def invoice_appointment(appointment_doc): if not appointment_doc.name: - return False - sales_invoice = frappe.new_doc("Sales Invoice") - sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer") - sales_invoice.appointment = appointment_doc.name - sales_invoice.due_date = getdate() - sales_invoice.is_pos = True - sales_invoice.company = appointment_doc.company - sales_invoice.debit_to = get_receivable_account(appointment_doc.company) + return - item_line = sales_invoice.append("items") - service_item, practitioner_charge = service_item_and_practitioner_charge(appointment_doc) - item_line.item_code = service_item - item_line.description = "Consulting Charges: " + appointment_doc.practitioner - item_line.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company) - item_line.rate = practitioner_charge - item_line.amount = practitioner_charge - item_line.qty = 1 - item_line.reference_dt = "Patient Appointment" - item_line.reference_dn = appointment_doc.name + if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing') and \ + not frappe.db.get_value('Patient Appointment', appointment_doc.name, 'invoiced'): + sales_invoice = frappe.new_doc('Sales Invoice') + sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') + sales_invoice.appointment = appointment_doc.name + sales_invoice.due_date = getdate() + sales_invoice.is_pos = True + sales_invoice.company = appointment_doc.company + sales_invoice.debit_to = get_receivable_account(appointment_doc.company) - payments_line = sales_invoice.append("payments") - payments_line.mode_of_payment = appointment_doc.mode_of_payment - payments_line.amount = appointment_doc.paid_amount + item = sales_invoice.append('items', {}) + item = get_appointment_item(appointment_doc, item) - sales_invoice.set_missing_values(for_validate = True) + payment = sales_invoice.append('payments', {}) + payments_line.mode_of_payment = appointment_doc.mode_of_payment + payments_line.amount = appointment_doc.paid_amount - sales_invoice.save(ignore_permissions=True) - sales_invoice.submit() - frappe.msgprint(_("Sales Invoice {0} created as paid".format(sales_invoice.name)), alert=True) - frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name) + sales_invoice.set_missing_values(for_validate=True) -def appointment_cancel(appointment_id): - appointment = frappe.get_doc("Patient Appointment", appointment_id) - # If invoiced --> fee_validity update with -1 visit + sales_invoice.save(ignore_permissions=True) + sales_invoice.submit() + frappe.msgprint(_('Sales Invoice {0} created as paid'.format(sales_invoice.name)), alert=True) + frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name) + + +def get_appointment_item(appointment_doc, item): + service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment_doc) + item.item_code = service_item + item.description = _('Consulting Charges: {0}').format(appointment_doc.practitioner) + item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company) + item.rate = practitioner_charge + item.amount = practitioner_charge + item.qty = 1 + item.reference_dt = 'Patient Appointment' + item.reference_dn = appointment_doc.name + return item + + +def cancel_appointment(appointment_id): + appointment = frappe.get_doc('Patient Appointment', appointment_id) + # If invoiced --> fee_validity update visit as -1 if appointment.invoiced: - sales_invoice = exists_sales_invoice(appointment) + sales_invoice = check_sales_invoice_exists(appointment) if sales_invoice and cancel_sales_invoice(sales_invoice): frappe.msgprint( - _("Appointment {0} and Sales Invoice {1} cancelled".format(appointment.name, sales_invoice.name)) + _('Appointment {0} and Sales Invoice {1} cancelled'.format(appointment.name, sales_invoice.name)) ) else: - validity = validity_exists(appointment.practitioner, appointment.patient) + validity = check_validity_exists(appointment.practitioner, appointment.patient) if validity: - fee_validity = frappe.get_doc("Fee Validity", validity[0][0]) - if appointment_valid_in_fee_validity(appointment, fee_validity.valid_till, True, fee_validity.ref_invoice): + fee_validity = frappe.get_doc('Fee Validity', validity) + if validate_appointment_in_fee_validity(appointment, fee_validity.valid_till, fee_validity.ref_invoice): visited = fee_validity.visited - 1 - frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited) + frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited) frappe.msgprint( - _("Appointment cancelled, Please review and cancel the invoice {0}".format(fee_validity.ref_invoice)) + _('Appointment cancelled. Please review and cancel the invoice {0}'.format(fee_validity.ref_invoice)) ) else: - frappe.msgprint(_("Appointment cancelled")) + frappe.msgprint(_('Appointment Cancelled')) else: - frappe.msgprint(_("Appointment cancelled")) + frappe.msgprint(_('Appointment Cancelled')) else: - frappe.msgprint(_("Appointment cancelled")) + frappe.msgprint(_('Appointment Cancelled')) -def appointment_valid_in_fee_validity(appointment, valid_end_date, invoiced, ref_invoice): - valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days") - max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit") +def validate_appointment_in_fee_validity(appointment, valid_end_date, ref_invoice): + valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') + max_visit = frappe.db.get_single_value('Healthcare Settings', 'max_visit') valid_start_date = add_days(getdate(valid_end_date), -int(valid_days)) - # Appointments which has same fee validity range with the appointment - appointments = frappe.get_list("Patient Appointment",{'patient': appointment.patient, 'invoiced': invoiced, - 'appointment_date':("<=", getdate(valid_end_date)), 'appointment_date':(">=", getdate(valid_start_date)), - 'practitioner': appointment.practitioner}, order_by="appointment_date desc", limit=int(max_visit)) + # Appointments which have same fee validity range with the appointment + appointments = frappe.get_list('Patient Appointment', { + 'patient': appointment.patient, + 'invoiced': True, + 'appointment_date': ('<=', getdate(valid_end_date)), + 'appointment_date':('>=', getdate(valid_start_date)), + 'practitioner': appointment.practitioner + }, order_by='appointment_date desc', limit=int(max_visit)) if appointments and len(appointments) > 0: appointment_obj = appointments[len(appointments)-1] - sales_invoice = exists_sales_invoice(appointment_obj) + sales_invoice = check_sales_invoice_exists(appointment_obj) if sales_invoice.name == ref_invoice: return True return False def cancel_sales_invoice(sales_invoice): - if frappe.db.get_value("Healthcare Settings", None, "manage_appointment_invoice_automatically") == '1': + if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'): if len(sales_invoice.items) == 1: sales_invoice.cancel() return True return False -def exists_sales_invoice_item(appointment): +def check_si_item_exists(appointment): return frappe.db.exists( - "Sales Invoice Item", + 'Sales Invoice Item', { - "reference_dt": "Patient Appointment", - "reference_dn": appointment.name + 'reference_dt': 'Patient Appointment', + 'reference_dn': appointment.name } ) -def exists_sales_invoice(appointment): - sales_item_exist = exists_sales_invoice_item(appointment) - if sales_item_exist: - sales_invoice = frappe.get_doc("Sales Invoice", frappe.db.get_value("Sales Invoice Item", sales_item_exist, "parent")) +def check_sales_invoice_exists(appointment): + si_item = check_si_item_exists(appointment) + if si_item: + sales_invoice = frappe.get_doc('Sales Invoice', frappe.db.get_value('Sales Invoice Item', si_item, 'parent')) return sales_invoice return False @@ -181,176 +205,164 @@ def get_availability_data(date, practitioner): """ date = getdate(date) - weekday = date.strftime("%A") + weekday = date.strftime('%A') - available_slots = [] - slot_details = [] - practitioner_schedule = None + practitioner_doc = frappe.get_doc('Healthcare Practitioner', practitioner) + check_employee_wise_availability(date, practitioner_doc) + + if practitioner_doc.practitioner_schedules: + slot_details = get_available_slots(practitioner_doc.practitioner_schedules, date) + else: + frappe.throw(_('{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master'.format(practitioner))) + + + if not slot_details: + # TODO: return available slots in nearby dates + frappe.throw(_('Healthcare Practitioner not available on {0}').format(weekday)) + + return {'slot_details': slot_details} + +def check_employee_wise_availability(date, practitioner_doc): employee = None - - practitioner_obj = frappe.get_doc("Healthcare Practitioner", practitioner) - - # Get practitioner employee relation - if practitioner_obj.employee: - employee = practitioner_obj.employee - elif practitioner_obj.user_id: - if frappe.db.exists({ - "doctype": "Employee", - "user_id": practitioner_obj.user_id - }): - employee = frappe.get_doc("Employee", {"user_id": practitioner_obj.user_id}).name + if practitioner_doc.employee: + employee = practitioner_doc.employee + elif practitioner_doc.user_id: + employee = frappe.db.get_value('Employee', {'user_id': practitioner_doc.user_id}, 'name') if employee: - # Check if it is Holiday + # check holiday if is_holiday(employee, date): - frappe.throw(_("{0} is a company holiday".format(date))) + frappe.throw(_('{0} is a company holiday'.format(date))) - # Check if He/She on Leave + # check leave status leave_record = frappe.db.sql("""select half_day from `tabLeave Application` where employee = %s and %s between from_date and to_date and docstatus = 1""", (employee, date), as_dict=True) if leave_record: if leave_record[0].half_day: - frappe.throw(_("{0} on Half day Leave on {1}").format(practitioner, date)) + frappe.throw(_('{0} is on a Half day Leave on {1}').format(practitioner_doc.name, date)) else: - frappe.throw(_("{0} on Leave on {1}").format(practitioner, date)) + frappe.throw(_('{0} is on Leave on {1}').format(practitioner_doc.name, date)) - # get practitioners schedule - if practitioner_obj.practitioner_schedules: - for schedule in practitioner_obj.practitioner_schedules: - if schedule.schedule: - practitioner_schedule = frappe.get_doc("Practitioner Schedule", schedule.schedule) - else: - frappe.throw(_("{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master".format(practitioner))) +def get_available_slots(practitioner_schedules, date): + available_slots = [] + slot_details = [] + weekday = date.strftime('%A') + practitioner = None - if practitioner_schedule: - available_slots = [] - for t in practitioner_schedule.time_slots: - if weekday == t.day: - available_slots.append(t) + for schedule_entry in practitioner_schedules: + if schedule_entry.schedule: + practitioner_schedule = frappe.get_doc('Practitioner Schedule', schedule_entry.schedule) + else: + frappe.throw(_('{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner').format( + frappe.bold(practitioner))) - if available_slots: - appointments = [] + if practitioner_schedule: + available_slots = [] + for time_slot in practitioner_schedule.time_slots: + if weekday == time_slot.day: + available_slots.append(time_slot) - if schedule.service_unit: - slot_name = schedule.schedule+" - "+schedule.service_unit - allow_overlap = frappe.get_value('Healthcare Service Unit', schedule.service_unit, 'overlap_appointments') - if allow_overlap: - # fetch all appointments to practitioner by service unit - appointments = frappe.get_all( - "Patient Appointment", - filters={"practitioner": practitioner, "service_unit": schedule.service_unit, "appointment_date": date, "status": ["not in",["Cancelled"]]}, - fields=["name", "appointment_time", "duration", "status"]) - else: - # fetch all appointments to service unit - appointments = frappe.get_all( - "Patient Appointment", - filters={"service_unit": schedule.service_unit, "appointment_date": date, "status": ["not in",["Cancelled"]]}, - fields=["name", "appointment_time", "duration", "status"]) - else: - slot_name = schedule.schedule - # fetch all appointments to practitioner without service unit - appointments = frappe.get_all( - "Patient Appointment", - filters={"practitioner": practitioner, "service_unit": '', "appointment_date": date, "status": ["not in",["Cancelled"]]}, - fields=["name", "appointment_time", "duration", "status"]) + if available_slots: + appointments = [] + # fetch all appointments to practitioner by service unit + filters = { + 'practitioner': practitioner, + 'service_unit': schedule_entry.service_unit, + 'appointment_date': date, + 'status': ['not in',['Cancelled']] + } - slot_details.append({"slot_name":slot_name, "service_unit":schedule.service_unit, - "avail_slot":available_slots, 'appointments': appointments}) + 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') + if not allow_overlap: + # fetch all appointments to service unit + filters.pop('practitioner') + else: + slot_name = schedule_entry.schedule + # fetch all appointments to practitioner without service unit + filters['practitioner'] = practitioner + filters['service_unit'] = '' - else: - frappe.throw(_("{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master".format(practitioner))) + appointments = frappe.get_all( + 'Patient Appointment', + filters=filters, + fields=['name', 'appointment_time', 'duration', 'status']) - if not available_slots and not slot_details: - # TODO: return available slots in nearby dates - frappe.throw(_("Healthcare Practitioner not available on {0}").format(weekday)) + slot_details.append({'slot_name':slot_name, 'service_unit':schedule_entry.service_unit, + 'avail_slot':available_slots, 'appointments': appointments}) - return { - "slot_details": slot_details - } + return slot_details @frappe.whitelist() def update_status(appointment_id, status): - frappe.db.set_value("Patient Appointment", appointment_id, "status", status) + frappe.db.set_value('Patient Appointment', appointment_id, 'status', status) appointment_booked = True - if status == "Cancelled": + if status == 'Cancelled': appointment_booked = False - appointment_cancel(appointment_id) + cancel_appointment(appointment_id) - procedure_prescription = frappe.db.get_value("Patient Appointment", appointment_id, "procedure_prescription") + procedure_prescription = frappe.db.get_value('Patient Appointment', appointment_id, 'procedure_prescription') if procedure_prescription: - frappe.db.set_value("Procedure Prescription", procedure_prescription, "appointment_booked", appointment_booked) + frappe.db.set_value('Procedure Prescription', procedure_prescription, 'appointment_booked', appointment_booked) -@frappe.whitelist() -def set_open_appointments(): - today = getdate() - frappe.db.sql( - "update `tabPatient Appointment` set status='Open' where status = 'Scheduled'" - " and appointment_date = %s", today) - - -@frappe.whitelist() -def set_pending_appointments(): - today = getdate() - frappe.db.sql( - "update `tabPatient Appointment` set status='Pending' where status in " - "('Scheduled','Open') and appointment_date < %s", today) - - -def confirm_sms(doc): - if frappe.db.get_value("Healthcare Settings", None, "app_con") == '1': - message = frappe.db.get_value("Healthcare Settings", None, "app_con_msg") +def send_confirmation_msg(doc): + if frappe.db.get_single_value('Healthcare Settings', 'send_appointment_confirmation'): + message = frappe.db.get_single_value('Healthcare Settings', 'appointment_confirmation_msg') send_message(doc, message) @frappe.whitelist() -def create_encounter(appointment): - appointment = frappe.get_doc("Patient Appointment", appointment) - encounter = frappe.new_doc("Patient Encounter") - encounter.appointment = appointment.name - encounter.patient = appointment.patient - encounter.practitioner = appointment.practitioner - encounter.visit_department = appointment.department - encounter.patient_sex = appointment.patient_sex - encounter.encounter_date = appointment.appointment_date - if appointment.invoiced: - encounter.invoiced = True - return encounter.as_dict() +def make_encounter(source_name, target_doc=None): + doc = get_mapped_doc('Patient Appointment', source_name, { + 'Patient Appointment': { + 'doctype': 'Patient Encounter', + 'field_map': [ + ['appointment', 'name'], + ['patient', 'patient'], + ['practitioner', 'practitioner'], + ['visit_department', 'department'], + ['patient_sex', 'patient_sex'], + ['encounter_date', 'appointment_date'], + ['invoiced', 'invoiced'] + ] + } + }, target_doc) + return doc def remind_appointment(): - if frappe.db.get_value("Healthcare Settings", None, "app_rem") == '1': - rem_before = datetime.datetime.strptime(frappe.get_value("Healthcare Settings", None, "rem_before"), "%H:%M:%S") - rem_dt = datetime.datetime.now() + datetime.timedelta( - hours=rem_before.hour, minutes=rem_before.minute, seconds=rem_before.second) + if frappe.db.get_single_value('Healthcare Settings', 'send_appointment_reminder'): + remind_before = datetime.datetime.strptime(frappe.get_single_value('Healthcare Settings', 'remind_before'), '%H:%M:%S') + reminder_dt = datetime.datetime.now() + datetime.timedelta( + hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second) appointment_list = frappe.db.sql( - "select name from `tabPatient Appointment` where start_dt between %s and %s and reminded = 0 ", - (datetime.datetime.now(), rem_dt) + 'select name from `tabPatient Appointment` where start_dt between %s and %s and reminded = 0 ', + (datetime.datetime.now(), reminder_dt) ) for i in range(0, len(appointment_list)): - doc = frappe.get_doc("Patient Appointment", appointment_list[i][0]) - message = frappe.db.get_value("Healthcare Settings", None, "app_rem_msg") + doc = frappe.get_doc('Patient Appointment', appointment_list[i][0]) + message = frappe.db.get_single_value('Healthcare Settings', 'appointment_reminder_msg') send_message(doc, message) - frappe.db.set_value("Patient Appointment", doc.name, "reminded",1) - + frappe.db.set_value('Patient Appointment', doc.name, 'reminded',1) def send_message(doc, message): - patient = frappe.get_doc("Patient", doc.patient) + patient = frappe.get_doc('Patient', doc.patient) if patient.mobile: - context = {"doc": doc, "alert": doc, "comments": None} - if doc.get("_comments"): - context["comments"] = json.loads(doc.get("_comments")) + context = {'doc': doc, 'alert': doc, 'comments': None} + if doc.get('_comments'): + context['comments'] = json.loads(doc.get('_comments')) # jinja to string convertion happens here message = frappe.render_template(message, context) number = [patient.mobile] send_sms(number, message) - @frappe.whitelist() def get_events(start, end, filters=None): """Returns events for Gantt / Calendar view rendering. diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index bae0e4df6a..3a08e3350f 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -47,7 +47,7 @@ def get_healthcare_services_to_invoice(patient): if not practitioner_exist_in_list: valid_till = patient_appointment_obj.appointment_date + datetime.timedelta(days=int(valid_days)) visits = 0 - validity_exist = validity_exists(patient_appointment_obj.practitioner, patient_appointment_obj.patient) + validity_exist = check_validity_exists(patient_appointment_obj.practitioner, patient_appointment_obj.patient) if validity_exist: fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0]) valid_till = fee_validity.valid_till @@ -60,7 +60,7 @@ def get_healthcare_services_to_invoice(patient): income_account = None service_item = None if patient_appointment_obj.practitioner: - service_item, practitioner_charge = service_item_and_practitioner_charge(patient_appointment_obj) + service_item, practitioner_charge = get_service_item_and_practitioner_charge(patient_appointment_obj) income_account = get_income_account(patient_appointment_obj.practitioner, patient_appointment_obj.company) item_to_invoice.append({'reference_type': 'Patient Appointment', 'reference_name': patient_appointment_obj.name, 'service': service_item, 'rate': practitioner_charge, @@ -75,7 +75,7 @@ def get_healthcare_services_to_invoice(patient): income_account = None service_item = None if encounter_obj.practitioner: - service_item, practitioner_charge = service_item_and_practitioner_charge(encounter_obj) + service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter_obj) income_account = get_income_account(encounter_obj.practitioner, encounter_obj.company) item_to_invoice.append({'reference_type': 'Patient Encounter', 'reference_name': encounter_obj.name, @@ -159,9 +159,9 @@ def get_healthcare_services_to_invoice(patient): else: frappe.throw(_("The Patient {0} do not have customer refrence to invoice").format(patient.name)) -def service_item_and_practitioner_charge(doc): - is_ip = doc_is_ip(doc) - if is_ip: +def get_service_item_and_practitioner_charge(doc): + is_inpatient = doc_is_inpatient(doc) + if is_inpatient: service_item = get_practitioner_service_item(doc.practitioner, "inpatient_visit_charge_item") if not service_item: service_item = get_healthcare_service_item("inpatient_visit_charge_item") @@ -170,26 +170,26 @@ def service_item_and_practitioner_charge(doc): if not service_item: service_item = get_healthcare_service_item("op_consulting_charge_item") if not service_item: - throw_config_service_item(is_ip) + throw_config_service_item(is_inpatient) - practitioner_charge = get_practitioner_charge(doc.practitioner, is_ip) + practitioner_charge = get_practitioner_charge(doc.practitioner, is_inpatient) if not practitioner_charge: - throw_config_practitioner_charge(is_ip, doc.practitioner) + throw_config_practitioner_charge(is_inpatient, doc.practitioner) return service_item, practitioner_charge -def throw_config_service_item(is_ip): +def throw_config_service_item(is_inpatient): service_item_lable = "Out Patient Consulting Charge Item" - if is_ip: + if is_inpatient: service_item_lable = "Inpatient Visit Charge Item" msg = _(("Please Configure {0} in ").format(service_item_lable) \ + """Healthcare Settings""") frappe.throw(msg) -def throw_config_practitioner_charge(is_ip, practitioner): +def throw_config_practitioner_charge(is_inpatient, practitioner): charge_name = "OP Consulting Charge" - if is_ip: + if is_inpatient: charge_name = "Inpatient Visit Charge" msg = _(("Please Configure {0} for Healthcare Practitioner").format(charge_name) \ @@ -202,14 +202,14 @@ def get_practitioner_service_item(practitioner, service_item_field): def get_healthcare_service_item(service_item_field): return frappe.db.get_value("Healthcare Settings", None, service_item_field) -def doc_is_ip(doc): - is_ip = False +def doc_is_inpatient(doc): + is_inpatient = False if doc.inpatient_record: - is_ip = True - return is_ip + is_inpatient = True + return is_inpatient -def get_practitioner_charge(practitioner, is_ip): - if is_ip: +def get_practitioner_charge(practitioner, is_inpatient): + if is_inpatient: practitioner_charge = frappe.db.get_value("Healthcare Practitioner", practitioner, "inpatient_visit_charge") else: practitioner_charge = frappe.db.get_value("Healthcare Practitioner", practitioner, "op_consulting_charge") @@ -271,7 +271,7 @@ def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field): doc_created = frappe.db.get_value(dt, {'prescription': ref_dn}) frappe.db.set_value(dt, doc_created, 'invoiced', invoiced) -def validity_exists(practitioner, patient): +def check_validity_exists(practitioner, patient): return frappe.db.exists({ "doctype": "Fee Validity", "practitioner": practitioner, @@ -279,7 +279,7 @@ def validity_exists(practitioner, patient): def manage_fee_validity(appointment_name, method, ref_invoice=None): appointment_doc = frappe.get_doc("Patient Appointment", appointment_name) - validity_exist = validity_exists(appointment_doc.practitioner, appointment_doc.patient) + validity_exist = check_validity_exists(appointment_doc.practitioner, appointment_doc.patient) do_not_update = False visited = 0 if validity_exist: