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