Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | # Copyright (c) 2018, earthians and contributors |
| 3 | # For license information, please see license.txt |
| 4 | |
| 5 | from __future__ import unicode_literals |
Chillar Anand | 915b343 | 2021-09-02 16:44:59 +0530 | [diff] [blame] | 6 | |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 7 | import json |
Chillar Anand | 915b343 | 2021-09-02 16:44:59 +0530 | [diff] [blame] | 8 | import math |
| 9 | |
| 10 | import frappe |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 11 | from frappe import _ |
Chillar Anand | 915b343 | 2021-09-02 16:44:59 +0530 | [diff] [blame] | 12 | from frappe.utils import cstr, rounded, time_diff_in_hours |
Rucha Mahabal | 5e3c51b | 2020-11-30 13:35:00 +0530 | [diff] [blame] | 13 | from frappe.utils.formatters import format_value |
Chillar Anand | 915b343 | 2021-09-02 16:44:59 +0530 | [diff] [blame] | 14 | |
Rucha Mahabal | 2f2c09b | 2020-03-17 18:10:39 +0530 | [diff] [blame] | 15 | from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity |
Chillar Anand | 915b343 | 2021-09-02 16:44:59 +0530 | [diff] [blame] | 16 | from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account |
Jamsheer | 0ae100b | 2018-08-01 14:29:43 +0530 | [diff] [blame] | 17 | from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 18 | |
Chillar Anand | 915b343 | 2021-09-02 16:44:59 +0530 | [diff] [blame] | 19 | |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 20 | @frappe.whitelist() |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 21 | def get_healthcare_services_to_invoice(patient, company): |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 22 | patient = frappe.get_doc('Patient', patient) |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 23 | items_to_invoice = [] |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 24 | if patient: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 25 | validate_customer_created(patient) |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 26 | # Customer validated, build a list of billable services |
| 27 | items_to_invoice += get_appointments_to_invoice(patient, company) |
| 28 | items_to_invoice += get_encounters_to_invoice(patient, company) |
| 29 | items_to_invoice += get_lab_tests_to_invoice(patient, company) |
| 30 | items_to_invoice += get_clinical_procedures_to_invoice(patient, company) |
| 31 | items_to_invoice += get_inpatient_services_to_invoice(patient, company) |
Rucha Mahabal | 434791e | 2020-10-24 14:20:38 +0530 | [diff] [blame] | 32 | items_to_invoice += get_therapy_plans_to_invoice(patient, company) |
Rucha Mahabal | d730451 | 2020-04-23 00:52:47 +0530 | [diff] [blame] | 33 | items_to_invoice += get_therapy_sessions_to_invoice(patient, company) |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 34 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 35 | return items_to_invoice |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 36 | |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 37 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 38 | def validate_customer_created(patient): |
| 39 | if not frappe.db.get_value('Patient', patient.name, 'customer'): |
| 40 | msg = _("Please set a Customer linked to the Patient") |
Rushabh Mehta | 542bc01 | 2020-11-18 15:00:34 +0530 | [diff] [blame] | 41 | msg += " <b><a href='/app/Form/Patient/{0}'>{0}</a></b>".format(patient.name) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 42 | frappe.throw(msg, title=_('Customer Not Found')) |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 43 | |
Rucha Mahabal | 434791e | 2020-10-24 14:20:38 +0530 | [diff] [blame] | 44 | |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 45 | def get_appointments_to_invoice(patient, company): |
| 46 | appointments_to_invoice = [] |
| 47 | patient_appointments = frappe.get_list( |
| 48 | 'Patient Appointment', |
| 49 | fields = '*', |
anoop | 5903002 | 2020-07-28 21:15:54 +0530 | [diff] [blame] | 50 | filters = {'patient': patient.name, 'company': company, 'invoiced': 0, 'status': ['not in', 'Cancelled']}, |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 51 | order_by = 'appointment_date' |
| 52 | ) |
| 53 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 54 | for appointment in patient_appointments: |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 55 | # Procedure Appointments |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 56 | if appointment.procedure_template: |
| 57 | if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 58 | appointments_to_invoice.append({ |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 59 | 'reference_type': 'Patient Appointment', |
| 60 | 'reference_name': appointment.name, |
| 61 | 'service': appointment.procedure_template |
| 62 | }) |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 63 | # Consultation Appointments, should check fee validity |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 64 | else: |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 65 | if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \ |
| 66 | frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}): |
| 67 | continue # Skip invoicing, fee validty present |
| 68 | practitioner_charge = 0 |
| 69 | income_account = None |
| 70 | service_item = None |
| 71 | if appointment.practitioner: |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 72 | details = get_service_item_and_practitioner_charge(appointment) |
| 73 | service_item = details.get('service_item') |
| 74 | practitioner_charge = details.get('practitioner_charge') |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 75 | income_account = get_income_account(appointment.practitioner, appointment.company) |
| 76 | appointments_to_invoice.append({ |
| 77 | 'reference_type': 'Patient Appointment', |
| 78 | 'reference_name': appointment.name, |
| 79 | 'service': service_item, |
| 80 | 'rate': practitioner_charge, |
| 81 | 'income_account': income_account |
| 82 | }) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 83 | |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 84 | return appointments_to_invoice |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 85 | |
| 86 | |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 87 | def get_encounters_to_invoice(patient, company): |
Rucha Mahabal | 0f05925 | 2021-01-13 09:46:33 +0530 | [diff] [blame] | 88 | if not isinstance(patient, str): |
| 89 | patient = patient.name |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 90 | encounters_to_invoice = [] |
| 91 | encounters = frappe.get_list( |
| 92 | 'Patient Encounter', |
| 93 | fields=['*'], |
Rucha Mahabal | 0f05925 | 2021-01-13 09:46:33 +0530 | [diff] [blame] | 94 | filters={'patient': patient, 'company': company, 'invoiced': False, 'docstatus': 1} |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 95 | ) |
| 96 | if encounters: |
| 97 | for encounter in encounters: |
| 98 | if not encounter.appointment: |
| 99 | practitioner_charge = 0 |
| 100 | income_account = None |
| 101 | service_item = None |
| 102 | if encounter.practitioner: |
Rucha Mahabal | 1354197 | 2021-01-13 09:12:50 +0530 | [diff] [blame] | 103 | if encounter.inpatient_record and \ |
| 104 | frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'): |
| 105 | continue |
| 106 | |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 107 | details = get_service_item_and_practitioner_charge(encounter) |
| 108 | service_item = details.get('service_item') |
| 109 | practitioner_charge = details.get('practitioner_charge') |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 110 | income_account = get_income_account(encounter.practitioner, encounter.company) |
| 111 | |
| 112 | encounters_to_invoice.append({ |
| 113 | 'reference_type': 'Patient Encounter', |
| 114 | 'reference_name': encounter.name, |
| 115 | 'service': service_item, |
| 116 | 'rate': practitioner_charge, |
| 117 | 'income_account': income_account |
| 118 | }) |
| 119 | |
| 120 | return encounters_to_invoice |
| 121 | |
| 122 | |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 123 | def get_lab_tests_to_invoice(patient, company): |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 124 | lab_tests_to_invoice = [] |
| 125 | lab_tests = frappe.get_list( |
| 126 | 'Lab Test', |
| 127 | fields=['name', 'template'], |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 128 | filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 129 | ) |
| 130 | for lab_test in lab_tests: |
Rucha Mahabal | 131452c | 2020-04-27 10:52:38 +0530 | [diff] [blame] | 131 | item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable']) |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 132 | if is_billable: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 133 | lab_tests_to_invoice.append({ |
| 134 | 'reference_type': 'Lab Test', |
| 135 | 'reference_name': lab_test.name, |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 136 | 'service': item |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 137 | }) |
| 138 | |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 139 | lab_prescriptions = frappe.db.sql( |
| 140 | ''' |
| 141 | SELECT |
| 142 | lp.name, lp.lab_test_code |
| 143 | FROM |
| 144 | `tabPatient Encounter` et, `tabLab Prescription` lp |
| 145 | WHERE |
| 146 | et.patient=%s |
| 147 | and lp.parent=et.name |
| 148 | and lp.lab_test_created=0 |
| 149 | and lp.invoiced=0 |
| 150 | ''', (patient.name), as_dict=1) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 151 | |
| 152 | for prescription in lab_prescriptions: |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 153 | item, is_billable = frappe.get_cached_value('Lab Test Template', prescription.lab_test_code, ['item', 'is_billable']) |
| 154 | if prescription.lab_test_code and is_billable: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 155 | lab_tests_to_invoice.append({ |
| 156 | 'reference_type': 'Lab Prescription', |
| 157 | 'reference_name': prescription.name, |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 158 | 'service': item |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 159 | }) |
| 160 | |
| 161 | return lab_tests_to_invoice |
| 162 | |
| 163 | |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 164 | def get_clinical_procedures_to_invoice(patient, company): |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 165 | clinical_procedures_to_invoice = [] |
| 166 | procedures = frappe.get_list( |
| 167 | 'Clinical Procedure', |
| 168 | fields='*', |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 169 | filters={'patient': patient.name, 'company': company, 'invoiced': False} |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 170 | ) |
| 171 | for procedure in procedures: |
| 172 | if not procedure.appointment: |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 173 | item, is_billable = frappe.get_cached_value('Clinical Procedure Template', procedure.procedure_template, ['item', 'is_billable']) |
| 174 | if procedure.procedure_template and is_billable: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 175 | clinical_procedures_to_invoice.append({ |
| 176 | 'reference_type': 'Clinical Procedure', |
| 177 | 'reference_name': procedure.name, |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 178 | 'service': item |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 179 | }) |
| 180 | |
| 181 | # consumables |
| 182 | if procedure.invoice_separately_as_consumables and procedure.consume_stock \ |
| 183 | and procedure.status == 'Completed' and not procedure.consumption_invoiced: |
| 184 | |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 185 | service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 186 | if not service_item: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 187 | frappe.throw(_('Please configure Clinical Procedure Consumable Item in {0}').format( |
| 188 | frappe.utils.get_link_to_form('Healthcare Settings', 'Healthcare Settings')), |
| 189 | title=_('Missing Configuration')) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 190 | |
| 191 | clinical_procedures_to_invoice.append({ |
| 192 | 'reference_type': 'Clinical Procedure', |
| 193 | 'reference_name': procedure.name, |
| 194 | 'service': service_item, |
| 195 | 'rate': procedure.consumable_total_amount, |
| 196 | 'description': procedure.consumption_details |
| 197 | }) |
| 198 | |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 199 | procedure_prescriptions = frappe.db.sql( |
| 200 | ''' |
| 201 | SELECT |
| 202 | pp.name, pp.procedure |
| 203 | FROM |
| 204 | `tabPatient Encounter` et, `tabProcedure Prescription` pp |
| 205 | WHERE |
| 206 | et.patient=%s |
| 207 | and pp.parent=et.name |
| 208 | and pp.procedure_created=0 |
| 209 | and pp.invoiced=0 |
| 210 | and pp.appointment_booked=0 |
| 211 | ''', (patient.name), as_dict=1) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 212 | |
| 213 | for prescription in procedure_prescriptions: |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 214 | item, is_billable = frappe.get_cached_value('Clinical Procedure Template', prescription.procedure, ['item', 'is_billable']) |
| 215 | if is_billable: |
Rucha Mahabal | 197165f | 2020-03-26 17:29:50 +0530 | [diff] [blame] | 216 | clinical_procedures_to_invoice.append({ |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 217 | 'reference_type': 'Procedure Prescription', |
| 218 | 'reference_name': prescription.name, |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 219 | 'service': item |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 220 | }) |
| 221 | |
| 222 | return clinical_procedures_to_invoice |
| 223 | |
| 224 | |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 225 | def get_inpatient_services_to_invoice(patient, company): |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 226 | services_to_invoice = [] |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 227 | inpatient_services = frappe.db.sql( |
| 228 | ''' |
| 229 | SELECT |
| 230 | io.* |
| 231 | FROM |
| 232 | `tabInpatient Record` ip, `tabInpatient Occupancy` io |
| 233 | WHERE |
| 234 | ip.patient=%s |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 235 | and ip.company=%s |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 236 | and io.parent=ip.name |
| 237 | and io.left=1 |
| 238 | and io.invoiced=0 |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 239 | ''', (patient.name, company), as_dict=1) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 240 | |
| 241 | for inpatient_occupancy in inpatient_services: |
| 242 | service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 243 | service_unit_type = frappe.get_cached_doc('Healthcare Service Unit Type', service_unit_type) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 244 | if service_unit_type and service_unit_type.is_billable: |
| 245 | hours_occupied = time_diff_in_hours(inpatient_occupancy.check_out, inpatient_occupancy.check_in) |
| 246 | qty = 0.5 |
| 247 | if hours_occupied > 0: |
| 248 | actual_qty = hours_occupied / service_unit_type.no_of_hours |
| 249 | floor = math.floor(actual_qty) |
| 250 | decimal_part = actual_qty - floor |
| 251 | if decimal_part > 0.5: |
| 252 | qty = rounded(floor + 1, 1) |
| 253 | elif decimal_part < 0.5 and decimal_part > 0: |
| 254 | qty = rounded(floor + 0.5, 1) |
| 255 | if qty <= 0: |
| 256 | qty = 0.5 |
| 257 | services_to_invoice.append({ |
| 258 | 'reference_type': 'Inpatient Occupancy', |
| 259 | 'reference_name': inpatient_occupancy.name, |
| 260 | 'service': service_unit_type.item, 'qty': qty |
| 261 | }) |
| 262 | |
| 263 | return services_to_invoice |
| 264 | |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 265 | |
Rucha Mahabal | 434791e | 2020-10-24 14:20:38 +0530 | [diff] [blame] | 266 | def get_therapy_plans_to_invoice(patient, company): |
| 267 | therapy_plans_to_invoice = [] |
| 268 | therapy_plans = frappe.get_list( |
| 269 | 'Therapy Plan', |
| 270 | fields=['therapy_plan_template', 'name'], |
| 271 | filters={ |
| 272 | 'patient': patient.name, |
| 273 | 'invoiced': 0, |
| 274 | 'company': company, |
| 275 | 'therapy_plan_template': ('!=', '') |
| 276 | } |
| 277 | ) |
| 278 | for plan in therapy_plans: |
| 279 | therapy_plans_to_invoice.append({ |
| 280 | 'reference_type': 'Therapy Plan', |
| 281 | 'reference_name': plan.name, |
| 282 | 'service': frappe.db.get_value('Therapy Plan Template', plan.therapy_plan_template, 'linked_item') |
| 283 | }) |
| 284 | |
| 285 | return therapy_plans_to_invoice |
| 286 | |
| 287 | |
Rucha Mahabal | d730451 | 2020-04-23 00:52:47 +0530 | [diff] [blame] | 288 | def get_therapy_sessions_to_invoice(patient, company): |
Rucha Mahabal | eaa956b | 2020-04-22 13:07:12 +0530 | [diff] [blame] | 289 | therapy_sessions_to_invoice = [] |
Rucha Mahabal | 434791e | 2020-10-24 14:20:38 +0530 | [diff] [blame] | 290 | therapy_plans = frappe.db.get_all('Therapy Plan', {'therapy_plan_template': ('!=', '')}) |
| 291 | therapy_plans_created_from_template = [] |
| 292 | for entry in therapy_plans: |
| 293 | therapy_plans_created_from_template.append(entry.name) |
| 294 | |
Rucha Mahabal | eaa956b | 2020-04-22 13:07:12 +0530 | [diff] [blame] | 295 | therapy_sessions = frappe.get_list( |
| 296 | 'Therapy Session', |
| 297 | fields='*', |
Rucha Mahabal | 434791e | 2020-10-24 14:20:38 +0530 | [diff] [blame] | 298 | filters={ |
| 299 | 'patient': patient.name, |
| 300 | 'invoiced': 0, |
| 301 | 'company': company, |
| 302 | 'therapy_plan': ('not in', therapy_plans_created_from_template) |
| 303 | } |
Rucha Mahabal | eaa956b | 2020-04-22 13:07:12 +0530 | [diff] [blame] | 304 | ) |
| 305 | for therapy in therapy_sessions: |
| 306 | if not therapy.appointment: |
| 307 | if therapy.therapy_type and frappe.db.get_value('Therapy Type', therapy.therapy_type, 'is_billable'): |
| 308 | therapy_sessions_to_invoice.append({ |
| 309 | 'reference_type': 'Therapy Session', |
| 310 | 'reference_name': therapy.name, |
| 311 | 'service': frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item') |
| 312 | }) |
| 313 | |
| 314 | return therapy_sessions_to_invoice |
| 315 | |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 316 | @frappe.whitelist() |
Rucha Mahabal | 24055e1 | 2020-02-24 19:09:50 +0530 | [diff] [blame] | 317 | def get_service_item_and_practitioner_charge(doc): |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 318 | if isinstance(doc, str): |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 319 | doc = json.loads(doc) |
| 320 | doc = frappe.get_doc(doc) |
| 321 | |
| 322 | service_item = None |
| 323 | practitioner_charge = None |
| 324 | department = doc.medical_department if doc.doctype == 'Patient Encounter' else doc.department |
| 325 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 326 | is_inpatient = doc.inpatient_record |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 327 | |
| 328 | if doc.get('appointment_type'): |
| 329 | service_item, practitioner_charge = get_appointment_type_service_item(doc.appointment_type, department, is_inpatient) |
| 330 | |
| 331 | if not service_item and not practitioner_charge: |
| 332 | service_item, practitioner_charge = get_practitioner_service_item(doc.practitioner, is_inpatient) |
Jamsheer | ee5f9c7 | 2018-07-30 12:42:06 +0530 | [diff] [blame] | 333 | if not service_item: |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 334 | service_item = get_healthcare_service_item(is_inpatient) |
| 335 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 336 | if not service_item: |
Rucha Mahabal | 24055e1 | 2020-02-24 19:09:50 +0530 | [diff] [blame] | 337 | throw_config_service_item(is_inpatient) |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 338 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 339 | if not practitioner_charge: |
Rucha Mahabal | 24055e1 | 2020-02-24 19:09:50 +0530 | [diff] [blame] | 340 | throw_config_practitioner_charge(is_inpatient, doc.practitioner) |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 341 | |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 342 | return {'service_item': service_item, 'practitioner_charge': practitioner_charge} |
| 343 | |
| 344 | |
| 345 | def get_appointment_type_service_item(appointment_type, department, is_inpatient): |
Chillar Anand | 915b343 | 2021-09-02 16:44:59 +0530 | [diff] [blame] | 346 | from erpnext.healthcare.doctype.appointment_type.appointment_type import ( |
| 347 | get_service_item_based_on_department, |
| 348 | ) |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 349 | |
| 350 | item_list = get_service_item_based_on_department(appointment_type, department) |
| 351 | service_item = None |
| 352 | practitioner_charge = None |
| 353 | |
| 354 | if item_list: |
| 355 | if is_inpatient: |
| 356 | service_item = item_list.get('inpatient_visit_charge_item') |
| 357 | practitioner_charge = item_list.get('inpatient_visit_charge') |
| 358 | else: |
| 359 | service_item = item_list.get('op_consulting_charge_item') |
| 360 | practitioner_charge = item_list.get('op_consulting_charge') |
| 361 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 362 | return service_item, practitioner_charge |
| 363 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 364 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 365 | def throw_config_service_item(is_inpatient): |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 366 | service_item_label = _('Out Patient Consulting Charge Item') |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 367 | if is_inpatient: |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 368 | service_item_label = _('Inpatient Visit Charge Item') |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 369 | |
Rucha Mahabal | 4f9a147 | 2020-03-23 10:40:39 +0530 | [diff] [blame] | 370 | msg = _(('Please Configure {0} in ').format(service_item_label) \ |
Rushabh Mehta | 542bc01 | 2020-11-18 15:00:34 +0530 | [diff] [blame] | 371 | + '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>''') |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 372 | frappe.throw(msg, title=_('Missing Configuration')) |
| 373 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 374 | |
Rucha Mahabal | 24055e1 | 2020-02-24 19:09:50 +0530 | [diff] [blame] | 375 | def throw_config_practitioner_charge(is_inpatient, practitioner): |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 376 | charge_name = _('OP Consulting Charge') |
Rucha Mahabal | 24055e1 | 2020-02-24 19:09:50 +0530 | [diff] [blame] | 377 | if is_inpatient: |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 378 | charge_name = _('Inpatient Visit Charge') |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 379 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 380 | msg = _(('Please Configure {0} for Healthcare Practitioner').format(charge_name) \ |
Rushabh Mehta | 542bc01 | 2020-11-18 15:00:34 +0530 | [diff] [blame] | 381 | + ''' <b><a href='/app/Form/Healthcare Practitioner/{0}'>{0}</a></b>'''.format(practitioner)) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 382 | frappe.throw(msg, title=_('Missing Configuration')) |
| 383 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 384 | |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 385 | def get_practitioner_service_item(practitioner, is_inpatient): |
| 386 | service_item = None |
| 387 | practitioner_charge = None |
| 388 | |
| 389 | if is_inpatient: |
| 390 | service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['inpatient_visit_charge_item', 'inpatient_visit_charge']) |
| 391 | else: |
| 392 | service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['op_consulting_charge_item', 'op_consulting_charge']) |
| 393 | |
| 394 | return service_item, practitioner_charge |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 395 | |
Jamsheer | ee5f9c7 | 2018-07-30 12:42:06 +0530 | [diff] [blame] | 396 | |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 397 | def get_healthcare_service_item(is_inpatient): |
| 398 | service_item = None |
| 399 | |
| 400 | if is_inpatient: |
| 401 | service_item = frappe.db.get_single_value('Healthcare Settings', 'inpatient_visit_charge_item') |
| 402 | else: |
| 403 | service_item = frappe.db.get_single_value('Healthcare Settings', 'op_consulting_charge_item') |
| 404 | |
| 405 | return service_item |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 406 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 407 | |
Rucha Mahabal | 24055e1 | 2020-02-24 19:09:50 +0530 | [diff] [blame] | 408 | def get_practitioner_charge(practitioner, is_inpatient): |
| 409 | if is_inpatient: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 410 | practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, 'inpatient_visit_charge') |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 411 | else: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 412 | practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, 'op_consulting_charge') |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 413 | if practitioner_charge: |
| 414 | return practitioner_charge |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 415 | return False |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 416 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 417 | |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 418 | def manage_invoice_submit_cancel(doc, method): |
| 419 | if doc.items: |
| 420 | for item in doc.items: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 421 | if item.get('reference_dt') and item.get('reference_dn'): |
| 422 | if frappe.get_meta(item.reference_dt).has_field('invoiced'): |
Jamsheer | 146683b | 2018-07-25 11:30:30 +0530 | [diff] [blame] | 423 | set_invoiced(item, method, doc.name) |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 424 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 425 | if method=='on_submit' and frappe.db.get_single_value('Healthcare Settings', 'create_lab_test_on_si_submit'): |
| 426 | create_multiple('Sales Invoice', doc.name) |
| 427 | |
Jamsheer | 0ae100b | 2018-08-01 14:29:43 +0530 | [diff] [blame] | 428 | |
Jamsheer | 146683b | 2018-07-25 11:30:30 +0530 | [diff] [blame] | 429 | def set_invoiced(item, method, ref_invoice=None): |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 430 | invoiced = False |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 431 | if method=='on_submit': |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 432 | validate_invoiced_on_submit(item) |
| 433 | invoiced = True |
| 434 | |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 435 | if item.reference_dt == 'Clinical Procedure': |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 436 | service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') |
| 437 | if service_item == item.item_code: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 438 | frappe.db.set_value(item.reference_dt, item.reference_dn, 'consumption_invoiced', invoiced) |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 439 | else: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 440 | frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced) |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 441 | else: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 442 | frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced) |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 443 | |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 444 | if item.reference_dt == 'Patient Appointment': |
| 445 | if frappe.db.get_value('Patient Appointment', item.reference_dn, 'procedure_template'): |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 446 | dt_from_appointment = 'Clinical Procedure' |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 447 | else: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 448 | dt_from_appointment = 'Patient Encounter' |
Rucha Mahabal | 06d1b04 | 2020-03-12 12:16:23 +0530 | [diff] [blame] | 449 | manage_doc_for_appointment(dt_from_appointment, item.reference_dn, invoiced) |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 450 | |
| 451 | elif item.reference_dt == 'Lab Prescription': |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 452 | manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, 'Lab Test', 'lab_test_created') |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 453 | |
| 454 | elif item.reference_dt == 'Procedure Prescription': |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 455 | manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, 'Clinical Procedure', 'procedure_created') |
| 456 | |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 457 | |
| 458 | def validate_invoiced_on_submit(item): |
Rucha Mahabal | ccc8092 | 2021-02-18 16:41:10 +0530 | [diff] [blame] | 459 | if item.reference_dt == 'Clinical Procedure' and \ |
| 460 | frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') == item.item_code: |
Rucha Mahabal | 06d1b04 | 2020-03-12 12:16:23 +0530 | [diff] [blame] | 461 | is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'consumption_invoiced') |
Jamsheer | 8da6f4e | 2018-07-26 21:03:17 +0530 | [diff] [blame] | 462 | else: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 463 | is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced') |
| 464 | if is_invoiced: |
Rucha Mahabal | 434791e | 2020-10-24 14:20:38 +0530 | [diff] [blame] | 465 | frappe.throw(_('The item referenced by {0} - {1} is already invoiced').format( |
| 466 | item.reference_dt, item.reference_dn)) |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 467 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 468 | |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 469 | def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field): |
| 470 | created = frappe.db.get_value(ref_dt, ref_dn, created_check_field) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 471 | if created: |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 472 | # Fetch the doc created for the prescription |
Jamsheer | eafb046 | 2018-07-25 13:15:12 +0530 | [diff] [blame] | 473 | doc_created = frappe.db.get_value(dt, {'prescription': ref_dn}) |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 474 | frappe.db.set_value(dt, doc_created, 'invoiced', invoiced) |
| 475 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 476 | |
Rucha Mahabal | cd31996 | 2020-03-13 15:39:31 +0530 | [diff] [blame] | 477 | def check_fee_validity(appointment): |
Rucha Mahabal | f2574dd | 2020-03-17 19:28:18 +0530 | [diff] [blame] | 478 | if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): |
| 479 | return |
| 480 | |
Rucha Mahabal | cd31996 | 2020-03-13 15:39:31 +0530 | [diff] [blame] | 481 | validity = frappe.db.exists('Fee Validity', { |
| 482 | 'practitioner': appointment.practitioner, |
Rucha Mahabal | 2f2c09b | 2020-03-17 18:10:39 +0530 | [diff] [blame] | 483 | 'patient': appointment.patient, |
Rucha Mahabal | f2574dd | 2020-03-17 19:28:18 +0530 | [diff] [blame] | 484 | 'valid_till': ('>=', appointment.appointment_date) |
Rucha Mahabal | cd31996 | 2020-03-13 15:39:31 +0530 | [diff] [blame] | 485 | }) |
| 486 | if not validity: |
| 487 | return |
| 488 | |
Rucha Mahabal | f2574dd | 2020-03-17 19:28:18 +0530 | [diff] [blame] | 489 | validity = frappe.get_doc('Fee Validity', validity) |
| 490 | return validity |
| 491 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 492 | |
Rucha Mahabal | 2f2c09b | 2020-03-17 18:10:39 +0530 | [diff] [blame] | 493 | def manage_fee_validity(appointment): |
| 494 | fee_validity = check_fee_validity(appointment) |
anoop | 93d0c78 | 2020-04-13 16:42:03 +0530 | [diff] [blame] | 495 | |
Rucha Mahabal | cd31996 | 2020-03-13 15:39:31 +0530 | [diff] [blame] | 496 | if fee_validity: |
Rucha Mahabal | 2f2c09b | 2020-03-17 18:10:39 +0530 | [diff] [blame] | 497 | if appointment.status == 'Cancelled' and fee_validity.visited > 0: |
| 498 | fee_validity.visited -= 1 |
| 499 | frappe.db.delete('Fee Validity Reference', {'appointment': appointment.name}) |
Rucha Mahabal | 2cec6bd | 2020-03-26 14:38:12 +0530 | [diff] [blame] | 500 | elif fee_validity.status == 'Completed': |
| 501 | return |
Rucha Mahabal | cd31996 | 2020-03-13 15:39:31 +0530 | [diff] [blame] | 502 | else: |
Rucha Mahabal | 2f2c09b | 2020-03-17 18:10:39 +0530 | [diff] [blame] | 503 | fee_validity.visited += 1 |
| 504 | fee_validity.append('ref_appointments', { |
| 505 | 'appointment': appointment.name |
| 506 | }) |
| 507 | fee_validity.save(ignore_permissions=True) |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 508 | else: |
Rucha Mahabal | 2f2c09b | 2020-03-17 18:10:39 +0530 | [diff] [blame] | 509 | fee_validity = create_fee_validity(appointment) |
| 510 | return fee_validity |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 511 | |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 512 | |
Rucha Mahabal | 06d1b04 | 2020-03-12 12:16:23 +0530 | [diff] [blame] | 513 | def manage_doc_for_appointment(dt_from_appointment, appointment, invoiced): |
Rucha Mahabal | c4b2dce | 2020-03-09 23:57:00 +0530 | [diff] [blame] | 514 | dn_from_appointment = frappe.db.get_value( |
| 515 | dt_from_appointment, |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 516 | filters={'appointment': appointment} |
Jamsheer | ba11972 | 2018-07-06 15:58:13 +0530 | [diff] [blame] | 517 | ) |
| 518 | if dn_from_appointment: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 519 | frappe.db.set_value(dt_from_appointment, dn_from_appointment, 'invoiced', invoiced) |
| 520 | |
Jamsheer | e82f27a | 2018-07-30 11:28:37 +0530 | [diff] [blame] | 521 | |
| 522 | @frappe.whitelist() |
| 523 | def get_drugs_to_invoice(encounter): |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 524 | encounter = frappe.get_doc('Patient Encounter', encounter) |
Jamsheer | e82f27a | 2018-07-30 11:28:37 +0530 | [diff] [blame] | 525 | if encounter: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 526 | patient = frappe.get_doc('Patient', encounter.patient) |
| 527 | if patient: |
| 528 | if patient.customer: |
| 529 | items_to_invoice = [] |
Jamsheer | e82f27a | 2018-07-30 11:28:37 +0530 | [diff] [blame] | 530 | for drug_line in encounter.drug_prescription: |
| 531 | if drug_line.drug_code: |
| 532 | qty = 1 |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 533 | if frappe.db.get_value('Item', drug_line.drug_code, 'stock_uom') == 'Nos': |
Jamsheer | e82f27a | 2018-07-30 11:28:37 +0530 | [diff] [blame] | 534 | qty = drug_line.get_quantity() |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 535 | |
| 536 | description = '' |
| 537 | if drug_line.dosage and drug_line.period: |
| 538 | description = _('{0} for {1}').format(drug_line.dosage, drug_line.period) |
| 539 | |
| 540 | items_to_invoice.append({ |
| 541 | 'drug_code': drug_line.drug_code, |
| 542 | 'quantity': qty, |
| 543 | 'description': description |
| 544 | }) |
| 545 | return items_to_invoice |
| 546 | else: |
| 547 | validate_customer_created(patient) |
| 548 | |
Jamsheer | 4371c7e | 2018-08-01 18:40:05 +0530 | [diff] [blame] | 549 | |
| 550 | @frappe.whitelist() |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 551 | def get_children(doctype, parent=None, company=None, is_root=False): |
| 552 | parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_') |
Jamsheer | 4371c7e | 2018-08-01 18:40:05 +0530 | [diff] [blame] | 553 | fields = [ |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 554 | 'name as value', |
| 555 | 'is_group as expandable', |
| 556 | 'lft', |
| 557 | 'rgt' |
Jamsheer | 4371c7e | 2018-08-01 18:40:05 +0530 | [diff] [blame] | 558 | ] |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 559 | |
| 560 | filters = [["ifnull(`{0}`,'')".format(parent_fieldname), |
| 561 | '=', '' if is_root else parent]] |
Jamsheer | 4371c7e | 2018-08-01 18:40:05 +0530 | [diff] [blame] | 562 | |
| 563 | if is_root: |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 564 | fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else [] |
| 565 | filters.append(['company', '=', company]) |
Jamsheer | 4371c7e | 2018-08-01 18:40:05 +0530 | [diff] [blame] | 566 | else: |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 567 | fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy', |
| 568 | 'occupancy_status'] if doctype == 'Healthcare Service Unit' else [] |
| 569 | fields += [parent_fieldname + ' as parent'] |
Jamsheer | 4371c7e | 2018-08-01 18:40:05 +0530 | [diff] [blame] | 570 | |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 571 | service_units = frappe.get_list(doctype, fields=fields, filters=filters) |
| 572 | for each in service_units: |
| 573 | if each['expandable'] == 1: # group node |
| 574 | available_count = frappe.db.count('Healthcare Service Unit', filters={ |
| 575 | 'parent_healthcare_service_unit': each['value'], |
| 576 | 'inpatient_occupancy': 1}) |
Jamsheer | 4371c7e | 2018-08-01 18:40:05 +0530 | [diff] [blame] | 577 | |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 578 | if available_count > 0: |
| 579 | occupied_count = frappe.db.count('Healthcare Service Unit', { |
| 580 | 'parent_healthcare_service_unit': each['value'], |
| 581 | 'inpatient_occupancy': 1, |
| 582 | 'occupancy_status': 'Occupied'}) |
| 583 | # set occupancy status of group node |
| 584 | each['occupied_of_available'] = str( |
| 585 | occupied_count) + ' Occupied of ' + str(available_count) |
Rucha Mahabal | ced978e | 2020-04-02 18:45:53 +0530 | [diff] [blame] | 586 | |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 587 | return service_units |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 588 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 589 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 590 | @frappe.whitelist() |
| 591 | def get_patient_vitals(patient, from_date=None, to_date=None): |
| 592 | if not patient: return |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 593 | |
Rucha Mahabal | 4dd6b99 | 2020-05-25 18:42:01 +0530 | [diff] [blame] | 594 | vitals = frappe.db.get_all('Vital Signs', filters={ |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 595 | 'docstatus': 1, |
| 596 | 'patient': patient |
Rucha Mahabal | 4dd6b99 | 2020-05-25 18:42:01 +0530 | [diff] [blame] | 597 | }, order_by='signs_date, signs_time', fields=['*']) |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 598 | |
| 599 | if len(vitals): |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 600 | return vitals |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 601 | return False |
| 602 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 603 | |
| 604 | @frappe.whitelist() |
| 605 | def render_docs_as_html(docs): |
| 606 | # docs key value pair {doctype: docname} |
| 607 | docs_html = "<div class='col-md-12 col-sm-12 text-muted'>" |
| 608 | for doc in docs: |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 609 | docs_html += render_doc_as_html(doc['doctype'], doc['docname'])['html'] + '<br/>' |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 610 | return {'html': docs_html} |
| 611 | |
Rucha Mahabal | 27512c8 | 2020-03-09 17:29:23 +0530 | [diff] [blame] | 612 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 613 | @frappe.whitelist() |
| 614 | def render_doc_as_html(doctype, docname, exclude_fields = []): |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 615 | """ |
| 616 | Render document as HTML |
| 617 | """ |
| 618 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 619 | doc = frappe.get_doc(doctype, docname) |
| 620 | meta = frappe.get_meta(doctype) |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 621 | doc_html = section_html = section_label = html = "" |
| 622 | sec_on = has_data = False |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 623 | col_on = 0 |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 624 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 625 | for df in meta.fields: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 626 | # on section break append previous section and html to doc html |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 627 | if df.fieldtype == "Section Break": |
| 628 | if has_data and col_on and sec_on: |
| 629 | doc_html += section_html + html + "</div>" |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 630 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 631 | elif has_data and not col_on and sec_on: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 632 | doc_html += """ |
| 633 | <br> |
| 634 | <div class='row'> |
| 635 | <div class='col-md-12 col-sm-12'> |
| 636 | <b>{0}</b> |
| 637 | </div> |
| 638 | </div> |
| 639 | <div class='row'> |
| 640 | <div class='col-md-12 col-sm-12'> |
| 641 | {1} {2} |
| 642 | </div> |
| 643 | </div> |
| 644 | """.format(section_label, section_html, html) |
| 645 | |
| 646 | # close divs for columns |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 647 | while col_on: |
| 648 | doc_html += "</div>" |
| 649 | col_on -= 1 |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 650 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 651 | sec_on = True |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 652 | has_data = False |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 653 | col_on = 0 |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 654 | section_html = html = "" |
| 655 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 656 | if df.label: |
| 657 | section_label = df.label |
| 658 | continue |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 659 | |
| 660 | # on column break append html to section html or doc html |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 661 | if df.fieldtype == "Column Break": |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 662 | if sec_on and not col_on and has_data: |
| 663 | section_html += """ |
| 664 | <br> |
| 665 | <div class='row'> |
| 666 | <div class='col-md-12 col-sm-12'> |
| 667 | <b>{0}</b> |
| 668 | </div> |
| 669 | </div> |
| 670 | <div class='row'> |
| 671 | <div class='col-md-4 col-sm-4'> |
| 672 | {1} |
| 673 | </div> |
| 674 | """.format(section_label, html) |
| 675 | elif col_on == 1 and has_data: |
| 676 | section_html += "<div class='col-md-4 col-sm-4'>" + html + "</div>" |
| 677 | elif col_on > 1 and has_data: |
| 678 | doc_html += "<div class='col-md-4 col-sm-4'>" + html + "</div>" |
| 679 | else: |
| 680 | doc_html += """ |
| 681 | <div class='row'> |
| 682 | <div class='col-md-12 col-sm-12'> |
| 683 | {0} |
| 684 | </div> |
| 685 | </div> |
| 686 | """.format(html) |
| 687 | |
| 688 | html = "" |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 689 | col_on += 1 |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 690 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 691 | if df.label: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 692 | html += "<br>" + df.label |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 693 | continue |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 694 | |
| 695 | # on table iterate through items and create table |
| 696 | # based on the in_list_view property |
| 697 | # append to section html or doc html |
| 698 | if df.fieldtype == "Table": |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 699 | items = doc.get(df.fieldname) |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 700 | if not items: |
| 701 | continue |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 702 | child_meta = frappe.get_meta(df.options) |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 703 | |
| 704 | if not has_data: |
| 705 | has_data = True |
| 706 | table_head = table_row = "" |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 707 | create_head = True |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 708 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 709 | for item in items: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 710 | table_row += "<tr>" |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 711 | for cdf in child_meta.fields: |
| 712 | if cdf.in_list_view: |
| 713 | if create_head: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 714 | table_head += "<th class='text-muted'>" + cdf.label + "</th>" |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 715 | if item.get(cdf.fieldname): |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 716 | table_row += "<td>" + cstr(item.get(cdf.fieldname)) + "</td>" |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 717 | else: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 718 | table_row += "<td></td>" |
| 719 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 720 | create_head = False |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 721 | table_row += "</tr>" |
| 722 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 723 | if sec_on: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 724 | section_html += """ |
| 725 | <table class='table table-condensed bordered'> |
| 726 | {0} {1} |
| 727 | </table> |
| 728 | """.format(table_head, table_row) |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 729 | else: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 730 | html += """ |
| 731 | <table class='table table-condensed table-bordered'> |
| 732 | {0} {1} |
| 733 | </table> |
| 734 | """.format(table_head, table_row) |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 735 | continue |
Rucha Mahabal | 5e3c51b | 2020-11-30 13:35:00 +0530 | [diff] [blame] | 736 | |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 737 | # on any other field type add label and value to html |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 738 | if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 739 | formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc) |
| 740 | html += "<br>{0} : {1}".format(df.label or df.fieldname, formatted_value) |
Rucha Mahabal | 5e3c51b | 2020-11-30 13:35:00 +0530 | [diff] [blame] | 741 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 742 | if not has_data : has_data = True |
Rucha Mahabal | 5e3c51b | 2020-11-30 13:35:00 +0530 | [diff] [blame] | 743 | |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 744 | if sec_on and col_on and has_data: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 745 | doc_html += section_html + html + "</div></div>" |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 746 | elif sec_on and not col_on and has_data: |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 747 | doc_html += """ |
| 748 | <div class='col-md-12 col-sm-12'> |
| 749 | <div class='col-md-12 col-sm-12'> |
| 750 | {0} {1} |
| 751 | </div> |
| 752 | </div> |
| 753 | """.format(section_html, html) |
Jamsheer | 5073ac4 | 2019-07-12 12:28:34 +0530 | [diff] [blame] | 754 | |
Rucha Mahabal | 1682402 | 2021-08-30 18:26:56 +0530 | [diff] [blame] | 755 | return {"html": doc_html} |
Rucha Mahabal | 212eb4b | 2021-08-30 13:10:18 +0530 | [diff] [blame] | 756 | |
| 757 | |
| 758 | def update_address_links(address, method): |
| 759 | ''' |
| 760 | Hook validate Address |
| 761 | If Patient is linked in Address, also link the associated Customer |
| 762 | ''' |
| 763 | if 'Healthcare' not in frappe.get_active_domains(): |
| 764 | return |
| 765 | |
| 766 | patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', address.links)) |
| 767 | |
| 768 | for link in patient_links: |
| 769 | customer = frappe.db.get_value('Patient', link.get('link_name'), 'customer') |
| 770 | if customer and not address.has_link('Customer', customer): |
| 771 | address.append('links', dict(link_doctype = 'Customer', link_name = customer)) |
| 772 | |
| 773 | |
| 774 | def update_patient_email_and_phone_numbers(contact, method): |
| 775 | ''' |
| 776 | Hook validate Contact |
| 777 | Update linked Patients' primary mobile and phone numbers |
| 778 | ''' |
| 779 | if 'Healthcare' not in frappe.get_active_domains(): |
| 780 | return |
| 781 | |
| 782 | if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone): |
| 783 | patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', contact.links)) |
| 784 | |
| 785 | for link in patient_links: |
| 786 | contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1) |
| 787 | if contact.email_id and contact.email_id != contact_details.get('email'): |
| 788 | frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id) |
| 789 | if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'): |
| 790 | frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no) |
| 791 | if contact.phone and contact.phone != contact_details.get('phone'): |
| 792 | frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone) |