From 2f2c09bd9860226c1aed579d75fdc34812f43e07 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 17 Mar 2020 18:10:39 +0530 Subject: [PATCH] fix: wrong fee validity calculation and payment fields visibility in appointment --- .../doctype/fee_validity/fee_validity.json | 28 +++--- .../doctype/fee_validity/fee_validity.py | 32 ++----- .../fee_validity_reference/__init__.py | 0 .../fee_validity_reference.json | 32 +++++++ .../fee_validity_reference.py | 10 ++ .../healthcare_settings.json | 17 +++- .../patient_appointment.js | 81 +++++++++------- .../patient_appointment.py | 73 +++++---------- erpnext/healthcare/utils.py | 92 +++---------------- 9 files changed, 173 insertions(+), 192 deletions(-) create mode 100644 erpnext/healthcare/doctype/fee_validity_reference/__init__.py create mode 100644 erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json create mode 100644 erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json index a65f46fc79a..84321694dd9 100644 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json +++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.json @@ -13,13 +13,14 @@ "patient", "column_break_3", "status", + "section_break_5", "section_break_3", "max_visits", "visited", - "valid_till", + "ref_appointments", "column_break_6", - "ref_invoice", - "start_date" + "start_date", + "valid_till" ], "fields": [ { @@ -50,12 +51,6 @@ "fieldtype": "Date", "label": "Valid till" }, - { - "fieldname": "ref_invoice", - "fieldtype": "Link", - "label": "Reference Invoice", - "options": "Sales Invoice" - }, { "fieldname": "section_break_3", "fieldtype": "Section Break", @@ -84,15 +79,26 @@ "read_only": 1 }, { - "fetch_from": "ref_invoice.posting_date", + "fetch_from": "ref_appointment.appointment_date", "fieldname": "start_date", "fieldtype": "Date", "label": "Start Date", "read_only": 1 + }, + { + "fieldname": "ref_appointments", + "fieldtype": "Table MultiSelect", + "label": "Reference Appointments", + "options": "Fee Validity Reference" + }, + { + "collapsible": 1, + "fieldname": "section_break_5", + "fieldtype": "Section Break" } ], "links": [], - "modified": "2020-03-09 23:14:08.581821", + "modified": "2020-03-14 21:42:40.766973", "modified_by": "Administrator", "module": "Healthcare", "name": "Fee Validity", diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py index b7e76b0ed40..fc15410207f 100644 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py @@ -23,32 +23,20 @@ class FeeValidity(Document): elif valid_till < today: self.status = 'Expired' - -def update_fee_validity(fee_validity, date, ref_invoice=None): - max_visits = frappe.db.get_single_value("Healthcare Settings", "max_visits") - valid_days = frappe.db.get_single_value("Healthcare Settings", "valid_days") - if not valid_days: - valid_days = 1 - if not max_visits: - max_visits = 1 - date = getdate(date) - valid_till = date + datetime.timedelta(days=int(valid_days)) - fee_validity.max_visits = max_visits +def create_fee_validity(appointment): + fee_validity = frappe.new_doc('Fee Validity') + fee_validity.practitioner = appointment.practitioner + fee_validity.patient = appointment.patient + fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1 + valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1 fee_validity.visited = 1 - fee_validity.valid_till = valid_till - fee_validity.ref_invoice = ref_invoice + fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days)) + fee_validity.append('ref_appointments', { + 'appointment': appointment.name + }) fee_validity.save(ignore_permissions=True) return fee_validity - -def create_fee_validity(practitioner, patient, date, ref_invoice=None): - fee_validity = frappe.new_doc("Fee Validity") - fee_validity.practitioner = practitioner - fee_validity.patient = patient - fee_validity = update_fee_validity(fee_validity, date, ref_invoice) - return fee_validity - - def update_validity_status(): docs = frappe.get_all('Fee Validity', filters={'status': ['not in', ['Completed', 'Expired']]}) for doc in docs: diff --git a/erpnext/healthcare/doctype/fee_validity_reference/__init__.py b/erpnext/healthcare/doctype/fee_validity_reference/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json new file mode 100644 index 00000000000..40f128e973d --- /dev/null +++ b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-03-13 16:08:42.859996", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "appointment" + ], + "fields": [ + { + "fieldname": "appointment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient Appointment", + "options": "Patient Appointment", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-15 00:27:02.076470", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Fee Validity Reference", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py new file mode 100644 index 00000000000..c8192808320 --- /dev/null +++ b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class FeeValidityReference(Document): + pass diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json index 2d03c88fa73..bbd9ae303af 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json @@ -14,6 +14,7 @@ "collect_registration_fee", "registration_fee", "automate_appointment_invoicing", + "enable_free_follow_ups", "max_visits", "valid_days", "healthcare_service_items", @@ -78,10 +79,12 @@ "options": "Currency" }, { + "depends_on": "eval:doc.enable_free_follow_ups == 1", "description": "Time period (Valid number of days) for free consultations", "fieldname": "valid_days", "fieldtype": "Int", - "label": "Valid number of days" + "label": "Valid number of days", + "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1" }, { "collapsible": 1, @@ -287,15 +290,23 @@ "label": "Remind Before" }, { + "depends_on": "eval:doc.enable_free_follow_ups == 1", "description": "The number of free follow ups (Patient Encounters in valid days) allowed", "fieldname": "max_visits", "fieldtype": "Int", - "label": "Number of Patient Encounters in valid days" + "label": "Number of Patient Encounters in valid days", + "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1" + }, + { + "default": "0", + "fieldname": "enable_free_follow_ups", + "fieldtype": "Check", + "label": "Enable Free Follow-ups" } ], "issingle": 1, "links": [], - "modified": "2020-03-09 17:43:23.251559", + "modified": "2020-03-13 14:34:40.962503", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Settings", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 6b264d40bbb..82d70f8e9dd 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -40,24 +40,6 @@ 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'}, 'automate_appointment_invoicing', (settings) => { - if (settings.automate_appointment_invoicing) { - if (!frm.doc.mode_of_payment) { - frappe.msgprint({ - title: __('Not Allowed'), - message: __('Please select a Mode of Payment first'), - indicator: 'red' - }); - } - if (!frm.doc.paid_amount) { - frappe.msgprint({ - title: __('Not Allowed'), - message: __('Please set the Paid Amount first'), - indicator: 'red' - }); - } - } - }); if (!frm.doc.patient) { frappe.msgprint({ title: __('Not Allowed'), @@ -65,7 +47,33 @@ frappe.ui.form.on('Patient Appointment', { indicator: 'red' }); } else { - check_and_set_availability(frm); + frappe.call({ + method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', + args: {'patient': frm.doc.patient}, + callback: function(data) { + if (data.message == true) { + if (frm.doc.mode_of_payment && frm.doc.paid_amount) { + check_and_set_availability(frm); + } + if (!frm.doc.mode_of_payment) { + frappe.msgprint({ + title: __('Not Allowed'), + message: __('Please select a Mode of Payment first'), + indicator: 'red' + }); + } + if (!frm.doc.paid_amount) { + frappe.msgprint({ + title: __('Not Allowed'), + message: __('Please set the Paid Amount first'), + indicator: 'red' + }); + } + } else { + check_and_set_availability(frm); + } + } + }); } }); } else { @@ -107,8 +115,12 @@ frappe.ui.form.on('Patient Appointment', { create_vital_signs(frm); }, __('Create')); } + }, - frm.events.toggle_payment_fields(frm); + patient: function(frm) { + if (frm.doc.patient) { + frm.trigger('toggle_payment_fields'); + } }, get_procedure_from_encounter: function(frm) { @@ -116,17 +128,24 @@ frappe.ui.form.on('Patient Appointment', { }, toggle_payment_fields: function(frm) { - frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, ['automate_appointment_invoicing'], (settings) => { - if (settings.automate_appointment_invoicing == 1) { - 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); - frm.set_df_property('paid_amount', 'reqd', 1); - } else { - frm.set_df_property('mode_of_payment', 'hidden', 1); - frm.set_df_property('paid_amount', 'hidden', 1); - frm.set_df_property('mode_of_payment', 'reqd', 0); - frm.set_df_property('paid_amount', 'reqd', 0); + frappe.call({ + method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', + args: {'patient': frm.doc.patient}, + callback: function(data) { + if (data.message.fee_validity) { + // if fee validity exists and automated appointment invoicing is enabled, + // show payment fields as non-mandatory + 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', 0); + frm.set_df_property('paid_amount', 'reqd', 0); + } else { + // if automated appointment invoicing is disabled, hide fields + frm.toggle_display('mode_of_payment', data.message ? 1 : 0); + frm.toggle_display('paid_amount', data.message ? 1 : 0); + frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0); + frm.toggle_reqd('paid_amount', data.message ? 1 :0); + } } }); } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index e8e65ed8887..1cf90c67861 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -13,7 +13,7 @@ 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 check_fee_validity, get_service_item_and_practitioner_charge +from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity class PatientAppointment(Document): def validate(self): @@ -84,19 +84,28 @@ class PatientAppointment(Document): frappe.db.set_value('Patient Appointment', self.name, 'notes', comments) def update_fee_validity(self): - fee_validity = check_fee_validity(self) + fee_validity = manage_fee_validity(self) if fee_validity: - visited = fee_validity.visited + 1 - frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited) - if fee_validity.ref_invoice: - frappe.db.set_value('Patient Appointment', self.name, 'invoiced', True) - frappe.db.set_value('Patient Appointment', self.name, 'ref_sales_invoice', fee_validity.ref_invoice) frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till)) +@frappe.whitelist() +def check_payment_fields_reqd(patient): + automate_invoicing = frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing') + free_follow_ups = frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') + if automate_invoicing: + if free_follow_ups: + fee_validity = frappe.db.exists('Fee Validity', {'patient': patient, 'status': 'Ongoing'}) + if fee_validity: + return {'fee_validity': fee_validity} + return True + return False + def invoice_appointment(appointment_doc): - if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing') and \ - not frappe.db.get_value('Patient Appointment', appointment_doc.name, 'invoiced'): + automate_invoicing = frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing') + appointment_invoiced = frappe.db.get_value('Patient Appointment', appointment_doc.name, 'invoiced') + fee_validity = check_fee_validity(appointment_doc) + if automate_invoicing and not appointment_invoiced and not fee_validity: sales_invoice = frappe.new_doc('Sales Invoice') sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') sales_invoice.appointment = appointment_doc.name @@ -136,51 +145,19 @@ def get_appointment_item(appointment_doc, 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 = 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)) - ) + msg = _('Appointment {0} and Sales Invoice {1} cancelled').format(appointment.name, sales_invoice.name) else: - validity = check_fee_validity(appointment.practitioner, appointment.patient) - if validity: - 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.msgprint( - _('Appointment cancelled. Please review and cancel the invoice {0}'.format(fee_validity.ref_invoice)) - ) - else: - frappe.msgprint(_('Appointment Cancelled')) - else: - frappe.msgprint(_('Appointment Cancelled')) + msg = _('Appointment Cancelled. Please review and cancel the invoice {0}').format(fee_validity.ref_invoice) else: - frappe.msgprint(_('Appointment Cancelled')) + fee_validity = manage_fee_validity(appointment) + msg = _('Appointment Cancelled.') + if fee_validity: + msg += _('Fee Validity {0} updated.').format(fee_validity.name) - -def validate_appointment_in_fee_validity(appointment, valid_end_date, ref_invoice): - valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') - max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') - valid_start_date = add_days(getdate(valid_end_date), -int(valid_days)) - - # 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_visits)) - - if appointments and len(appointments) > 0: - appointment_obj = appointments[len(appointments)-1] - sales_invoice = check_sales_invoice_exists(appointment_obj) - if sales_invoice.name == ref_invoice: - return True - return False + frappe.msgprint(msg) def cancel_sales_invoice(sales_invoice): diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 3c4e78346d6..d1b5b7c04d6 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -9,7 +9,7 @@ from frappe import _ import math from frappe.utils import time_diff_in_hours, rounded, getdate, add_days from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account -from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity, update_fee_validity +from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple @frappe.whitelist() @@ -317,7 +317,6 @@ def set_invoiced(item, method, ref_invoice=None): if frappe.db.get_value('Patient Appointment', item.reference_dn, 'procedure_template'): dt_from_appointment = 'Clinical Procedure' else: - manage_fee_validity(item.reference_dn, method, ref_invoice) dt_from_appointment = 'Patient Encounter' manage_doc_for_appointment(dt_from_appointment, item.reference_dn, invoiced) @@ -349,7 +348,8 @@ def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field): def check_fee_validity(appointment): validity = frappe.db.exists('Fee Validity', { 'practitioner': appointment.practitioner, - 'patient': appointment.patient + 'patient': appointment.patient, + 'status': 'Ongoing' }) if not validity: return @@ -359,83 +359,21 @@ def check_fee_validity(appointment): if fee_validity.valid_till >= appointment_date and fee_validity.visited < fee_validity.max_visits: return fee_validity - -def manage_fee_validity(appointment_name, method, ref_invoice=None): - appointment_doc = frappe.get_doc('Patient Appointment', appointment_name) - fee_validity = check_fee_validity(appointment_doc) - do_not_update = False - visited = 0 +def manage_fee_validity(appointment): + fee_validity = check_fee_validity(appointment) if fee_validity: - if method == 'on_cancel' and appointment_doc.status != 'Closed': - if ref_invoice == fee_validity.ref_invoice: - visited = fee_validity.visited - 1 - if visited < 0: - visited = 0 - frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited) - do_not_update = True - elif method == 'on_submit' and fee_validity.visited < fee_validity.max_visits: - visited = fee_validity.visited + 1 - frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited) - do_not_update = True + if appointment.status == 'Cancelled' and fee_validity.visited > 0: + fee_validity.visited -= 1 + frappe.db.delete('Fee Validity Reference', {'appointment': appointment.name}) else: - do_not_update = False - - if not do_not_update: - fee_validity = update_fee_validity(fee_validity, appointment_doc.appointment_date, ref_invoice) + fee_validity.visited += 1 + fee_validity.append('ref_appointments', { + 'appointment': appointment.name + }) + fee_validity.save(ignore_permissions=True) else: - fee_validity = create_fee_validity(appointment_doc.practitioner, appointment_doc.patient, appointment_doc.appointment_date, ref_invoice) - - visited = fee_validity.visited - mark_appointments_as_invoiced(fee_validity, ref_invoice, method, appointment_doc, visited) - - if method == 'on_cancel': - ref_invoice_in_fee_validity = frappe.db.get_value('Fee Validity', fee_validity.name, 'ref_invoice') - if ref_invoice_in_fee_validity == ref_invoice: - frappe.delete_doc('Fee Validity', fee_validity.name) - - -def mark_appointments_as_invoiced(fee_validity, ref_invoice, method, appointment_doc, visited): - if method == 'on_cancel': - invoiced = True - else: - invoiced = False - - patient_appointments = appointments_valid_in_fee_validity(appointment_doc, invoiced) - if patient_appointments and fee_validity: - visit = visited - for appointment in patient_appointments: - if method == 'on_cancel' and appointment.status != 'Closed': - if ref_invoice == fee_validity.ref_invoice: - visited -= 1 - if visited < 0: - visited = 0 - frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited) - frappe.db.set_value('Patient Appointment', appointment.name, 'invoiced', False) - manage_doc_for_appointment('Patient Encounter', appointment.name, False) - elif method == 'on_submit' and int(fee_validity.max_visits) > visit: - if ref_invoice == fee_validity.ref_invoice: - visited += 1 - frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited) - frappe.db.set_value('Patient Appointment', appointment.name, 'invoiced', True) - manage_doc_for_appointment('Patient Encounter', appointment.name, True) - if ref_invoice == fee_validity.ref_invoice: - visit = visit + 1 - - -def appointments_valid_in_fee_validity(appointment, invoiced): - valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') - max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') - if int(max_visits) < 1: - max_visits = 1 - valid_days_date = add_days(getdate(appointment.appointment_date), int(valid_days)) - - return frappe.get_list('Patient Appointment',{ - 'patient': appointment.patient, - 'invoiced': invoiced, - 'appointment_date':('<=', valid_days_date), - 'appointment_date':('>=', getdate(appointment.appointment_date)), - 'practitioner': appointment.practitioner - }, order_by='appointment_date', limit=int(max_visits)-1) + fee_validity = create_fee_validity(appointment) + return fee_validity def manage_doc_for_appointment(dt_from_appointment, appointment, invoiced):