From bd70e139147611a2935366fa6b8ee38ddc883a53 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Fri, 10 Nov 2023 00:35:32 +0300 Subject: [PATCH 01/12] feat: monthly NHIF detail report to show details of monthly NHIF summary report --- .../report/monthly_nhif_detail/__init__.py | 0 .../monthly_nhif_detail.json | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 hms_tz/hms_tz/report/monthly_nhif_detail/__init__.py create mode 100644 hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json diff --git a/hms_tz/hms_tz/report/monthly_nhif_detail/__init__.py b/hms_tz/hms_tz/report/monthly_nhif_detail/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json b/hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json new file mode 100644 index 00000000..9ee8cf9b --- /dev/null +++ b/hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json @@ -0,0 +1,51 @@ +{ + "add_total_row": 1, + "columns": [], + "creation": "2023-11-10 00:32:31.598540", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [ + { + "fieldname": "claim_month", + "fieldtype": "Int", + "label": "Claim Month", + "mandatory": 1, + "wildcard_filter": 0 + }, + { + "fieldname": "claim_year", + "fieldtype": "Int", + "label": "Claim Year", + "mandatory": 1, + "wildcard_filter": 0 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "mandatory": 1, + "options": "Company", + "wildcard_filter": 0 + } + ], + "idx": 0, + "is_standard": "Yes", + "letter_head": "", + "modified": "2023-11-10 00:32:54.106583", + "modified_by": "Administrator", + "module": "Hms Tz", + "name": "Monthly NHIF Detail", + "owner": "Administrator", + "prepared_report": 1, + "query": "SELECT\n npc.name AS \"NHIF Patient Claim:Link/NHIF Patient Claim:\",\n npc.patient AS \"Patient:Link/Patient:150\",\n npc.patient_name AS \"Patient Name:Data:\",\n npc.patient_appointment AS \"Patient Appointment:Link/Patient Appointment:150\",\n npci.ref_doctype AS \"Service Type:Data:\",\n npci.item_name,\n npc.gender AS \"Gender::\",\n npc.attendance_date AS \"Appointment Date:Date:\",\n npc.date_discharge AS \"Discharge Date:Date:\",\n SUM(npci.amount_claimed) AS \"Amount Claimed:Currency:\",\n SUM(npci.item_quantity) AS \"Total Quantity:Int:\",\n npc.folio_no AS \"Folio No::\"\nFROM `tabNHIF Patient Claim` npc\nINNER JOIN `tabNHIF Patient Claim Item` npci ON npc.name = npci.parent\nWHERE npc.docstatus = 1\n AND npc.claim_month = %(claim_month)s\n AND npc.claim_year = %(claim_year)s\n AND npc.company = %(company)s\nGROUP BY npc.patient, npc.patient_appointment, npci.ref_doctype, npci.item_name, npc.gender,npc.attendance_date, npc.date_discharge", + "ref_doctype": "NHIF Patient Claim", + "report_name": "Monthly NHIF Detail", + "report_type": "Query Report", + "roles": [ + { + "role": "System Manager" + } + ] +} \ No newline at end of file From 410d6f7dd390315cf5678ad484e31130bcdd29ae Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Fri, 10 Nov 2023 18:30:37 +0300 Subject: [PATCH 02/12] feat: filter dosage based on dosage form and has restricted qty on drug prescription table --- hms_tz/nhif/api/patient_encounter.js | 16 ++++-- hms_tz/nhif/api/patient_encounter.py | 56 +++++++++++++++---- hms_tz/patches.txt | 1 + ...for_dosage_form_and_prescription_dosage.py | 27 +++++++++ 4 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 hms_tz/patches/custom_fields/has_restricted_qty_for_dosage_form_and_prescription_dosage.py diff --git a/hms_tz/nhif/api/patient_encounter.js b/hms_tz/nhif/api/patient_encounter.js index 18624ae6..b461de73 100755 --- a/hms_tz/nhif/api/patient_encounter.js +++ b/hms_tz/nhif/api/patient_encounter.js @@ -73,6 +73,18 @@ frappe.ui.form.on('Patient Encounter', { // filter medication based on company filter_drug_prescriptions(frm); + // Mosaic: https://worklog.aakvatech.com/Mosaic/Task/96b2f5e26c + //filter dosage based on dosage form and has restricted qty ticked + frm.set_query("dosage", "drug_prescription", function (doc, cdt, cdn) { + const child = locals[cdt][cdn]; + return { + query: "hms_tz.nhif.api.patient_encounter.get_filtered_dosage", + filters: { + dosage_form: child.dosage_form + } + } + }); + frm.set_query('therapy_type', 'therapies', function () { return { filters: { @@ -856,10 +868,6 @@ frappe.ui.form.on('Drug Prescription', { validate_stock_item(frm, row.drug_code, row.prescribe, row.quantity, row.healthcare_service_unit, "Drug Prescription"); } }, - dosage: function (frm, cdt, cdn) { - frappe.model.set_value(cdt, cdn, "quantity", 0); - frm.refresh_field("drug_prescription"); - }, dosage: (frm, cdt, cdn) => { let row = locals[cdt][cdn]; if (row.dosage && row.period) { diff --git a/hms_tz/nhif/api/patient_encounter.py b/hms_tz/nhif/api/patient_encounter.py index 73074192..deadb5ac 100644 --- a/hms_tz/nhif/api/patient_encounter.py +++ b/hms_tz/nhif/api/patient_encounter.py @@ -72,8 +72,8 @@ def after_insert(doc, method): ) if ( - pharmacy_details and - pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 + pharmacy_details + and pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 ): if doc.inpatient_record: if not pharmacy_details.ipd_cash_pharmacy: @@ -99,14 +99,14 @@ def after_insert(doc, method): [ "auto_set_pharmacy_on_patient_encounter", "opd_insurance_pharmacy", - "ipd_insurance_pharmacy" + "ipd_insurance_pharmacy", ], as_dict=1, ) if ( - pharmacy_details and - pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 + pharmacy_details + and pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 ): if doc.inpatient_record: if not pharmacy_details.ipd_insurance_pharmacy: @@ -396,12 +396,13 @@ def on_submit_validation(doc, method): if template not in hsic_map: for row_item in healthcare_service_templates[template]: if ( - doc.company and - frappe.get_cached_value( + doc.company + and frappe.get_cached_value( "Company", doc.company, "auto_prescribe_items_on_patient_encounter", - ) == 1 + ) + == 1 ): row_item.prescribe = 1 @@ -417,12 +418,13 @@ def on_submit_validation(doc, method): if template in hsic_map: for row_item in healthcare_service_templates[template]: if ( - doc.company and - frappe.get_cached_value( + doc.company + and frappe.get_cached_value( "Company", doc.company, "auto_prescribe_items_on_patient_encounter", - ) == 1 + ) + == 1 ): row_item.prescribe = 1 @@ -901,7 +903,8 @@ def create_delivery_note_per_encounter(patient_encounter_doc, method): "dosage_form: " + str(row.get("dosage_form") or ""), "interval: " + str(row.get("interval") or ""), "interval_uom: " + str(row.get("interval_uom") or ""), - "medical_code: " + str(row.get("medical_code") or "No medical code"), + "medical_code: " + + str(row.get("medical_code") or "No medical code"), "Doctor's comment: " + (row.get("comment") or "Take medication as per dosage."), ] @@ -2465,3 +2468,32 @@ def get_filterd_drug(doctype, txt, searchfield, start, page_len, filters): ) return data + + +@frappe.whitelist() +def get_filtered_dosage(doctype, txt, searchfield, start, page_len, filters): + doctype = "Prescription Dosage" + if filters.get("dosage_form"): + if ( + frappe.get_cached_value( + "Dosage Form", filters.get("dosage_form"), "has_restricted_qty" + ) == 1 + ): + return frappe.get_all( + doctype, + filters={"has_restricted_qty": 1}, + fields=[searchfield], + as_list=1, + ) + else: + return frappe.get_all( + "Prescription Dosage", + fields=[searchfield], + as_list=1, + ) + else: + return frappe.get_all( + "Prescription Dosage", + fields=[searchfield], + as_list=1, + ) diff --git a/hms_tz/patches.txt b/hms_tz/patches.txt index dfe35b8c..f3236b15 100644 --- a/hms_tz/patches.txt +++ b/hms_tz/patches.txt @@ -50,3 +50,4 @@ hms_tz.patches.custom_fields.add_inpatient_record_to_pythiotherapy_doctypes hms_tz.patches.custom_fields.auto_finalize_patient_encounter hms_tz.patches.custom_fields.auto_prescribe_items_on_patient_encounter hms_tz.patches.property_setter.additional_property_setters_for_hms_tz +hms_tz.patches.custom_fields.has_restricted_qty_for_dosage_form_and_prescription_dosage \ No newline at end of file diff --git a/hms_tz/patches/custom_fields/has_restricted_qty_for_dosage_form_and_prescription_dosage.py b/hms_tz/patches/custom_fields/has_restricted_qty_for_dosage_form_and_prescription_dosage.py new file mode 100644 index 00000000..ef337591 --- /dev/null +++ b/hms_tz/patches/custom_fields/has_restricted_qty_for_dosage_form_and_prescription_dosage.py @@ -0,0 +1,27 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + fields = { + "Dosage Form": [ + { + "fieldname": "has_restricted_qty", + "fieldtype": "Check", + "label": "Has Restricted Qty", + "insert_after": "dosage_form", + "description": "if ticked, medication with this Dosage Form will undergo prescription dosage filtering on Patient Encounter." + } + ], + "Prescription Dosage": [ + { + "fieldname": "has_restricted_qty", + "fieldtype": "Check", + "label": "Has Restricted Qty", + "insert_after": "dosage", + "description": "if ticked, this Prescription Dosage will be filtered in when Quantity's restricted Medication is selected on Patient Encounter." + } + ], + } + + create_custom_fields(fields, update=True) + From feb762f93003163c4b6ea9299f69a6902341b3dd Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Mon, 13 Nov 2023 18:21:36 +0300 Subject: [PATCH 03/12] feat: add appointment time, admitted time and discharge time on NHIF Patient Claim also include them on JSON payload to be sent to NHIF --- .../nhif_patient_claim.json | 31 ++++++++++++-- .../nhif_patient_claim/nhif_patient_claim.py | 42 ++++++++++++++----- .../nhif_patient_claim_item.json | 7 ++-- .../original_nhif_patient_claim_item.json | 7 ++-- 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.json b/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.json index 97b9cc72..0f1891f8 100644 --- a/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.json +++ b/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.json @@ -40,15 +40,18 @@ "serial_no", "created_by", "item_crt_by", + "delayreason", "column_break_18", "practitioner_name", "practitioner_no", "date_discharge", + "discharge_time", "date_admitted", + "admitted_time", "patient_type_code", "attendance_date", + "attendance_time", "patient_file_no", - "delayreason", "patient_file_section", "patient_file", "claim_file_section", @@ -429,6 +432,7 @@ "read_only": 1 }, { + "allow_on_submit": 1, "fieldname": "original_nhif_patient_claim_item", "fieldtype": "Table", "options": "Original NHIF Patient Claim Item", @@ -439,15 +443,35 @@ "fieldname": "original_section_break", "fieldtype": "Section Break", "label": "Original NHIF Patient Claim Item" + }, + { + "fieldname": "discharge_time", + "fieldtype": "Time", + "label": "Discharge Time", + "read_only": 1 + }, + { + "fieldname": "admitted_time", + "fieldtype": "Time", + "label": "Admitted Time", + "read_only": 1 + }, + { + "fetch_from": "patient_appointment.appointment_time", + "fieldname": "attendance_time", + "fieldtype": "Time", + "label": "Attendance Time", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-08 15:15:23.999156", + "modified": "2023-11-13 15:41:59.324916", "modified_by": "Administrator", "module": "NHIF", "name": "NHIF Patient Claim", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -481,6 +505,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "patient_name", "track_changes": 1 -} +} \ No newline at end of file diff --git a/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.py b/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.py index 5082dd15..502d733b 100644 --- a/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.py +++ b/hms_tz/nhif/doctype/nhif_patient_claim/nhif_patient_claim.py @@ -15,11 +15,13 @@ getdate, get_fullname, nowdate, + nowtime, get_datetime, time_diff_in_seconds, now_datetime, cint, get_url_to_form, + get_time, ) from hms_tz.nhif.doctype.nhif_response_log.nhif_response_log import add_log from hms_tz.nhif.api.healthcare_utils import ( @@ -229,17 +231,27 @@ def set_claim_values(self): # Reset values for every validate self.patient_type_code = "OUT" self.date_admitted = None + self.admitted_time = None self.date_discharge = None + self.discharge_time = None if self.inpatient_record: - discharge_date, date_admitted, admitted_datetime = frappe.get_value( + ( + discharge_date, + scheduled_date, + admitted_datetime, + time_created, + ) = frappe.get_value( "Inpatient Record", self.inpatient_record, - ["discharge_date", "scheduled_date", "admitted_datetime"], + ["discharge_date", "scheduled_date", "admitted_datetime", "creation"], ) - if getdate(date_admitted) < getdate(admitted_datetime): - self.date_admitted = date_admitted + + if getdate(scheduled_date) < getdate(admitted_datetime): + self.date_admitted = scheduled_date + self.admitted_time = get_time(get_datetime(time_created)) else: self.date_admitted = getdate(admitted_datetime) + self.admitted_time = get_time(get_datetime(admitted_datetime)) # If the patient is same day discharged then consider it as Outpatient if self.date_admitted == getdate(discharge_date): @@ -249,8 +261,14 @@ def set_claim_values(self): self.patient_type_code = "IN" self.date_discharge = discharge_date - self.attendance_date = frappe.get_value( - "Patient Appointment", self.patient_appointment, "appointment_date" + # the time claim is created will treated as discharge time + # because there is no field of discharge time on Inpatient Record + self.discharge_time = nowtime() + + self.attendance_date, self.attendance_time = frappe.get_value( + "Patient Appointment", + self.patient_appointment, + ["appointment_date", "appointment_time"], ) if self.date_discharge: self.claim_year = int(self.date_discharge.strftime("%Y")) @@ -438,9 +456,7 @@ def set_patient_claim_item(self, inpatient_record=None, called_method=None): child["ref_doctype"], row.get(child["ref_docname"]) ) - new_row.status = get_LRPMT_status( - encounter.name, row, child - ) + new_row.status = get_LRPMT_status(encounter.name, row, child) new_row.patient_encounter = encounter.name new_row.ref_doctype = row.doctype new_row.ref_docname = row.name @@ -700,8 +716,12 @@ def get_folio_json_data(self): entities.AttendanceDate = str(self.attendance_date) entities.PatientTypeCode = self.patient_type_code if self.patient_type_code == "IN": - entities.DateAdmitted = str(self.date_admitted) - entities.DateDischarged = str(self.date_discharge) + entities.DateAdmitted = ( + str(self.date_admitted) + " " + str(self.admitted_time) + ) + entities.DateDischarged = ( + str(self.date_discharge) + " " + str(self.discharge_time) + ) entities.PractitionerName = self.practitioner_name entities.PractitionerNo = self.practitioner_no entities.CreatedBy = self.item_crt_by diff --git a/hms_tz/nhif/doctype/nhif_patient_claim_item/nhif_patient_claim_item.json b/hms_tz/nhif/doctype/nhif_patient_claim_item/nhif_patient_claim_item.json index e795b0a3..f0a89bd6 100644 --- a/hms_tz/nhif/doctype/nhif_patient_claim_item/nhif_patient_claim_item.json +++ b/hms_tz/nhif/doctype/nhif_patient_claim_item/nhif_patient_claim_item.json @@ -87,7 +87,7 @@ }, { "fieldname": "patient_encounter", - "fieldtype": "Data", + "fieldtype": "Small Text", "in_list_view": 1, "label": "Patient Encounter", "read_only": 1 @@ -103,7 +103,7 @@ }, { "fieldname": "ref_docname", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Ref DocName", "read_only": 1 }, @@ -192,7 +192,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-31 23:25:53.234675", + "modified": "2023-11-12 23:56:57.222360", "modified_by": "Administrator", "module": "NHIF", "name": "NHIF Patient Claim Item", @@ -201,5 +201,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/hms_tz/nhif/doctype/original_nhif_patient_claim_item/original_nhif_patient_claim_item.json b/hms_tz/nhif/doctype/original_nhif_patient_claim_item/original_nhif_patient_claim_item.json index 68458521..f5f23a27 100644 --- a/hms_tz/nhif/doctype/original_nhif_patient_claim_item/original_nhif_patient_claim_item.json +++ b/hms_tz/nhif/doctype/original_nhif_patient_claim_item/original_nhif_patient_claim_item.json @@ -103,14 +103,14 @@ }, { "fieldname": "patient_encounter", - "fieldtype": "Data", + "fieldtype": "Small Text", "in_list_view": 1, "label": "Patient Encounter", "read_only": 1 }, { "fieldname": "ref_docname", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Ref DocName", "read_only": 1 }, @@ -191,7 +191,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-23 11:34:00.954562", + "modified": "2023-11-12 23:56:15.344856", "modified_by": "Administrator", "module": "NHIF", "name": "Original NHIF Patient Claim Item", @@ -200,5 +200,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From f27d018a3441910d78bdef2b805edcf1ff96f0f5 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Mon, 13 Nov 2023 18:33:02 +0300 Subject: [PATCH 04/12] chore: reconcile original NHIF patient claim item table incase user forgot to reconcile and changed manually the repeated items on NHIF patient claim item table --- .../nhif_tracking_claim_change.py | 89 +++++++++++++++++-- .../additional_property_setters_for_hms_tz.py | 2 +- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/hms_tz/nhif/doctype/nhif_tracking_claim_change/nhif_tracking_claim_change.py b/hms_tz/nhif/doctype/nhif_tracking_claim_change/nhif_tracking_claim_change.py index 1bd37d8a..0f3ec660 100644 --- a/hms_tz/nhif/doctype/nhif_tracking_claim_change/nhif_tracking_claim_change.py +++ b/hms_tz/nhif/doctype/nhif_tracking_claim_change/nhif_tracking_claim_change.py @@ -3,7 +3,7 @@ import frappe from frappe.model.document import Document -from frappe.utils import nowdate, nowtime, get_fullname, cstr +from frappe.utils import nowdate, nowtime, get_fullname, cstr, flt class NHIFTrackingClaimChange(Document): @@ -13,21 +13,25 @@ class NHIFTrackingClaimChange(Document): ref_docnames_list = [] -def track_changes_of_claim_items(claim): +def track_changes_of_claim_items(claim_doc): + claim_no = reconcile_original_nhif_patient_claim_items(claim_doc) + claim = frappe.get_doc("NHIF Patient Claim", claim_no) + claim.reload() + for row in claim.original_nhif_patient_claim_item: for item in claim.nhif_patient_claim_item: - if item.item_code == row.item_code: - if item.amount_claimed == row.amount_claimed: - ref_docnames_list.append(cstr(item.ref_docname)) + if cstr(item.item_code) == cstr(row.item_code): + if flt(item.amount_claimed) == flt(row.amount_claimed): + ref_docnames_list.append(cstr(row.ref_docname)) - elif item.amount_claimed != row.amount_claimed: - ref_docnames_list.append(cstr(item.ref_docname)) + elif flt(item.amount_claimed) != flt(row.amount_claimed): + ref_docnames_list.append(cstr(row.ref_docname)) create_nhif_track_record( item, claim, row.amount_claimed, "Amount Changed" ) for row in claim.original_nhif_patient_claim_item: - if row.ref_docname not in ref_docnames_list: + if cstr(row.ref_docname) not in ref_docnames_list: if row.ref_doctype == "Patient Appointment": create_nhif_track_record(row, claim, row.amount_claimed, "Item Removed") @@ -282,3 +286,72 @@ def get_medication_change_request_reference(item, encounter): ).run(as_dict=1) return ref_name[0].name if ref_name else None + + +def reconcile_original_nhif_patient_claim_items(claim_doc): + unique_items = [] + repeated_items = [] + unique_refcodes = [] + + for row in claim_doc.original_nhif_patient_claim_item: + if row.item_code not in unique_refcodes: + unique_refcodes.append(row.item_code) + unique_items.append(row) + else: + repeated_items.append(row) + + if len(repeated_items) > 0: + claim_doc.original_nhif_patient_claim_item = [] + for item in unique_items: + ref_docnames = [] + ref_encounters = [] + + for d in repeated_items: + if str(item.item_code) == str(d.item_code): + item.item_quantity += d.item_quantity + item.amount_claimed += d.amount_claimed + + if d.approval_ref_no: + approval_ref_no = None + if item.approval_ref_no: + approval_ref_no = ( + str(item.approval_ref_no) + "," + str(d.approval_ref_no) + ) + else: + approval_ref_no = d.approval_ref_no + + item.approval_ref_no = approval_ref_no + + if d.patient_encounter: + ref_encounters.append(d.patient_encounter) + if d.ref_docname: + ref_docnames.append(d.ref_docname) + + if item.status != "Submitted" and d.status == "Submitted": + item.status = "Submitted" + + if item.patient_encounter: + ref_encounters.append(item.patient_encounter) + if item.ref_docname: + ref_docnames.append(item.ref_docname) + + if len(ref_encounters) > 0: + item.patient_encounter = ",".join(set(ref_encounters)) + + if len(ref_docnames) > 0: + item.ref_docname = ",".join(set(ref_docnames)) + + item.name = None + claim_doc.append("original_nhif_patient_claim_item", item) + + for record in repeated_items: + if record.docstatus == 1: + record.cancel() + + record.delete(ignore_permissions=True, force=True, delete_permanently=True) + + claim_doc.save(ignore_permissions=True) + claim_doc.reload() + return claim_doc.name + + return claim_doc.name diff --git a/hms_tz/patches/property_setter/additional_property_setters_for_hms_tz.py b/hms_tz/patches/property_setter/additional_property_setters_for_hms_tz.py index 33dfce78..a506ca76 100644 --- a/hms_tz/patches/property_setter/additional_property_setters_for_hms_tz.py +++ b/hms_tz/patches/property_setter/additional_property_setters_for_hms_tz.py @@ -65,7 +65,7 @@ def execute(): }, { "doctype": "Clinical Procedure", - "fieldname": "medical_code_standard", + "fieldname": "status", "property": "allow_on_submit", "property_type": "Check", "value": 1 From 6d0653f5e7b59638302845ead1d11bd9f7587a75 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Tue, 21 Nov 2023 20:59:57 +0300 Subject: [PATCH 05/12] feat: add payment mode filter, show cancelled items on the report and add resolve return of quantities for medications --- .../itemwise_hospital_revenue.js | 76 +- .../itemwise_hospital_revenue.py | 1810 +++++++++++++---- 2 files changed, 1478 insertions(+), 408 deletions(-) diff --git a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js index 0c97bc7b..dc640fad 100644 --- a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js +++ b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js @@ -11,12 +11,48 @@ frappe.query_reports["Itemwise Hospital Revenue"] = { "options": "Company", "default": frappe.defaults.get_user_default("Company"), "reqd": 1, + "on_change": function () { + let company = frappe.query_report.get_filter_value('company'); + let payment_modes = frappe.query_report.get_filter('payment_mode'); + frappe.call({ + method: "hms_tz.nhif.report.itemwise_hospital_revenue.itemwise_hospital_revenue.get_payment_modes", + args: { + company: company + }, + async: false, + callback: function (r) { + payment_modes.df.options = '' + if (r.message && r.message.length > 0) { + payment_modes.df.options = r.message; + } + payment_modes.refresh(); + } + }); + } }, { "fieldname": "from_date", "label": __("From Date"), "fieldtype": "Date", "reqd": 1, + "on_change": function () { + let company = frappe.query_report.get_filter_value('company'); + let payment_modes = frappe.query_report.get_filter('payment_mode'); + frappe.call({ + method: "hms_tz.nhif.report.itemwise_hospital_revenue.itemwise_hospital_revenue.get_payment_modes", + args: { + company: company + }, + async: false, + callback: function (r) { + payment_modes.df.options = '' + if (r.message && r.message.length > 0) { + payment_modes.df.options = r.message; + } + payment_modes.refresh(); + } + }); + } }, { "fieldname": "to_date", @@ -29,6 +65,44 @@ frappe.query_reports["Itemwise Hospital Revenue"] = { "label": __("Service Type"), "fieldtype": "Link", "options": "Item Group", - } + }, + { + "fieldname": "payment_mode", + "label": __("Payment Mode"), + "fieldtype": "Select", + "hidden": 0, + }, + { + "fieldname": "show_only_cancelled_items", + "label": __("Show Only Cancelled Items"), + "fieldtype": "Check", + "on_change": function () { + let show_only_cancelled = frappe.query_report.get_filter_value('show_only_cancelled_items'); + let bill_doctype = frappe.query_report.get_filter('bill_doctype'); + let service_type = frappe.query_report.get_filter('service_type'); + if (show_only_cancelled == 1) { + bill_doctype.df.hidden = 0; + service_type.df.hidden = 1; + frappe.query_report.set_filter_value('payment_mode', ''); + frappe.query_report.set_filter_value('service_type', ''); + frappe.query_report.set_filter_value('bill_doctype', ''); + } else { + bill_doctype.df.hidden = 1; + service_type.df.hidden = 0; + frappe.query_report.set_filter_value('payment_mode', ''); + frappe.query_report.set_filter_value('service_type', ''); + frappe.query_report.set_filter_value('bill_doctype', ''); + } + bill_doctype.refresh(); + service_type.refresh(); + } + }, + { + "fieldname": "bill_doctype", + "label": __("Bill Doctype"), + "fieldtype": "Select", + "options": "\nLab Test\nRadiology Examination\nClinical Procedure\nDelivery Note", + "hidden": 1, + }, ] }; \ No newline at end of file diff --git a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py index c840229a..9fed9b5c 100644 --- a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py +++ b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py @@ -5,21 +5,28 @@ from pypika import Case, functions as fn from frappe.query_builder import DocType from frappe.query_builder.functions import Sum, Count +from pypika.terms import ValueWrapper def execute(filters=None): - data = [] columns = get_columns(filters) - data = get_appointment_data(filters) - data += get_lab_data(filters) - data += get_radiology_data(filters) - data += get_procedure_data(filters) - data += get_drug_data(filters) - data += get_therapy_data(filters) - data += get_ipd_beds_data(filters) - data += get_ipd_cons_data(filters) - # data += get_procedural_charges(filters) - return columns, data + + if filters.get("show_only_cancelled_items") == 1: + data = get_cancelled_data(filters) + return columns, data + + else: + if not filters.payment_mode: + data = get_cash_insurance_data(filters) + return columns, data + + elif filters.payment_mode == "Cash": + data = get_cash_data(filters) + return columns, data + + else: + data = get_insurance_data(filters) + return columns, data def get_columns(filters): @@ -49,84 +56,220 @@ def get_columns(filters): "fieldtype": "Data", "width": 120, }, - {"fieldname": "bill_no", "label": "Bill No", "fieldtype": "Data", "width": 120}, - { - "fieldname": "service_type", - "label": "Service Type", - "fieldtype": "Data", - "width": 120, - }, - { - "fieldname": "service_name", - "label": "Service Name", - "fieldtype": "Data", - "width": 120, - }, - {"fieldname": "qty", "label": "Qty", "fieldtype": "Int", "width": 50}, - { - "fieldname": "rate", - "label": "Rate", - "fieldtype": "Currency", - "width": 120, - }, - { - "fieldname": "discount_amount", - "label": "Discount Amount", - "fieldtype": "Currency", - "width": 120, - }, - { - "fieldname": "amount", - "label": "Amount", - "fieldtype": "Currency", - "width": 120, - }, - { - "fieldname": "payment_method", - "label": "Payment Method", - "fieldtype": "Data", - "width": 120, - }, - { - "fieldname": "department", - "label": "Department", - "fieldtype": "Data", - "width": 120, - }, - { - "fieldnaeme": "practitioner", - "label": "Practitioner", - "fieldtype": "Data", - "width": 120, - }, - { - "fieldname": "service_unit", - "label": "Service Unit", - "fieldtype": "Data", - "width": 120, - }, - { - "fieldname": "status", - "label": "Status", - "fieldtype": "Data", - "width": 120, - }, ] + if not filters.show_only_cancelled_items: + columns += [ + { + "fieldname": "bill_no", + "label": "Bill No", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "service_type", + "label": "Service Type", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "service_name", + "label": "Service Name", + "fieldtype": "Data", + "width": 120, + }, + {"fieldname": "qty", "label": "Qty", "fieldtype": "Int", "width": 50}, + { + "fieldname": "rate", + "label": "Rate", + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "discount_amount", + "label": "Discount Amount", + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "amount", + "label": "Amount", + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "payment_method", + "label": "Payment Method", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "department", + "label": "Department", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldnaeme": "practitioner", + "label": "Practitioner", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "service_unit", + "label": "Service Unit", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "status", + "label": "Status", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "date_modified", + "label": "Date Modified", + "fieldtype": "Datetime", + "width": 120, + }, + ] + elif filters.get("show_only_cancelled_items") == 1: + columns += [ + { + "fieldname": "encounter_no", + "label": "Encounter No", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "bill_doctype", + "label": "Bill Doctype", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "bill_no", + "label": "Bill No", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "service_name", + "label": "Service Name", + "fieldtype": "Data", + "width": 120, + }, + {"fieldname": "qty", "label": "Qty", "fieldtype": "Int", "width": 50}, + { + "fieldname": "rate", + "label": "Rate", + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "amount", + "label": "Amount", + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "payment_method", + "label": "Payment Method", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "reference_no", + "label": "LRPMT Return No", + "fieldtype": "Data", + "width": 120, + }, + # { + # "fieldnaeme": "requested_by", + # "label": "Requested By", + # "fieldtype": "Data", + # "width": 120, + # }, + # { + # "fieldname": "approved_by", + # "label": "Approved By", + # "fieldtype": "Data", + # "width": 120, + # }, + { + "fieldname": "reason", + "label": "Cancellation Reason", + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "date_modified", + "label": "Date Modified", + "fieldtype": "Datetime", + "width": 120, + }, + ] return columns -def get_appointment_data(filters): +def get_cash_insurance_data(filters): + cash_insurance_data = [] + cash_insurance_data += get_insurance_appointment_data(filters) + cash_insurance_data += get_cash_appointment_data(filters) + cash_insurance_data += get_insurance_lab_data(filters) + cash_insurance_data += get_cash_lab_data(filters) + cash_insurance_data += get_insurance_radiology_data(filters) + cash_insurance_data += get_cash_radiology_data(filters) + cash_insurance_data += get_insurance_procedure_data(filters) + cash_insurance_data += get_cash_procedure_data(filters) + cash_insurance_data += get_insurance_drug_data(filters) + cash_insurance_data += get_cash_drug_data(filters) + cash_insurance_data += get_direct_sales_drug_data(filters) + cash_insurance_data += get_insurance_therapy_data(filters) + cash_insurance_data += get_cash_therapy_data(filters) + cash_insurance_data += get_insurance_ipd_beds_data(filters) + cash_insurance_data += get_cash_ipd_beds_data(filters) + cash_insurance_data += get_insurance_ipd_cons_data(filters) + cash_insurance_data += get_cash_ipd_cons_data(filters) + + return cash_insurance_data + + +def get_cash_data(filters): + cash_data = [] + cash_data += get_cash_appointment_data(filters) + cash_data += get_cash_lab_data(filters) + cash_data += get_cash_radiology_data(filters) + cash_data += get_cash_procedure_data(filters) + cash_data += get_cash_drug_data(filters) + cash_data += get_direct_sales_drug_data(filters) + cash_data += get_cash_therapy_data(filters) + cash_data += get_cash_ipd_beds_data(filters) + cash_data += get_cash_ipd_cons_data(filters) + + return cash_data + + +def get_insurance_data(filters): + insurance_data = [] + insurance_data += get_insurance_appointment_data(filters) + insurance_data += get_insurance_lab_data(filters) + insurance_data += get_insurance_radiology_data(filters) + insurance_data += get_insurance_procedure_data(filters) + insurance_data += get_insurance_drug_data(filters) + insurance_data += get_insurance_therapy_data(filters) + insurance_data += get_insurance_ipd_beds_data(filters) + insurance_data += get_insurance_ipd_cons_data(filters) + + return insurance_data + + +def get_insurance_appointment_data(filters): appointment = DocType("Patient Appointment") item = DocType("Item") - sii = DocType("Sales Invoice Item") - service_type_map = None - if filters.service_type: - service_type_map = item.item_group == filters.service_type - else: - service_type_map = item.item_group.isnotnull() - appointment_data_non_cash = ( + insurance_appointment_query = ( frappe.qb.from_(appointment) .inner_join(item) .on(appointment.billing_item == item.name) @@ -137,18 +280,12 @@ def get_appointment_data(filters): appointment.patient.as_("patient"), appointment.patient_name.as_("patient_name"), Case() - .when(appointment.inpatient_record.isnull(), "Out-Patient") - .else_("In-Patient") + .when(appointment.appointment_type.like("Emergency"), "In-Patient") + .else_("Out-Patient") .as_("patient_type"), item.item_group.as_("service_type"), appointment.billing_item.as_("service_name"), - Case() - .when( - appointment.mode_of_payment.isin(("", None)), - appointment.coverage_plan_name, - ) - .else_("Cash") - .as_("payment_method"), + appointment.coverage_plan_name.as_("payment_method"), Count("*").as_("qty"), fn.Max(appointment.paid_amount).as_("rate"), Sum(appointment.paid_amount).as_("amount"), @@ -159,6 +296,7 @@ def get_appointment_data(filters): appointment.practitioner.as_("practitioner"), appointment.department.as_("department"), appointment.service_unit.as_("service_unit"), + appointment.modified.as_("date_modified"), ) .where( (appointment.company == filters.company) @@ -166,8 +304,11 @@ def get_appointment_data(filters): & (appointment.status != "Cancelled") & (appointment.follow_up == 0) & (appointment.has_no_consultation_charges == 0) - & service_type_map & (appointment.invoiced == 0) + & ( + (appointment.insurance_subscription.isnotnull()) + & (appointment.insurance_subscription != "") + ) ) .groupby( appointment.appointment_date, @@ -178,9 +319,29 @@ def get_appointment_data(filters): appointment.practitioner, appointment.status, ) - ).run(as_dict=True) + ) + + if filters.service_type: + insurance_appointment_query = insurance_appointment_query.where( + item.item_group == filters.service_type + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_appointment_query = insurance_appointment_query.where( + appointment.insurance_company == filters.payment_mode + ) + + insurance_appointment_data = insurance_appointment_query.run(as_dict=True) + + return insurance_appointment_data + + +def get_cash_appointment_data(filters): + appointment = DocType("Patient Appointment") + item = DocType("Item") + sii = DocType("Sales Invoice Item") - appointment_data_cash = ( + cash_appointment_query = ( frappe.qb.from_(appointment) .inner_join(item) .on(appointment.billing_item == item.name) @@ -193,28 +354,16 @@ def get_appointment_data(filters): appointment.patient.as_("patient"), appointment.patient_name.as_("patient_name"), Case() - .when(appointment.inpatient_record.isnull(), "Out-Patient") - .else_("In-Patient") + .when(appointment.appointment_type.like("Emergency"), "In-Patient") + .else_("Out-Patient") .as_("patient_type"), item.item_group.as_("service_type"), appointment.billing_item.as_("service_name"), - Case() - .when( - appointment.mode_of_payment.isin(("", None)), - appointment.coverage_plan_name, - ) - .else_("Cash") - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(appointment.paid_amount).as_("rate"), - Case() - .when( - appointment.ref_sales_invoice.isnotnull(), - Sum(sii.amount - sii.net_amount), - ) - .else_(0) - .as_("discount_amount"), - Sum(appointment.paid_amount).as_("amount"), + Sum(sii.amount - sii.net_amount).as_("discount_amount"), + Sum(sii.net_amount).as_("amount"), Case() .when(appointment.status == "Closed", "Submitted") .else_("Draft") @@ -222,6 +371,7 @@ def get_appointment_data(filters): appointment.practitioner.as_("practitioner"), appointment.department.as_("department"), appointment.service_unit.as_("service_unit"), + appointment.modified.as_("date_modified"), ) .where( (appointment.company == filters.company) @@ -229,36 +379,42 @@ def get_appointment_data(filters): & (appointment.status != "Cancelled") & (appointment.follow_up == 0) & (appointment.has_no_consultation_charges == 0) - & service_type_map & (appointment.invoiced == 1) + & ( + (appointment.ref_sales_invoice.isnotnull()) + & (appointment.ref_sales_invoice != "") + ) + & (sii.docstatus == 1) + & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) ) .groupby( appointment.appointment_date, appointment.patient, appointment.name, appointment.billing_item, - appointment.coverage_plan_name, + appointment.ref_sales_invoice, appointment.practitioner, appointment.status, ) - ).run(as_dict=True) + ) + + if filters.service_type: + cash_appointment_query = cash_appointment_query.where( + item.item_group == filters.service_type + ) - return appointment_data_non_cash + appointment_data_cash + cash_appointment_data = cash_appointment_query.run(as_dict=True) + return cash_appointment_data -def get_lab_data(filters): + +def get_insurance_lab_data(filters): lab = DocType("Lab Test") lab_prescription = DocType("Lab Prescription") template = DocType("Lab Test Template") - sii = DocType("Sales Invoice Item") encounter = DocType("Patient Encounter") - service_type_map = None - if filters.service_type: - service_type_map = template.lab_test_group == filters.service_type - else: - service_type_map = template.lab_test_group.isnotnull() - insurance_lab_data = ( + insurance_lab_query = ( frappe.qb.from_(lab) .inner_join(lab_prescription) .on(lab.hms_tz_ref_childname == lab_prescription.name) @@ -278,10 +434,7 @@ def get_lab_data(filters): .as_("patient_type"), template.lab_test_group.as_("service_type"), lab.template.as_("service_name"), - Case() - .when(lab.prescribe == 1, "Cash") - .else_(lab.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + lab.hms_tz_insurance_coverage_plan.as_("payment_method"), Count("*").as_("qty"), fn.Max(lab_prescription.amount).as_("rate"), Sum(lab_prescription.amount).as_("amount"), @@ -289,15 +442,25 @@ def get_lab_data(filters): lab.practitioner.as_("practitioner"), lab.department.as_("department"), lab_prescription.department_hsu.as_("service_unit"), + lab.modified.as_("date_modified"), ) .where( (lab.company == filters.company) & (lab.result_date.between(filters.from_date, filters.to_date)) & ~lab.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"]) + & ( + ( + lab.hms_tz_insurance_coverage_plan.isnotnull() + & (lab.hms_tz_insurance_coverage_plan != "") + ) + ) & (lab.docstatus != 2) & (lab.ref_doctype == "Patient Encounter") & (lab.ref_docname == lab_prescription.parent) - & service_type_map + & (lab.prescribe == 0) + & (lab_prescription.prescribe == 0) + & (lab_prescription.is_cancelled == 0) + & (lab_prescription.is_not_available_inhouse == 0) ) .groupby( lab.result_date, @@ -308,9 +471,33 @@ def get_lab_data(filters): lab.practitioner, lab.docstatus, ) - ).run(as_dict=True) + ) + + if filters.service_type: + insurance_lab_query = insurance_lab_query.where( + template.lab_test_group == filters.service_type + ) - cash_lab_data = ( + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_lab_query = insurance_lab_query.where( + lab.insurance_company == filters.payment_mode + ) + + insurance_lab_data = insurance_lab_query.run(as_dict=True) + + return insurance_lab_data + + +def get_cash_lab_data(filters): + lab = DocType("Lab Test") + lab_prescription = DocType("Lab Prescription") + template = DocType("Lab Test Template") + sii = DocType("Sales Invoice Item") + encounter = DocType("Patient Encounter") + + # Paid Lab Data for OPD Patients, Admitted and Discharged Patients + # Link to Sales invoice + paid_cash_lab_query = ( frappe.qb.from_(lab) .inner_join(lab_prescription) .on(lab.hms_tz_ref_childname == lab_prescription.name) @@ -332,33 +519,92 @@ def get_lab_data(filters): .as_("patient_type"), template.lab_test_group.as_("service_type"), lab.template.as_("service_name"), - Case() - .when(lab.prescribe == 1, "Cash") - .else_(lab.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), + Count("*").as_("qty"), + fn.Max(lab_prescription.amount).as_("rate"), + Sum(sii.amount - sii.net_amount).as_("discount_amount"), + Sum(sii.net_amount).as_("amount"), + Case().when(lab.docstatus == 1, "Submitted").else_("Draft").as_("status"), + lab.practitioner.as_("practitioner"), + lab.department.as_("department"), + lab_prescription.department_hsu.as_("service_unit"), + lab.modified.as_("date_modified"), + ) + .where( + (lab.company == filters.company) + & (lab.result_date.between(filters.from_date, filters.to_date)) + & (~lab.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) + & (lab.docstatus != 2) + & (lab.ref_doctype == "Patient Encounter") + & (lab.ref_docname == lab_prescription.parent) + & (lab.prescribe == 1) + & (lab_prescription.prescribe == 1) + & (lab_prescription.invoiced == 1) + & (lab_prescription.is_cancelled == 0) + & (lab_prescription.is_not_available_inhouse == 0) + & (sii.docstatus == 1) + & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) + ) + .groupby( + lab.result_date, + lab.patient, + lab.name, + lab.template, + lab.prescribe, + lab.practitioner, + lab.docstatus, + ) + ) + + if filters.service_type: + paid_cash_lab_query = paid_cash_lab_query.where( + template.lab_test_group == filters.service_type + ) + + paid_cash_lab_data = paid_cash_lab_query.run(as_dict=True) + + # Unpaid Lab Data for ongoing Admitted Patients + # No link to Sales invoice + unpaid_cash_lab_query = ( + frappe.qb.from_(lab) + .inner_join(lab_prescription) + .on(lab.hms_tz_ref_childname == lab_prescription.name) + .inner_join(template) + .on(lab.template == template.name) + .inner_join(encounter) + .on(lab.ref_docname == encounter.name) + .select( + lab.result_date.as_("date"), + encounter.appointment.as_("appointment_no"), + lab.name.as_("bill_no"), + lab.patient.as_("patient"), + lab.patient_name.as_("patient_name"), + ValueWrapper("In-Patient").as_("patient_type"), + template.lab_test_group.as_("service_type"), + lab.template.as_("service_name"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(lab_prescription.amount).as_("rate"), Sum(lab_prescription.amount).as_("amount"), - Case() - .when( - lab.prescribe == 1, - Sum(sii.amount - sii.net_amount), - ) - .else_(0) - .as_("discount_amount"), Case().when(lab.docstatus == 1, "Submitted").else_("Draft").as_("status"), lab.practitioner.as_("practitioner"), lab.department.as_("department"), lab_prescription.department_hsu.as_("service_unit"), + lab.modified.as_("date_modified"), ) .where( (lab.company == filters.company) & (lab.result_date.between(filters.from_date, filters.to_date)) - & ~lab.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"]) + & (~lab.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) & (lab.docstatus != 2) & (lab.ref_doctype == "Patient Encounter") & (lab.ref_docname == lab_prescription.parent) - & service_type_map + & ((lab.inpatient_record.isnotnull()) & (lab.inpatient_record != "")) + & (lab.prescribe == 1) + & (lab_prescription.prescribe == 1) + & (lab_prescription.invoiced == 0) + & (lab_prescription.is_cancelled == 0) + & (lab_prescription.is_not_available_inhouse == 0) ) .groupby( lab.result_date, @@ -369,24 +615,24 @@ def get_lab_data(filters): lab.practitioner, lab.docstatus, ) - ).run(as_dict=True) + ) + if filters.service_type: + unpaid_cash_lab_query = unpaid_cash_lab_query.where( + template.lab_test_group == filters.service_type + ) + + unpaid_cash_lab_data = unpaid_cash_lab_query.run(as_dict=True) - return insurance_lab_data + cash_lab_data + return paid_cash_lab_data + unpaid_cash_lab_data -def get_radiology_data(filters): +def get_insurance_radiology_data(filters): rad = DocType("Radiology Examination") rad_prescription = DocType("Radiology Procedure Prescription") template = DocType("Radiology Examination Template") - sii = DocType("Sales Invoice Item") encounter = DocType("Patient Encounter") - service_type_map = None - if filters.service_type: - service_type_map = template.item_group == filters.service_type - else: - service_type_map = template.item_group.isnotnull() - insurance_rad_data = ( + insurance_rad_query = ( frappe.qb.from_(rad) .inner_join(rad_prescription) .on(rad.hms_tz_ref_childname == rad_prescription.name) @@ -406,10 +652,7 @@ def get_radiology_data(filters): .as_("patient_type"), template.item_group.as_("service_type"), rad.radiology_examination_template.as_("service_name"), - Case() - .when(rad.prescribe == 1, "Cash") - .else_(rad.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + rad.hms_tz_insurance_coverage_plan.as_("payment_method"), Count("*").as_("qty"), fn.Max(rad_prescription.amount).as_("rate"), Sum(rad_prescription.amount).as_("amount"), @@ -417,17 +660,23 @@ def get_radiology_data(filters): rad.practitioner.as_("practitioner"), rad.medical_department.as_("department"), rad_prescription.department_hsu.as_("service_unit"), + rad.modified.as_("date_modified"), ) .where( (rad.company == filters.company) & (rad.start_date.between(filters.from_date, filters.to_date)) - & ~rad.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"]) + & (~rad.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) + & ( + (rad.hms_tz_insurance_coverage_plan.isnotnull()) + & (rad.hms_tz_insurance_coverage_plan != "") + ) & (rad.docstatus != 2) & (rad.ref_doctype == "Patient Encounter") - & (rad.ref_docname.isnotnull()) & (rad.ref_docname == rad_prescription.parent) - & (rad_prescription.invoiced == 0) - & service_type_map + & (rad.prescribe == 0) + & (rad_prescription.prescribe == 0) + & (rad_prescription.is_cancelled == 0) + & (rad_prescription.is_not_available_inhouse == 0) ) .groupby( rad.start_date, @@ -438,9 +687,33 @@ def get_radiology_data(filters): rad.practitioner, rad.docstatus, ) - ).run(as_dict=True) + ) + + if filters.service_type: + insurance_rad_query = insurance_rad_query.where( + template.item_group == filters.service_type + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_rad_query = insurance_rad_query.where( + rad.insurance_company == filters.payment_mode + ) + + insurance_rad_data = insurance_rad_query.run(as_dict=True) + + return insurance_rad_data + + +def get_cash_radiology_data(filters): + rad = DocType("Radiology Examination") + rad_prescription = DocType("Radiology Procedure Prescription") + template = DocType("Radiology Examination Template") + sii = DocType("Sales Invoice Item") + encounter = DocType("Patient Encounter") - cash_rad_data = ( + # Paid Radiology Data for OPD Patients, Admitted and Discharged Patients + # Link to Sales invoice + paid_cash_rad_query = ( frappe.qb.from_(rad) .inner_join(rad_prescription) .on(rad.hms_tz_ref_childname == rad_prescription.name) @@ -462,24 +735,16 @@ def get_radiology_data(filters): .as_("patient_type"), template.item_group.as_("service_type"), rad.radiology_examination_template.as_("service_name"), - Case() - .when(rad.prescribe == 1, "Cash") - .else_(rad.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(rad_prescription.amount).as_("rate"), - Sum(rad_prescription.amount).as_("amount"), - Case() - .when( - rad.prescribe == 1, - Sum(sii.amount - sii.net_amount), - ) - .else_(0) - .as_("discount_amount"), + Sum(sii.net_amount).as_("amount"), + Sum(sii.amount - sii.net_amount).as_("discount_amount"), Case().when(rad.docstatus == 1, "Submitted").else_("Draft").as_("status"), rad.practitioner.as_("practitioner"), rad.medical_department.as_("department"), rad_prescription.department_hsu.as_("service_unit"), + rad.modified.as_("date_modified"), ) .where( (rad.company == filters.company) @@ -487,10 +752,14 @@ def get_radiology_data(filters): & ~rad.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"]) & (rad.docstatus != 2) & (rad.ref_doctype == "Patient Encounter") - & (rad.ref_docname.isnotnull()) & (rad.ref_docname == rad_prescription.parent) + & (rad.prescribe == 1) & (rad_prescription.invoiced == 1) - & service_type_map + & (rad_prescription.prescribe == 1) + & (rad_prescription.is_cancelled == 0) + & (rad_prescription.is_not_available_inhouse == 0) + & (sii.docstatus == 1) + & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) ) .groupby( rad.start_date, @@ -501,24 +770,86 @@ def get_radiology_data(filters): rad.practitioner, rad.docstatus, ) - ).run(as_dict=True) + ) - return insurance_rad_data + cash_rad_data + if filters.service_type: + paid_cash_rad_query = paid_cash_rad_query.where( + template.item_group == filters.service_type + ) + paid_cash_rad_data = paid_cash_rad_query.run(as_dict=True) -def get_procedure_data(filters): + # Unpaid Radiology Data for ongoing Admitted Patients + # No link to Sales invoice + unpaid_cash_rad_query = ( + frappe.qb.from_(rad) + .inner_join(rad_prescription) + .on(rad.hms_tz_ref_childname == rad_prescription.name) + .inner_join(template) + .on(rad.radiology_examination_template == template.name) + .inner_join(encounter) + .on(rad.ref_docname == encounter.name) + .select( + rad.start_date.as_("date"), + encounter.appointment.as_("appointment_no"), + rad.name.as_("bill_no"), + rad.patient.as_("patient"), + rad.patient_name.as_("patient_name"), + ValueWrapper("In-Patient").as_("patient_type"), + template.item_group.as_("service_type"), + rad.radiology_examination_template.as_("service_name"), + ValueWrapper("Cash").as_("payment_method"), + Count("*").as_("qty"), + fn.Max(rad_prescription.amount).as_("rate"), + Sum(rad_prescription.amount).as_("amount"), + Case().when(rad.docstatus == 1, "Submitted").else_("Draft").as_("status"), + rad.practitioner.as_("practitioner"), + rad.medical_department.as_("department"), + rad_prescription.department_hsu.as_("service_unit"), + rad.modified.as_("date_modified"), + ) + .where( + (rad.company == filters.company) + & (rad.start_date.between(filters.from_date, filters.to_date)) + & (~rad.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) + & (rad.docstatus != 2) + & (rad.ref_doctype == "Patient Encounter") + & (rad.ref_docname == rad_prescription.parent) + & ((rad.inpatient_record.isnotnull()) & (rad.inpatient_record != "")) + & (rad.prescribe == 1) + & (rad_prescription.prescribe == 1) + & (rad_prescription.invoiced == 0) + & (rad_prescription.is_cancelled == 0) + & (rad_prescription.is_not_available_inhouse == 0) + ) + .groupby( + rad.start_date, + rad.patient, + rad.name, + rad.radiology_examination_template, + rad.hms_tz_insurance_coverage_plan, + rad.practitioner, + rad.docstatus, + ) + ) + + if filters.service_type: + unpaid_cash_rad_query = unpaid_cash_rad_query.where( + template.item_group == filters.service_type + ) + + unpaid_cash_rad_data = unpaid_cash_rad_query.run(as_dict=True) + + return paid_cash_rad_data + unpaid_cash_rad_data + + +def get_insurance_procedure_data(filters): procedure = DocType("Clinical Procedure") pp = DocType("Procedure Prescription") template = DocType("Clinical Procedure Template") - sii = DocType("Sales Invoice Item") encounter = DocType("Patient Encounter") - service_type_map = None - if filters.service_type: - service_type_map = template.item_group == filters.service_type - else: - service_type_map = template.item_group.isnotnull() - insurance_procedure_data = ( + insurance_procedure_query = ( frappe.qb.from_(procedure) .inner_join(pp) .on(procedure.hms_tz_ref_childname == pp.name) @@ -538,10 +869,7 @@ def get_procedure_data(filters): .as_("patient_type"), template.item_group.as_("service_type"), procedure.procedure_template.as_("service_name"), - Case() - .when(procedure.prescribe == 1, "Cash") - .else_(procedure.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + procedure.hms_tz_insurance_coverage_plan.as_("payment_method"), Count("*").as_("qty"), fn.Max(pp.amount).as_("rate"), Sum(pp.amount).as_("amount"), @@ -552,19 +880,27 @@ def get_procedure_data(filters): procedure.practitioner.as_("practitioner"), procedure.medical_department.as_("department"), pp.department_hsu.as_("service_unit"), + procedure.modified.as_("date_modified"), ) .where( (procedure.company == filters.company) & (procedure.start_date.between(filters.from_date, filters.to_date)) - & ~procedure.workflow_state.isin( - ["Not Serviced", "Submitted but Not Serviced"] + & ( + ~procedure.workflow_state.isin( + ["Not Serviced", "Submitted but Not Serviced"] + ) + ) + & ( + (procedure.hms_tz_insurance_coverage_plan.isnotnull()) + & (procedure.hms_tz_insurance_coverage_plan != "") ) & (procedure.docstatus != 2) & (procedure.ref_doctype == "Patient Encounter") - & (procedure.ref_docname.isnotnull()) & (procedure.ref_docname == pp.parent) - & (pp.invoiced == 0) - & service_type_map + & (pp.prescribe == 0) + & (pp.is_cancelled == 0) + & (pp.is_not_available_inhouse == 0) + & (procedure.prescribe == 0) ) .groupby( procedure.start_date, @@ -575,9 +911,33 @@ def get_procedure_data(filters): procedure.practitioner, procedure.docstatus, ) - ).run(as_dict=True) + ) + + if filters.service_type: + insurance_procedure_query = insurance_procedure_query.where( + template.item_group == filters.service_type + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_procedure_query = insurance_procedure_query.where( + procedure.insurance_company == filters.payment_mode + ) + + insurance_procedure_data = insurance_procedure_query.run(as_dict=True) + + return insurance_procedure_data + - cash_procedure_data = ( +def get_cash_procedure_data(filters): + procedure = DocType("Clinical Procedure") + pp = DocType("Procedure Prescription") + template = DocType("Clinical Procedure Template") + sii = DocType("Sales Invoice Item") + encounter = DocType("Patient Encounter") + + # Paid Procedure Data for OPD Patients, Admitted and Discharged Patients + # Link to Sales invoice + paid_procedure_query = ( frappe.qb.from_(procedure) .inner_join(pp) .on(procedure.hms_tz_ref_childname == pp.name) @@ -599,20 +959,80 @@ def get_procedure_data(filters): .as_("patient_type"), template.item_group.as_("service_type"), procedure.procedure_template.as_("service_name"), - Case() - .when(procedure.prescribe == 1, "Cash") - .else_(procedure.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(pp.amount).as_("rate"), - Sum(pp.amount).as_("amount"), + Sum(sii.amount - sii.net_amount).as_("discount_amount"), + Sum(sii.net_amount).as_("amount"), Case() - .when( - procedure.prescribe == 1, - Sum(sii.amount - sii.net_amount), + .when(procedure.docstatus == 1, "Submitted") + .else_("Draft") + .as_("status"), + procedure.practitioner.as_("practitioner"), + procedure.medical_department.as_("department"), + pp.department_hsu.as_("service_unit"), + procedure.modified.as_("date_modified"), + ) + .where( + (procedure.company == filters.company) + & (procedure.start_date.between(filters.from_date, filters.to_date)) + & ( + ~procedure.workflow_state.isin( + ["Not Serviced", "Submitted but Not Serviced"] + ) ) - .else_(0) - .as_("discount_amount"), + & (procedure.docstatus != 2) + & (procedure.ref_doctype == "Patient Encounter") + & (procedure.ref_docname.isnotnull()) + & (procedure.ref_docname == pp.parent) + & (pp.invoiced == 1) + & (procedure.prescribe == 1) + & (pp.prescribe == 1) + & (pp.is_cancelled == 0) + & (pp.is_not_available_inhouse == 0) + & (sii.docstatus == 1) + & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) + ) + .groupby( + procedure.start_date, + procedure.patient, + procedure.name, + procedure.procedure_template, + procedure.prescribe, + procedure.practitioner, + procedure.docstatus, + ) + ) + + if filters.service_type: + paid_procedure_query = paid_procedure_query.where( + template.item_group == filters.service_type + ) + paid_procedure_data = paid_procedure_query.run(as_dict=True) + + # Unpaid Radiology Data for ongoing Admitted Patients + # No link to Sales invoice + unpaid_procedure_query = ( + frappe.qb.from_(procedure) + .inner_join(pp) + .on(procedure.hms_tz_ref_childname == pp.name) + .inner_join(template) + .on(procedure.procedure_template == template.name) + .inner_join(encounter) + .on(procedure.ref_docname == encounter.name) + .select( + procedure.start_date.as_("date"), + encounter.appointment.as_("appointment_no"), + procedure.name.as_("bill_no"), + procedure.patient.as_("patient"), + procedure.patient_name.as_("patient_name"), + ValueWrapper("In-Patient").as_("patient_type"), + template.item_group.as_("service_type"), + procedure.procedure_template.as_("service_name"), + ValueWrapper("Cash").as_("payment_method"), + Count("*").as_("qty"), + fn.Max(pp.amount).as_("rate"), + Sum(pp.amount).as_("amount"), Case() .when(procedure.docstatus == 1, "Submitted") .else_("Draft") @@ -620,179 +1040,398 @@ def get_procedure_data(filters): procedure.practitioner.as_("practitioner"), procedure.medical_department.as_("department"), pp.department_hsu.as_("service_unit"), + procedure.modified.as_("date_modified"), ) .where( (procedure.company == filters.company) & (procedure.start_date.between(filters.from_date, filters.to_date)) - & ~procedure.workflow_state.isin( - ["Not Serviced", "Submitted but Not Serviced"] + & ( + ~procedure.workflow_state.isin( + ["Not Serviced", "Submitted but Not Serviced"] + ) ) & (procedure.docstatus != 2) & (procedure.ref_doctype == "Patient Encounter") & (procedure.ref_docname.isnotnull()) & (procedure.ref_docname == pp.parent) - & (pp.invoiced == 1) - & service_type_map + & ( + (procedure.inpatient_record.isnotnull()) + & (procedure.inpatient_record != "") + ) + & (procedure.prescribe == 1) + & (pp.prescribe == 1) + & (pp.invoiced == 0) + & (pp.is_cancelled == 0) + & (pp.is_not_available_inhouse == 0) ) .groupby( procedure.start_date, procedure.patient, procedure.name, procedure.procedure_template, - procedure.hms_tz_insurance_coverage_plan, + procedure.prescribe, procedure.practitioner, procedure.docstatus, ) - ).run(as_dict=True) + ) - return insurance_procedure_data + cash_procedure_data + if filters.service_type: + unpaid_procedure_query = unpaid_procedure_query.where( + template.item_group == filters.service_type + ) + unpaid_procedure_data = unpaid_procedure_query.run(as_dict=True) + + return paid_procedure_data + unpaid_procedure_data -def get_drug_data(filters): +def get_insurance_drug_data(filters): + pe = DocType("Patient Encounter") + dp = DocType("Drug Prescription") dn = DocType("Delivery Note") - dni = DocType("Delivery Note Item") + md = DocType("Medication") + + insurance_drug_query = ( + frappe.qb.from_(pe) + .inner_join(dp) + .on(dp.parent == pe.name) + .inner_join(md) + .on(dp.drug_code == md.name) + .inner_join(dn) + .on(pe.name == dn.reference_name) + .select( + pe.encounter_date.as_("date"), + pe.appointment.as_("appointment_no"), + dn.name.as_("bill_no"), + pe.patient.as_("patient"), + pe.patient_name.as_("patient_name"), + Case() + .when(pe.inpatient_record.isnull(), "Out-Patient") + .else_("In-Patient") + .as_("patient_type"), + md.item_group.as_("service_type"), + dp.drug_code.as_("service_name"), + dn.coverage_plan_name.as_("payment_method"), + Sum(dp.quantity - dp.quantity_returned).as_("qty"), + fn.Max(dp.amount).as_("rate"), + Sum((dp.quantity - dp.quantity_returned) * dp.amount).as_("amount"), + Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), + pe.practitioner.as_("practitioner"), + md.healthcare_service_order_category.as_("department"), + pe.healthcare_service_unit.as_("service_unit"), + dn.modified.as_("date_modified"), + ) + .where( + (pe.company == filters.company) + & (pe.encounter_date.between(filters.from_date, filters.to_date)) + & (dp.prescribe == 0) + & (dp.is_cancelled == 0) + & (dp.is_not_available_inhouse == 0) + & (dn.docstatus != 2) + & (dn.is_return == 0) + & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) + & ((dn.coverage_plan_name.isnotnull()) & (dn.coverage_plan_name != "")) + ) + .groupby( + pe.encounter_date, + pe.patient, + dn.name, + dp.drug_code, + pe.insurance_coverage_plan, + pe.practitioner, + dn.docstatus, + ) + ) + if filters.service_type: + insurance_drug_query = insurance_drug_query.where( + md.item_group == filters.service_type + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_drug_query = insurance_drug_query.where( + pe.insurance_company == filters.payment_mode + ) + + insurance_drug_data = insurance_drug_query.run(as_dict=True) + + return insurance_drug_data + + +def get_cash_drug_data(filters): + pe = DocType("Patient Encounter") dp = DocType("Drug Prescription") + dn = DocType("Delivery Note") md = DocType("Medication") - si = DocType("Sales Invoice") sii = DocType("Sales Invoice Item") - pe = DocType("Patient Encounter") - service_type_map = None - if filters.service_type: - service_type_map = md.item_group == filters.service_type - else: - service_type_map = md.item_group.isnotnull() - insurance_drug_data = ( - frappe.qb.from_(dni) - .inner_join(dn) - .on(dni.parent == dn.name) + + # Paid Drug Data for OPD Patients, Admitted and Discharged Patients + # Link to Sales invoice + paid_drug_query = ( + frappe.qb.from_(pe) + .inner_join(dp) + .on(dp.parent == pe.name) .inner_join(md) - .on(dni.item_code == md.item) - .inner_join(pe) - .on(dn.reference_name == pe.name) + .on(dp.drug_code == md.name) + .inner_join(sii) + .on(dp.name == sii.reference_dn) + .inner_join(dn) + .on(pe.name == dn.reference_name) .select( - dn.posting_date.as_("date"), - dn.hms_tz_appointment_no.as_("appointment_no"), + pe.encounter_date.as_("date"), + pe.appointment.as_("appointment_no"), dn.name.as_("bill_no"), - dn.patient.as_("patient"), - dn.patient_name.as_("patient_name"), + pe.patient.as_("patient"), + pe.patient_name.as_("patient_name"), Case() .when(pe.inpatient_record.isnull(), "Out-Patient") .else_("In-Patient") .as_("patient_type"), md.item_group.as_("service_type"), + dp.drug_code.as_("service_name"), + ValueWrapper("Cash").as_("payment_method"), + Sum(dp.quantity - dp.quantity_returned).as_("qty"), + fn.Max(dp.amount).as_("rate"), + Sum( + ((dp.quantity - dp.quantity_returned) * dp.amount) - sii.net_amount + ).as_("discount_amount"), + Sum((dp.quantity - dp.quantity_returned) * sii.net_rate).as_("amount"), + Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), + pe.practitioner.as_("practitioner"), + md.healthcare_service_order_category.as_("department"), + pe.healthcare_service_unit.as_("service_unit"), + dn.modified.as_("date_modified"), + ) + .where( + (pe.company == filters.company) + & (pe.encounter_date.between(filters.from_date, filters.to_date)) + & (dp.prescribe == 1) + & (dp.invoiced == 1) + & (dp.is_cancelled == 0) + & (dp.is_not_available_inhouse == 0) + & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) + & ((dn.coverage_plan_name.isnull()) | (dn.coverage_plan_name == "")) + & (dn.docstatus != 2) + & (dn.is_return == 0) + & (sii.docstatus == 1) + & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) + ) + .groupby( + pe.encounter_date, + pe.patient, + dn.name, + dp.drug_code, + ValueWrapper("Cash").as_("payment_method"), + pe.practitioner, + dn.docstatus, + ) + ) + if filters.service_type: + paid_drug_query = paid_drug_query.where(md.item_group == filters.service_type) + + paid_drug_data = paid_drug_query.run(as_dict=True) + + # Unpaid Drug Data for ongoing Admitted Patients + # No link to Sales invoice + unpaid_drug_query = ( + frappe.qb.from_(pe) + .inner_join(dp) + .on(dp.parent == pe.name) + .inner_join(md) + .on(dp.drug_code == md.name) + .inner_join(dn) + .on(pe.name == dn.reference_name) + .select( + pe.encounter_date.as_("date"), + pe.appointment.as_("appointment_no"), + dn.name.as_("bill_no"), + pe.patient.as_("patient"), + pe.patient_name.as_("patient_name"), + ValueWrapper("In-Patient").as_("patient_type"), + md.item_group.as_("service_type"), + dp.drug_code.as_("service_name"), + ValueWrapper("Cash").as_("payment_method"), + Sum(dp.quantity - dp.quantity_returned).as_("qty"), + fn.Max(dp.amount).as_("rate"), + Sum((dp.quantity - dp.quantity_returned) * dp.amount).as_("amount"), + Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), + pe.practitioner.as_("practitioner"), + md.healthcare_service_order_category.as_("department"), + pe.healthcare_service_unit.as_("service_unit"), + dn.modified.as_("date_modified"), + ) + .where( + (pe.company == filters.company) + & (pe.encounter_date.between(filters.from_date, filters.to_date)) + & ((pe.inpatient_record.isnotnull()) & (pe.inpatient_record != "")) + & (dp.prescribe == 1) + & (dp.invoiced == 0) + & (dp.is_cancelled == 0) + & (dp.is_not_available_inhouse == 0) + & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) + & ((dn.coverage_plan_name.isnull()) | (dn.coverage_plan_name == "")) + & (dn.docstatus != 2) + & (dn.is_return == 0) + ) + .groupby( + pe.encounter_date, + pe.patient, + dn.name, + dp.drug_code, + ValueWrapper("Cash").as_("payment_method"), + pe.practitioner, + dn.docstatus, + ) + ) + if filters.service_type: + unpaid_drug_query = unpaid_drug_query.where( + md.item_group == filters.service_type + ) + + unpaid_drug_data = unpaid_drug_query.run(as_dict=True) + + return paid_drug_data + unpaid_drug_data + + +def get_direct_sales_drug_data(filters): + dn = DocType("Delivery Note") + dni = DocType("Delivery Note Item") + + direct_sales_drug_query = ( + frappe.qb.from_(dn) + .inner_join(dni) + .on(dn.name == dni.parent) + .select( + dn.posting_date.as_("date"), + dn.name.as_("bill_no"), + ValueWrapper("Outsider Customer").as_("patient"), + dn.customer_name.as_("patient_name"), + ValueWrapper("Out-Patient").as_("patient_type"), + dni.item_group.as_("service_type"), dni.item_code.as_("service_name"), - dn.coverage_plan_name.as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Sum(dni.qty).as_("qty"), - fn.Max(dni.rate).as_("rate"), - Sum((dni.qty * dni.rate)).as_("amount"), - Sum(dni.discount_amount).as_("discount_amount"), + fn.Max(dni.net_rate).as_("rate"), + Sum(dni.net_amount).as_("amount"), + Sum(dni.amount - dni.net_amount).as_("discount_amount"), Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), dni.healthcare_practitioner.as_("practitioner"), - md.healthcare_service_order_category.as_("department"), + ValueWrapper("Pharmacy").as_("department"), dni.healthcare_service_unit.as_("service_unit"), + dn.modified.as_("date_modified"), ) .where( (dn.company == filters.company) & (dn.posting_date.between(filters.from_date, filters.to_date)) - & (~dn.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) + & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) & (dn.docstatus != 2) & (dn.is_return == 0) - & (dn.form_sales_invoice.isnull()) - & (dn.coverage_plan_name.isnotnull()) - & (md.disabled == 0) - & (service_type_map) + & (dn.patient.isnull() | (dn.patient == "")) + & ((dn.form_sales_invoice.isnotnull()) & (dn.form_sales_invoice != "")) ) .groupby( dn.posting_date, - dn.patient, + dn.customer_name, dn.name, dni.item_code, - dn.coverage_plan_name, + dn.form_sales_invoice, dni.healthcare_practitioner, dn.docstatus, ) - ).run(as_dict=True) + ) + if filters.service_type: + direct_sales_drug_query = direct_sales_drug_query.where( + dni.item_group == filters.service_type + ) + direct_sales_drug_data = direct_sales_drug_query.run(as_dict=True) - cash_drug_data = ( - frappe.qb.from_(dni) - .inner_join(dn) - .on(dni.parent == dn.name) - .inner_join(md) - .on(dni.item_code == md.item) - .inner_join(si) - .on(dn.form_sales_invoice == si.name) - .inner_join(sii) - .on(si.name == sii.parent) - .left_join(pe) - .on(dn.reference_name == pe.name) + return direct_sales_drug_data + + +def get_insurance_therapy_data(filters): + tp = DocType("Therapy Plan") + tpd = DocType("Therapy Plan Detail") + tt = DocType("Therapy Type") + pe = DocType("Patient Encounter") + + insurance_therapy_query = ( + frappe.qb.from_(tp) + .inner_join(pe) + .on(tp.ref_docname == pe.name) + .inner_join(tpd) + .on(pe.name == tpd.parent) + .inner_join(tt) + .on(tpd.therapy_type == tt.name) .select( - dn.posting_date.as_("date"), - dn.hms_tz_appointment_no.as_("appointment_no"), - dn.name.as_("bill_no"), - Case() - .when(dn.patient.isnull(), "Outsider Customer") - .else_(dn.patient) - .as_("patient"), - Case() - .when(dn.patient.isnull(), dn.customer_name) - .else_(dn.patient_name) - .as_("patient_name"), + tp.start_date.as_("date"), + tp.hms_tz_appointment.as_("appointment_no"), + tp.name.as_("bill_no"), + tp.patient.as_("patient"), + tp.patient_name.as_("patient_name"), Case() .when(pe.inpatient_record.isnull(), "Out-Patient") .else_("In-Patient") .as_("patient_type"), - md.item_group.as_("service_type"), - dni.item_code.as_("service_name"), + tt.item_group.as_("service_type"), + tpd.therapy_type.as_("service_name"), + tp.hms_tz_insurance_coverage_plan.as_("payment_method"), + Count("*").as_("qty"), + fn.Max(tpd.amount).as_("rate"), + Sum(tpd.amount).as_("amount"), Case() - .when(dn.coverage_plan_name.isnull(), "Cash") - .else_("Cash") - .as_("payment_method"), - Sum(dni.qty).as_("qty"), - fn.Max(dni.rate).as_("rate"), - Sum(dni.qty * dni.rate).as_("amount"), - Sum(sii.amount - sii.net_amount).as_("discount_amount"), - Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), - dni.healthcare_practitioner.as_("practitioner"), - md.healthcare_service_order_category.as_("department"), - dni.healthcare_service_unit.as_("service_unit"), + .when(tp.status == "Completed", "Submitted") + .else_("Draft") + .as_("status"), + pe.practitioner.as_("practitioner"), + tt.medical_department.as_("department"), + tpd.department_hsu.as_("service_unit"), + tp.modified.as_("date_modified"), ) .where( - (dn.company == filters.company) - & (dn.posting_date.between(filters.from_date, filters.to_date)) - & (~dn.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) - & (dn.docstatus != 2) - & (dn.is_return == 0) - & (~si.status.isin(["Credit Note Issued", "Return"])) - & (dn.coverage_plan_name.isnull()) - & service_type_map + (tp.company == filters.company) + & (tp.start_date.between(filters.from_date, filters.to_date)) + & (tpd.is_cancelled == 0) + & (tpd.is_not_available_inhouse == 0) + & (tpd.invoiced == 0) + & ( + (tp.hms_tz_insurance_coverage_plan.isnotnull()) + & (tp.hms_tz_insurance_coverage_plan != "") + ) ) .groupby( - dn.posting_date, - dn.patient, - dn.name, - dni.item_code, - dn.coverage_plan_name, - dni.healthcare_practitioner, - dn.docstatus, + tp.start_date, + tp.patient, + tp.name, + tpd.therapy_type, + tp.hms_tz_insurance_coverage_plan, + pe.practitioner, + tp.status, + ) + ) + + if filters.service_type: + insurance_therapy_query = insurance_therapy_query.where( + tt.item_group == filters.service_type + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_therapy_query = insurance_therapy_query.where( + pe.insurance_company == filters.payment_mode ) - ).run(as_dict=True) - return insurance_drug_data + cash_drug_data + insurance_therapy_data = insurance_therapy_query.run(as_dict=True) + return insurance_therapy_data -def get_therapy_data(filters): + +def get_cash_therapy_data(filters): tp = DocType("Therapy Plan") tpd = DocType("Therapy Plan Detail") tt = DocType("Therapy Type") sii = DocType("Sales Invoice Item") pe = DocType("Patient Encounter") - service_type_map = None - if filters.service_type: - service_type_map = tt.item_group == filters.service_type - else: - service_type_map = tt.item_group.isnotnull() - insurance_therapy_data = ( + # Paid Therapy Data for OPD Patients, Admitted and Discharged Patients + # Link to Sales invoice + paid_therapy_query = ( frappe.qb.from_(tp) .inner_join(pe) .on(tp.ref_docname == pe.name) @@ -800,6 +1439,8 @@ def get_therapy_data(filters): .on(pe.name == tpd.parent) .inner_join(tt) .on(tpd.therapy_type == tt.name) + .inner_join(sii) + .on(tpd.name == sii.reference_dn) .select( tp.start_date.as_("date"), tp.hms_tz_appointment.as_("appointment_no"), @@ -812,13 +1453,11 @@ def get_therapy_data(filters): .as_("patient_type"), tt.item_group.as_("service_type"), tpd.therapy_type.as_("service_name"), - Case() - .when(tpd.prescribe == 1, "Cash") - .else_(tp.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(tpd.amount).as_("rate"), - Sum(tpd.amount).as_("amount"), + (sii.amount - sii.net_amount).as_("discount_amount"), + Sum(sii.net_amount).as_("amount"), Case() .when(tp.status == "Completed", "Submitted") .else_("Draft") @@ -826,27 +1465,39 @@ def get_therapy_data(filters): pe.practitioner.as_("practitioner"), tt.medical_department.as_("department"), tpd.department_hsu.as_("service_unit"), + tp.modified.as_("date_modified"), ) .where( (tp.company == filters.company) & (tp.start_date.between(filters.from_date, filters.to_date)) + & (tpd.prescribe == 1) & (tpd.is_cancelled == 0) & (tpd.is_not_available_inhouse == 0) - & (tpd.invoiced == 0) - & service_type_map + & (tpd.invoiced == 1) + & (sii.docstatus == 1) + & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) ) .groupby( tp.start_date, tp.patient, tp.name, tpd.therapy_type, - tp.hms_tz_insurance_coverage_plan, + ValueWrapper("Cash").as_("payment_method"), pe.practitioner, tp.status, ) - ).run(as_dict=True) + ) + + if filters.service_type: + paid_therapy_query = paid_therapy_query.where( + tt.item_group == filters.service_type + ) + + paid_therapy_data = paid_therapy_query.run(as_dict=True) - cash_therapy_data = ( + # Unpaid Therapy Data for ongoing Admitted Patients + # No link to Sales invoice + unpaid_therapy_query = ( frappe.qb.from_(tp) .inner_join(pe) .on(tp.ref_docname == pe.name) @@ -854,8 +1505,6 @@ def get_therapy_data(filters): .on(pe.name == tpd.parent) .inner_join(tt) .on(tpd.therapy_type == tt.name) - .inner_join(sii) - .on(tpd.name == sii.reference_dn) .select( tp.start_date.as_("date"), tp.hms_tz_appointment.as_("appointment_no"), @@ -868,63 +1517,56 @@ def get_therapy_data(filters): .as_("patient_type"), tt.item_group.as_("service_type"), tpd.therapy_type.as_("service_name"), - Case() - .when(tpd.prescribe == 1, "Cash") - .else_(tp.hms_tz_insurance_coverage_plan) - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(tpd.amount).as_("rate"), Sum(tpd.amount).as_("amount"), Case() - .when( - tpd.prescribe == 1, - Sum(sii.amount - sii.net_amount), - ) - .else_(0) - .as_("discount_amount"), - Case() .when(tp.status == "Completed", "Submitted") .else_("Draft") .as_("status"), pe.practitioner.as_("practitioner"), tt.medical_department.as_("department"), tpd.department_hsu.as_("service_unit"), + tp.modified.as_("date_modified"), ) .where( (tp.company == filters.company) & (tp.start_date.between(filters.from_date, filters.to_date)) + & (tpd.prescribe == 1) & (tpd.is_cancelled == 0) & (tpd.is_not_available_inhouse == 0) - & (tpd.invoiced == 1) - & service_type_map + & (tpd.invoiced == 0) + & ((pe.inpatient_record.isnotnull()) & (pe.inpatient_record != "")) ) .groupby( tp.start_date, tp.patient, tp.name, tpd.therapy_type, - tp.hms_tz_insurance_coverage_plan, + ValueWrapper("Cash").as_("payment_method"), pe.practitioner, tp.status, ) - ).run(as_dict=True) + ) + + if filters.service_type: + unpaid_therapy_query = unpaid_therapy_query.where( + tt.item_group == filters.service_type + ) + + unpaid_therapy_data = unpaid_therapy_query.run(as_dict=True) - return insurance_therapy_data + cash_therapy_data + return paid_therapy_data + unpaid_therapy_data -def get_ipd_beds_data(filters): +def get_insurance_ipd_beds_data(filters): io = DocType("Inpatient Occupancy") ip = DocType("Inpatient Record") hsu = DocType("Healthcare Service Unit") hsut = DocType("Healthcare Service Unit Type") - sii = DocType("Sales Invoice Item") - service_type_map = None - if filters.service_type: - service_type_map = hsut.item_group == filters.service_type - else: - service_type_map = hsut.item_group.isnotnull() - insurance_ipd_beds_data = ( + insurance_ipd_beds_query = ( frappe.qb.from_(io) .inner_join(ip) .on(io.parent == ip.name) @@ -938,10 +1580,7 @@ def get_ipd_beds_data(filters): ip.name.as_("bill_no"), ip.patient.as_("patient"), ip.patient_name.as_("patient_name"), - Case() - .when(io.is_confirmed == 1, "In-Patient") - .else_("Out Patient") - .as_("patient_type"), + ValueWrapper("In-Patient").as_("patient_type"), hsut.item_group.as_("service_type"), hsu.service_unit_type.as_("service_name"), ip.insurance_coverage_plan.as_("payment_method"), @@ -950,24 +1589,50 @@ def get_ipd_beds_data(filters): Sum(io.amount).as_("amount"), hsu.service_unit_type.as_("department"), hsu.parent_healthcare_service_unit.as_("service_unit"), + io.modified.as_("date_modified"), ) .where( (ip.company == filters.company) & (io.check_in.between(filters.from_date, filters.to_date)) & (io.is_confirmed == 1) - & (ip.insurance_coverage_plan.isnotnull()) - & service_type_map + & ( + (ip.insurance_coverage_plan.isnotnull()) + & (ip.insurance_coverage_plan != "") + ) ) .groupby( fn.Date(io.check_in), ip.patient, ip.name, + io.name, hsu.service_unit_type, ip.insurance_coverage_plan, ) - ).run(as_dict=True) + ) + + if filters.service_type: + insurance_ipd_beds_query = insurance_ipd_beds_query.where( + hsut.item_group == filters.service_type + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_ipd_beds_query = insurance_ipd_beds_query.where( + ip.insurance_company == filters.payment_mode + ) + + insurance_ipd_beds_data = insurance_ipd_beds_query.run(as_dict=True) + + return insurance_ipd_beds_data + + +def get_cash_ipd_beds_data(filters): + io = DocType("Inpatient Occupancy") + ip = DocType("Inpatient Record") + hsu = DocType("Healthcare Service Unit") + hsut = DocType("Healthcare Service Unit Type") + sii = DocType("Sales Invoice Item") - cash_ipd_beds_data = ( + cash_ipd_beds_query = ( frappe.qb.from_(io) .inner_join(ip) .on(io.parent == ip.name) @@ -983,62 +1648,53 @@ def get_ipd_beds_data(filters): ip.name.as_("bill_no"), ip.patient.as_("patient"), ip.patient_name.as_("patient_name"), - Case() - .when(io.is_confirmed == 1, "In-Patient") - .else_("Out Patient") - .as_("patient_type"), + ValueWrapper("In-Patient").as_("patient_type"), hsut.item_group.as_("service_type"), hsu.service_unit_type.as_("service_name"), - Case() - .when(io.is_confirmed == 1, "Cash") - .else_("Cash") - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(io.amount).as_("rate"), - Sum(io.amount).as_("amount"), - Case() - .when( - io.invoiced == 1, - Sum(sii.amount - sii.net_amount), - ) - .else_(0) - .as_("discount_amount"), + Sum(sii.net_amount).as_("amount"), + Sum(sii.amount - sii.net_amount).as_("discount_amount"), hsu.service_unit_type.as_("department"), hsu.parent_healthcare_service_unit.as_("service_unit"), + io.modified.as_("date_modified"), ) .where( (ip.company == filters.company) & (io.check_in.between(filters.from_date, filters.to_date)) & (io.is_confirmed == 1) & (io.invoiced == 1) - & (ip.insurance_coverage_plan.isnull()) - & service_type_map + & (ip.insurance_coverage_plan.isnull() | (ip.insurance_coverage_plan == "")) + & (sii.docstatus == 1) + & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) ) .groupby( fn.Date(io.check_in), ip.patient, ip.name, + io.name, hsu.service_unit_type, ip.insurance_coverage_plan, ) - ).run(as_dict=True) + ) + if filters.service_type: + cash_ipd_beds_query = cash_ipd_beds_query.where( + hsut.item_group == filters.service_type + ) - return insurance_ipd_beds_data + cash_ipd_beds_data + cash_ipd_beds_data = cash_ipd_beds_query.run(as_dict=True) + return cash_ipd_beds_data -def get_ipd_cons_data(filters): + +def get_insurance_ipd_cons_data(filters): ic = DocType("Inpatient Consultancy") ip = DocType("Inpatient Record") it = DocType("Item") pe = DocType("Patient Encounter") - sii = DocType("Sales Invoice Item") - service_type_map = None - if filters.service_type: - service_type_map = it.item_group == filters.service_type - else: - service_type_map = it.item_group.isnotnull() - insurance_ipd_cons_data = ( + insurance_ipd_cons_query = ( frappe.qb.from_(ic) .inner_join(ip) .on(ic.parent == ip.name) @@ -1052,10 +1708,7 @@ def get_ipd_cons_data(filters): ip.name.as_("bill_no"), ip.patient.as_("patient"), ip.patient_name.as_("patient_name"), - Case() - .when(ic.is_confirmed == 1, "In-Patient") - .else_("Out Patient") - .as_("patient_type"), + ValueWrapper("In-Patient").as_("patient_type"), it.item_group.as_("service_type"), ic.consultation_item.as_("service_name"), ip.insurance_coverage_plan.as_("payment_method"), @@ -1065,25 +1718,51 @@ def get_ipd_cons_data(filters): pe.medical_department.as_("department"), ic.healthcare_practitioner.as_("practitioner"), pe.healthcare_service_unit.as_("service_unit"), + ic.modified.as_("date_modified"), ) .where( (ip.company == filters.company) & (ic.date.between(filters.from_date, filters.to_date)) & (ic.is_confirmed == 1) - & (ip.insurance_coverage_plan.isnotnull()) - & service_type_map + & ( + (ip.insurance_coverage_plan.isnotnull()) + & (ip.insurance_coverage_plan != "") + ) ) .groupby( ic.date, ip.patient, ip.name, + ic.name, ic.consultation_item, ip.insurance_coverage_plan, ic.healthcare_practitioner, ) - ).run(as_dict=True) + ) + + if filters.service_type: + insurance_ipd_cons_query = insurance_ipd_cons_query.where( + it.item_group == filters.service_type + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + insurance_ipd_cons_query = insurance_ipd_cons_query.where( + ip.insurance_company == filters.payment_mode + ) + + insurance_ipd_cons_data = insurance_ipd_cons_query.run(as_dict=True) + + return insurance_ipd_cons_data + + +def get_cash_ipd_cons_data(filters): + ic = DocType("Inpatient Consultancy") + ip = DocType("Inpatient Record") + it = DocType("Item") + pe = DocType("Patient Encounter") + sii = DocType("Sales Invoice Item") - cash_ipd_cons_data = ( + cash_ipd_cons_query = ( frappe.qb.from_(ic) .inner_join(ip) .on(ic.parent == ip.name) @@ -1099,49 +1778,48 @@ def get_ipd_cons_data(filters): ip.name.as_("bill_no"), ip.patient.as_("patient"), ip.patient_name.as_("patient_name"), - Case() - .when(ic.is_confirmed == 1, "In-Patient") - .else_("Out Patient") - .as_("patient_type"), + ValueWrapper("In-Patient").as_("patient_type"), it.item_group.as_("service_type"), ic.consultation_item.as_("service_name"), - Case() - .when(ic.is_confirmed == 1, "Cash") - .else_("Cash") - .as_("payment_method"), + ValueWrapper("Cash").as_("payment_method"), Count("*").as_("qty"), fn.Max(ic.rate).as_("rate"), - Sum(ic.rate).as_("amount"), - Case() - .when( - ic.hms_tz_invoiced == 1, - Sum(sii.amount - sii.net_amount), - ) - .else_(0) - .as_("discount_amount"), + Sum(sii.net_amount).as_("amount"), + Sum(sii.amount - sii.net_amount).as_("discount_amount"), pe.medical_department.as_("department"), ic.healthcare_practitioner.as_("practitioner"), pe.healthcare_service_unit.as_("service_unit"), + ic.modified.as_("date_modified"), ) .where( (ip.company == filters.company) & (ic.date.between(filters.from_date, filters.to_date)) & (ic.is_confirmed == 1) & (ic.hms_tz_invoiced == 1) - & (ip.insurance_coverage_plan.isnull()) - & service_type_map + & ( + (ip.insurance_coverage_plan.isnull()) + | (ip.insurance_coverage_plan == "") + ) ) .groupby( ic.date, ip.patient, ip.name, + ic.name, ic.consultation_item, ip.insurance_coverage_plan, ic.healthcare_practitioner, ) - ).run(as_dict=True) + ) + + if filters.service_type: + cash_ipd_cons_query = cash_ipd_cons_query.where( + it.item_group == filters.service_type + ) + + cash_ipd_cons_data = cash_ipd_cons_query.run(as_dict=True) - return insurance_ipd_cons_data + cash_ipd_cons_data + return cash_ipd_cons_data def get_procedural_charges(filters): @@ -1186,6 +1864,7 @@ def get_procedural_charges(filters): Sum((sii.amount - sii.net_amount)).as_("discount_amount"), sii.net_amount.as_("net_amount"), Case().when(si.docstatus == 1, "Submitted").else_("Draft").as_("status"), + sii.modified.as_("date_modified"), ) .where( (si.company == filters.company) @@ -1208,3 +1887,320 @@ def get_procedural_charges(filters): ) ).run(as_dict=True) return procedural_charges + + +def get_cancelled_lab_items(filters): + lreturn = DocType("LRPMT Returns") + ireturn = DocType("Item Return") + pe = DocType("Patient Encounter") + lp = DocType("Lab Prescription") + + lab_item_query = ( + frappe.qb.from_(lreturn) + .inner_join(ireturn) + .on(lreturn.name == ireturn.parent) + .inner_join(lp) + .on(ireturn.child_name == lp.name) + .inner_join(pe) + .on(ireturn.encounter_no == pe.name) + .select( + fn.Date(lreturn.modified).as_("date"), + lreturn.patient.as_("patient"), + lreturn.patient_name.as_("patient_name"), + Case() + .when(lreturn.inpatient_record.isnull(), "Out-Patient") + .else_("In-Patient") + .as_("patient_type"), + ireturn.item_name.as_("service_name"), + Case() + .when( + (pe.insurance_coverage_plan.isnull()) + | (pe.insurance_coverage_plan == ""), + "Cash", + ) + .else_(pe.insurance_coverage_plan) + .as_("payment_method"), + ireturn.quantity.as_("qty"), + lp.amount.as_("rate"), + lp.amount.as_("amount"), + ireturn.reference_doctype.as_("bill_doctype"), + ireturn.reference_docname.as_("bill_no"), + ireturn.encounter_no.as_("encounter_no"), + ireturn.reason.as_("reason"), + lreturn.name.as_("reference_no"), + lreturn.appointment.as_("appointment_no"), + lreturn.requested_by.as_("requested_by"), + lreturn.approved_by.as_("approved_by"), + lreturn.modified.as_("date_modified"), + ) + .where( + (lreturn.company == filters.company) + & (lreturn.modified.between(filters.from_date, filters.to_date)) + & (lreturn.docstatus == 1) + & (lp.is_cancelled == 1) + & (ireturn.reference_doctype == "Lab Test") + & (ireturn.encounter_no.isnotnull()) + ) + ) + + if filters.get("payment_mode") and filters.get("payment_mode") != "Cash": + lab_item_query = lab_item_query.where( + (pe.insurance_company == filters.get("payment_mode")) & (lp.prescribe == 0) + ) + + if filters.get("payment_mode") and filters.get("payment_mode") == "Cash": + lab_item_query = lab_item_query.where( + # pick only prescribed items so as to include uncovered items from insurance encounters + (lp.prescribe == 1) + ) + + lab_item_data = lab_item_query.run(as_dict=True) + + return lab_item_data + + +def get_cancelled_radiology_items(filters): + lreturn = DocType("LRPMT Returns") + ireturn = DocType("Item Return") + pe = DocType("Patient Encounter") + rpp = DocType("Radiology Procedure Prescription") + + radiology_item_query = ( + frappe.qb.from_(lreturn) + .inner_join(ireturn) + .on(lreturn.name == ireturn.parent) + .inner_join(rpp) + .on(ireturn.child_name == rpp.name) + .inner_join(pe) + .on(ireturn.encounter_no == pe.name) + .select( + fn.Date(lreturn.modified).as_("date"), + lreturn.patient.as_("patient"), + lreturn.patient_name.as_("patient_name"), + Case() + .when(lreturn.inpatient_record.isnull(), "Out-Patient") + .else_("In-Patient") + .as_("patient_type"), + ireturn.item_name.as_("service_name"), + Case() + .when( + (pe.insurance_coverage_plan.isnull()) + | (pe.insurance_coverage_plan == ""), + "Cash", + ) + .else_(pe.insurance_coverage_plan) + .as_("payment_method"), + ireturn.quantity.as_("qty"), + rpp.amount.as_("rate"), + rpp.amount.as_("amount"), + ireturn.reference_doctype.as_("bill_doctype"), + ireturn.reference_docname.as_("bill_no"), + ireturn.encounter_no.as_("encounter_no"), + ireturn.reason.as_("reason"), + lreturn.name.as_("reference_no"), + lreturn.appointment.as_("appointment_no"), + lreturn.requested_by.as_("requested_by"), + lreturn.approved_by.as_("approved_by"), + lreturn.modified.as_("date_modified"), + ) + .where( + (lreturn.company == filters.company) + & (lreturn.modified.between(filters.from_date, filters.to_date)) + & (lreturn.docstatus == 1) + & (rpp.is_cancelled == 1) + & (ireturn.reference_doctype == "Radiology Examination") + & (ireturn.encounter_no.isnotnull()) + ) + ) + + if filters.get("payment_mode") and filters.get("payment_mode") != "Cash": + radiology_item_query = radiology_item_query.where( + (pe.insurance_company == filters.get("payment_mode")) & (rpp.prescribe == 0) + ) + + if filters.get("payment_mode") and filters.get("payment_mode") == "Cash": + radiology_item_query = radiology_item_query.where( + # pick only prescribed items so as to include uncovered items from insurance encounters + (rpp.prescribe == 1) + ) + + radiology_item_data = radiology_item_query.run(as_dict=True) + + return radiology_item_data + + +def get_cancelled_procedure_items(filters): + lreturn = DocType("LRPMT Returns") + ireturn = DocType("Item Return") + pe = DocType("Patient Encounter") + pp = DocType("Procedure Prescription") + + precedure_item_query = ( + frappe.qb.from_(lreturn) + .inner_join(ireturn) + .on(lreturn.name == ireturn.parent) + .inner_join(pp) + .on(ireturn.child_name == pp.name) + .inner_join(pe) + .on(ireturn.encounter_no == pe.name) + .select( + fn.Date(lreturn.modified).as_("date"), + lreturn.patient.as_("patient"), + lreturn.patient_name.as_("patient_name"), + Case() + .when(lreturn.inpatient_record.isnull(), "Out-Patient") + .else_("In-Patient") + .as_("patient_type"), + ireturn.item_name.as_("service_name"), + Case() + .when( + (pe.insurance_coverage_plan.isnull()) + | (pe.insurance_coverage_plan == ""), + "Cash", + ) + .else_(pe.insurance_coverage_plan) + .as_("payment_method"), + ireturn.quantity.as_("qty"), + pp.amount.as_("rate"), + pp.amount.as_("amount"), + ireturn.reference_doctype.as_("bill_doctype"), + ireturn.reference_docname.as_("bill_no"), + ireturn.encounter_no.as_("encounter_no"), + ireturn.reason.as_("reason"), + lreturn.name.as_("reference_no"), + lreturn.appointment.as_("appointment_no"), + lreturn.requested_by.as_("requested_by"), + lreturn.approved_by.as_("approved_by"), + lreturn.modified.as_("date_modified"), + ) + .where( + (lreturn.company == filters.company) + & (lreturn.modified.between(filters.from_date, filters.to_date)) + & (lreturn.docstatus == 1) + & (pp.is_cancelled == 1) + & (ireturn.reference_doctype == "Clinical Procedure") + & (ireturn.encounter_no.isnotnull()) + ) + ) + if filters.get("payment_mode") and filters.get("payment_mode") != "Cash": + precedure_item_query = precedure_item_query.where( + (pe.insurance_company == filters.get("payment_mode")) & (pp.prescribe == 0) + ) + + if filters.get("payment_mode") and filters.get("payment_mode") == "Cash": + precedure_item_query = precedure_item_query.where( + # pick only prescribed items so as to include uncovered items from insurance encounters + (pp.prescribe == 1) + ) + + procedure_item_data = precedure_item_query.run(as_dict=True) + + return procedure_item_data + + +def get_cancelled_drug_items(filters): + lreturn = DocType("LRPMT Returns") + mreturn = DocType("Medication Return") + pe = DocType("Patient Encounter") + dp = DocType("Drug Prescription") + + drug_item_query = ( + frappe.qb.from_(lreturn) + .inner_join(mreturn) + .on(lreturn.name == mreturn.parent) + .inner_join(dp) + .on(mreturn.child_name == dp.name) + .inner_join(pe) + .on(mreturn.encounter_no == pe.name) + .select( + fn.Date(lreturn.modified).as_("date"), + lreturn.patient.as_("patient"), + lreturn.patient_name.as_("patient_name"), + Case() + .when(lreturn.inpatient_record.isnull(), "Out-Patient") + .else_("In-Patient") + .as_("patient_type"), + mreturn.drug_name.as_("service_name"), + Case() + .when( + (pe.insurance_coverage_plan.isnull()) + | (pe.insurance_coverage_plan == ""), + "Cash", + ) + .else_(pe.insurance_coverage_plan) + .as_("payment_method"), + mreturn.quantity_prescribed.as_("qty"), + dp.amount.as_("rate"), + (mreturn.quantity_prescribed * dp.amount).as_("amount"), + ValueWrapper("Delivery Note").as_("bill_doctype"), + mreturn.delivery_note_no.as_("bill_no"), + mreturn.encounter_no.as_("encounter_no"), + mreturn.reason.as_("reason"), + lreturn.name.as_("reference_no"), + lreturn.appointment.as_("appointment_no"), + lreturn.requested_by.as_("requested_by"), + lreturn.approved_by.as_("approved_by"), + lreturn.modified.as_("date_modified"), + ) + .where( + (lreturn.company == filters.company) + & (lreturn.modified.between(filters.from_date, filters.to_date)) + & (lreturn.docstatus == 1) + & (dp.is_cancelled == 1) + & (mreturn.encounter_no.isnotnull()) + ) + ) + + if filters.get("payment_mode") and filters.get("payment_mode") != "Cash": + drug_item_query = drug_item_query.where( + (pe.insurance_company == filters.get("payment_mode")) & (dp.prescribe == 0) + ) + + if filters.get("payment_mode") and filters.get("payment_mode") == "Cash": + drug_item_query = drug_item_query.where( + # pick only prescribed items so as to include uncovered items from insurance encounters + (dp.prescribe == 1) + ) + + drug_item_data = drug_item_query.run(as_dict=True) + + return drug_item_data + + +def get_cancelled_data(filters): + cancelled_data = [] + + if not filters.get("bill_doctype"): + cancelled_data += get_cancelled_lab_items(filters) + cancelled_data += get_cancelled_radiology_items(filters) + cancelled_data += get_cancelled_procedure_items(filters) + cancelled_data += get_cancelled_drug_items(filters) + + elif filters.get("bill_doctype") == "Lab Test": + cancelled_data += get_cancelled_lab_items(filters) + + elif filters.get("bill_doctype") == "Radiology Examination": + cancelled_data += get_cancelled_radiology_items(filters) + + elif filters.get("bill_doctype") == "Clinical Procedure": + cancelled_data += get_cancelled_procedure_items(filters) + + elif filters.get("bill_doctype") == "Delivery Note": + cancelled_data += get_cancelled_drug_items(filters) + + return cancelled_data + + +@frappe.whitelist() +def get_payment_modes(company): + payment_modes = ["", "Cash"] + if not company: + return payment_modes + + insurance_companies = frappe.get_all( + "Healthcare Insurance Company", + filters={"company": company, "disabled": 0}, + pluck="name", + ) + + return payment_modes + sorted(insurance_companies) From a249df8a1a1f96238b53e8aa8968a34ebbc065ad Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Tue, 28 Nov 2023 16:33:37 +0300 Subject: [PATCH 06/12] feat: stop editing/changing/submitting/duplicating of LRPMT items or docs whose NHIF patient claim is already submitted --- .../doctype/lrpmt_returns/lrpmt_returns.py | 52 ++-- hms_tz/nhif/api/delivery_note.js | 2 + hms_tz/nhif/api/healthcare_utils.py | 59 ++++- hms_tz/nhif/api/inpatient_record.js | 51 ++-- hms_tz/nhif/api/inpatient_record.py | 61 +---- hms_tz/nhif/api/patient_encounter.py | 42 +++- .../medication_change_request.py | 222 ++++++++++++------ hms_tz/patches.txt | 1 + ...e_of_lrpmt_items_after_claim_submission.py | 18 ++ 9 files changed, 328 insertions(+), 180 deletions(-) create mode 100644 hms_tz/patches/custom_fields/stop_change_of_lrpmt_items_after_claim_submission.py diff --git a/hms_tz/hms_tz/doctype/lrpmt_returns/lrpmt_returns.py b/hms_tz/hms_tz/doctype/lrpmt_returns/lrpmt_returns.py index 2888f371..7577fb9f 100644 --- a/hms_tz/hms_tz/doctype/lrpmt_returns/lrpmt_returns.py +++ b/hms_tz/hms_tz/doctype/lrpmt_returns/lrpmt_returns.py @@ -4,16 +4,29 @@ import frappe import json from frappe import bold, _ -from frappe.model.workflow import apply_workflow -from frappe.utils import nowdate, nowtime, flt, unique from frappe.model.document import Document +from frappe.model.workflow import apply_workflow +from frappe.utils import nowdate, nowtime, flt, unique, get_fullname, get_url_to_form +from hms_tz.nhif.api.healthcare_utils import validate_nhif_patient_claim_status class LRPMTReturns(Document): def validate(self): set_missing_values(self) + def before_insert(self): + validate_nhif_patient_claim_status( + "LRPMT Return", self.company, self.appointment + ) + def before_submit(self): + if not self.approved_by: + self.approved_by = get_fullname() + + validate_nhif_patient_claim_status( + "LRPMT Return", self.company, self.appointment + ) + validate_reason(self) validate_drug_row(self) cancel_lrpt_doc(self) @@ -54,7 +67,7 @@ def cancel_lrpt_doc(self): doc.status = "Not Serviced" doc.save(ignore_permissions=True) doc.reload() - + if ( doc.workflow_state == "Not Serviced" or doc.workflow_state == "Submitted but Not Serviced" @@ -75,6 +88,8 @@ def cancel_lrpt_doc(self): frappe.bold(item.reference_docname), ) ) + + def cancel_tharapy_plan_doc(patient, therapy_plan_id): therapy_sessions = frappe.get_all( "Therapy Session", @@ -91,7 +106,7 @@ def cancel_tharapy_plan_doc(patient, therapy_plan_id): therapy_session_doc.cancel() therapy_plan_doc = frappe.get_doc("Therapy Plan", therapy_plan_id) - + for entry in therapy_plan_doc.therapy_plan_details: if entry.no_of_sessions: entry.no_of_sessions = 0 @@ -103,7 +118,8 @@ def cancel_tharapy_plan_doc(patient, therapy_plan_id): therapy_plan_doc.save(ignore_permissions=True) therapy_plan_doc.reload() return therapy_plan_doc.name - + + def return_or_cancel_drug_item(self): if len(self.drug_items) == 0: return @@ -128,15 +144,11 @@ def return_or_cancel_drug_item(self): except Exception: frappe.log_error( frappe.get_traceback(), - str( - "Error in creating return delivery note for {0}".format( - dn.delivery_note_no - ) - ), + str("Error in creating return delivery note for {0}".format(dn)), ) frappe.throw( "Error in creating return delivery note against delivery note: {0}".format( - frappe.bold(dn.delivery_note_no) + frappe.bold(dn) ) ) @@ -157,7 +169,7 @@ def update_drug_description_for_draft_delivery_note(self, delivey_note): if dn_doc.workflow_state != "Not Serviced": apply_workflow(dn_doc, "Not Serviced") - + if dn_doc.workflow_state == "Not Serviced": for item in self.drug_items: if item.delivery_note_no == delivey_note and item.status == "Draft": @@ -195,7 +207,7 @@ def update_drug_prescription_for_submitted_delivery_note(item): item_cancelled = 1 else: item_cancelled = 0 - + frappe.db.set_value( "Drug Prescription", item.child_name, @@ -206,6 +218,7 @@ def update_drug_prescription_for_submitted_delivery_note(item): }, ) + def return_drug_quantity_to_stock(self, source_doc): target_doc = frappe.new_doc("Delivery Note") target_doc.customer = source_doc.customer @@ -388,17 +401,19 @@ def validate_drug_row(self): if msg: frappe.throw(title="Notification", msg=msg, exc="Frappe.ValidationError") - + + def get_unique_delivery_notes(self, status): return unique([d.delivery_note_no for d in self.drug_items if d.status == status]) + @frappe.whitelist() def get_lrpt_item_list(patient, appointment, company): item_list = [] child_list = get_lrpt_map() encounter_list = get_patient_encounters(patient, appointment, company) - + for child in child_list: items = frappe.get_all( child["doctype"], @@ -549,7 +564,7 @@ def get_refdoc(doctype, childname, template, encounter): "field": "procedure_template", }, ] - + for refd in ref_docs: if refd.get("table") == doctype: docname = frappe.get_value( @@ -566,6 +581,9 @@ def get_refdoc(doctype, childname, template, encounter): def set_missing_values(doc): + if not doc.requested_by: + doc.requested_by = get_fullname() + appointment_list = frappe.get_all( "Patient Appointment", filters={"patient": doc.patient, "status": "Closed"}, @@ -806,7 +824,7 @@ def get_drugs(patient, appointment, company): "dn_detail", ], ) - + for drug in drugs: drug.update( { diff --git a/hms_tz/nhif/api/delivery_note.js b/hms_tz/nhif/api/delivery_note.js index e51b65b2..13f70e3f 100644 --- a/hms_tz/nhif/api/delivery_note.js +++ b/hms_tz/nhif/api/delivery_note.js @@ -39,6 +39,7 @@ frappe.ui.form.on("Delivery Note", { method: "From Front End" }, freeze: true, + freeze_message: __(''), callback: (r) => { if (r.message == true) { frm.refresh() @@ -60,6 +61,7 @@ frappe.ui.form.on("Delivery Note", { name: frm.doc.name }, freeze: true, + freeze_message: __(''), callback: (r) => { }, }); diff --git a/hms_tz/nhif/api/healthcare_utils.py b/hms_tz/nhif/api/healthcare_utils.py index 0c1d2a00..f2e04ba5 100644 --- a/hms_tz/nhif/api/healthcare_utils.py +++ b/hms_tz/nhif/api/healthcare_utils.py @@ -135,16 +135,16 @@ def get_healthcare_service_order_to_invoice( qty = 1 if value.get("doctype") == "Drug Prescription": - qty = ((row.get("quantity") or 0) - ( + qty = (row.get("quantity") or 0) - ( row.get("quantity_returned") or 0 - )) + ) services_to_invoice.append( { "reference_type": row.doctype, "reference_name": row.name, "service": item_code, - "qty": qty, + "qty": qty, } ) @@ -1421,7 +1421,7 @@ def finalize_encounter(encounter_list): "finalized": 1, }, ) - + except Exception as e: frappe.log_error( frappe.get_traceback(), @@ -1453,3 +1453,54 @@ def finalize_encounter(encounter_list): ) if len(encounters) > 0: finalize_encounter(encounters) + + +def validate_nhif_patient_claim_status( + doctype_name, company, appointment, insurance_company=None, caller=None +): + """Stop Change/Cancel/Return of LRPMT Items After Claim Submission + + This is to ensure same LRPMT items on patient encounter and on NHIF patient claim, + if the claim is submitted, then the user should not be able to change/cancel/return LRPMT items + because items on Patient Encounter and items on NHIF patient claim will not match + """ + if ( + frappe.get_cached_value( + "Company", + company, + "stop_change_of_lrpmt_items_after_claim_submission", + ) + == 0 + ): + return + + claim_no = None + if not insurance_company and appointment: + insurance_company, claim_no = frappe.get_value( + "Patient Appointment", + appointment, + ["insurance_company", "nhif_patient_claim"], + ) + if insurance_company and "NHIF" in insurance_company: + if not claim_no: + claim_no = frappe.get_value( + "Patient Appointment", appointment, "nhif_patient_claim" + ) + + claim_status = frappe.get_value("NHIF Patient Claim", claim_no, "docstatus") + if claim_status == 1: + claim_url = get_url_to_form("NHIF Patient Claim", claim_no) + app_url = get_url_to_form("Patient Appointment", appointment) + msg = f"""
+ NHIF Patient Claim: {claim_no} for this Appointment: {appointment} is already submitted.

+ Please stop Duplicating/Editing/Submitting this {doctype_name} to avoid making changes on items whose Claim is already submitted +
""" + + if caller: + frappe.msgprint(msg) + else: + frappe.throw( + msg, + title="NHIF Patient Claim Already Submitted", + exc=frappe.ValidationError, + ) diff --git a/hms_tz/nhif/api/inpatient_record.js b/hms_tz/nhif/api/inpatient_record.js index 4bc48355..1b44144d 100644 --- a/hms_tz/nhif/api/inpatient_record.js +++ b/hms_tz/nhif/api/inpatient_record.js @@ -13,6 +13,9 @@ frappe.ui.form.on('Inpatient Record', { $('*[data-fieldname="inpatient_consultancy"]').find('.grid-remove-rows').hide(); $('*[data-fieldname="inpatient_consultancy"]').find('.grid-remove-all-rows').hide(); + frm.get_field("inpatient_occupancies").grid.cannot_add_rows = true; + frm.get_field("inpatient_consultancy").grid.cannot_add_rows = true; + if (!frm.doc.insurance_subscription) { frm.add_custom_button(__("Create Invoice"), () => { create_sales_invoice(frm); @@ -22,6 +25,10 @@ frappe.ui.form.on('Inpatient Record', { }).addClass("font-weight-bold"); } }, + onload(frm) { + frm.get_field("inpatient_occupancies").grid.cannot_add_rows = true; + frm.get_field("inpatient_consultancy").grid.cannot_add_rows = true; + } }); frappe.ui.form.on('Inpatient Occupancy', { @@ -36,23 +43,19 @@ frappe.ui.form.on('Inpatient Occupancy', { inpatient_occupancies_move: (frm, cdt, cdn) => { control_inpatient_record_move(frm, cdt, cdn); }, - confirmed: (frm, cdt, cdn) => { - let row = frappe.get_doc(cdt, cdn); - if (row.is_confirmed || !row.left) return; - if (frm.is_dirty()) { - frm.save(); - } + is_confirmed: (frm, cdt, cdn) => { frappe.call({ method: 'hms_tz.nhif.api.inpatient_record.confirmed', args: { - row: row, - doc: frm.doc + company: frm.doc.company, + appointment: frm.doc.patient_appointment, + insurance_company: frm.doc.insurance_company, }, - callback: function (data) { - if (data.message) { - + callback: function (r) { + if (r.message) { + frm.refresh_field("inpatient_consultancies"); + frm.reload_doc(); } - frm.reload_doc(); } }); }, @@ -75,15 +78,21 @@ frappe.ui.form.on('Inpatient Consultancy', { frm.fields_dict.inpatient_consultancy.grid.wrapper.find('.grid-insert-row').hide(); }, - confirmed: (frm, cdt, cdn) => { - let row = frappe.get_doc(cdt, cdn); - if (row.is_confirmed) return; - if (frm.is_dirty()) { - frm.save(); - } - frappe.model.set_value(cdt, cdn, "is_confirmed", 1); - frm.refresh_field("inpatient_consultancy"); - frm.save(); + is_confirmed: (frm, cdt, cdn) => { + frappe.call({ + method: 'hms_tz.nhif.api.inpatient_record.confirmed', + args: { + company: frm.doc.company, + appointment: frm.doc.patient_appointment, + insurance_company: frm.doc.insurance_company, + }, + callback: function (r) { + if (r.message) { + frm.refresh_field("inpatient_consultancy"); + frm.reload_doc(); + } + } + }); }, }); diff --git a/hms_tz/nhif/api/inpatient_record.py b/hms_tz/nhif/api/inpatient_record.py index 6c66c79d..027bd9af 100644 --- a/hms_tz/nhif/api/inpatient_record.py +++ b/hms_tz/nhif/api/inpatient_record.py @@ -6,15 +6,17 @@ import frappe from frappe import _ from frappe.utils import nowdate, nowtime, get_url_to_form -from hms_tz.nhif.api.healthcare_utils import get_item_rate, get_item_price from hms_tz.nhif.api.patient_appointment import get_mop_amount from hms_tz.nhif.api.patient_encounter import create_healthcare_docs_from_name from hms_tz.nhif.api.patient_appointment import get_discount_percent from erpnext.accounts.party import get_party_account from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account from hms_tz.nhif.api.healthcare_utils import ( + get_item_rate, + get_item_price, get_healthcare_service_order_to_invoice, get_warehouse_from_service_unit, + validate_nhif_patient_claim_status, ) import json @@ -77,58 +79,11 @@ def daily_update_inpatient_occupancies(): @frappe.whitelist() -def confirmed(row, doc): - row = frappe._dict(json.loads(row)) - doc = frappe._dict(json.loads(doc)) - if row.invoiced or not row.left: - return - encounter = frappe.get_doc("Patient Encounter", doc.admission_encounter) - service_unit_type, warehouse = frappe.get_cached_value( - "Healthcare Service Unit", row.service_unit, ["service_unit_type", "warehouse"] - ) - item_code = frappe.get_cached_value( - "Healthcare Service Unit Type", service_unit_type, "item_code" - ) - item_rate = 0 - if encounter.insurance_subscription: - item_rate = get_item_rate( - item_code, - encounter.company, - encounter.insurance_subscription, - encounter.insurance_company, - ) - if not item_rate: - frappe.throw( - _( - "There is no price in Insurance Subscription {0} for item {1}" - ).format(encounter.insurance_subscription, item_code) - ) - elif encounter.mode_of_payment: - price_list = frappe.get_cached_value( - "Mode of Payment", encounter.mode_of_payment, "price_list" - ) - if not price_list: - frappe.throw( - _("There is no in mode of payment {0}").format( - encounter.mode_of_payment - ) - ) - if price_list: - item_rate = get_item_price(item_code, price_list, encounter.company) - if not item_rate: - frappe.throw( - _("There is no price in price list {0} for item {1}").format( - price_list, item_code - ) - ) - - if item_rate: - delivery_note = create_delivery_note( - encounter, item_code, item_rate, warehouse, row, doc.primary_practitioner - ) - frappe.set_value(row.doctype, row.name, "is_confirmed", 1) - return delivery_note - +def confirmed(company, appointment, insurance_company): + if insurance_company and "NHIF" in insurance_company: + validate_nhif_patient_claim_status("Inpatient Record", company, appointment, insurance_company, "inpatient_record") + return True + def create_delivery_note(encounter, item_code, item_rate, warehouse, row, practitioner): insurance_subscription = encounter.insurance_subscription diff --git a/hms_tz/nhif/api/patient_encounter.py b/hms_tz/nhif/api/patient_encounter.py index 73074192..47adbaa2 100644 --- a/hms_tz/nhif/api/patient_encounter.py +++ b/hms_tz/nhif/api/patient_encounter.py @@ -23,6 +23,7 @@ create_individual_procedure_prescription, msgThrow, msgPrint, + validate_nhif_patient_claim_status, ) from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import ( get_receivable_account, @@ -56,6 +57,11 @@ def before_insert(doc, method): doc.encounter_date = nowdate() doc.encounter_time = nowtime() + if doc.insurance_company and "NHIF" in doc.insurance_company: + validate_nhif_patient_claim_status( + "Patient Encounter", doc.company, doc.appointment, doc.insurance_company + ) + # regency rock: 95 def after_insert(doc, method): @@ -72,8 +78,8 @@ def after_insert(doc, method): ) if ( - pharmacy_details and - pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 + pharmacy_details + and pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 ): if doc.inpatient_record: if not pharmacy_details.ipd_cash_pharmacy: @@ -99,14 +105,14 @@ def after_insert(doc, method): [ "auto_set_pharmacy_on_patient_encounter", "opd_insurance_pharmacy", - "ipd_insurance_pharmacy" + "ipd_insurance_pharmacy", ], as_dict=1, ) if ( - pharmacy_details and - pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 + pharmacy_details + and pharmacy_details.auto_set_pharmacy_on_patient_encounter == 1 ): if doc.inpatient_record: if not pharmacy_details.ipd_insurance_pharmacy: @@ -396,12 +402,13 @@ def on_submit_validation(doc, method): if template not in hsic_map: for row_item in healthcare_service_templates[template]: if ( - doc.company and - frappe.get_cached_value( + doc.company + and frappe.get_cached_value( "Company", doc.company, "auto_prescribe_items_on_patient_encounter", - ) == 1 + ) + == 1 ): row_item.prescribe = 1 @@ -417,12 +424,13 @@ def on_submit_validation(doc, method): if template in hsic_map: for row_item in healthcare_service_templates[template]: if ( - doc.company and - frappe.get_cached_value( + doc.company + and frappe.get_cached_value( "Company", doc.company, "auto_prescribe_items_on_patient_encounter", - ) == 1 + ) + == 1 ): row_item.prescribe = 1 @@ -474,6 +482,10 @@ def duplicate_encounter(encounter): "Cannot duplicate an encounter of healthcare package order, Please let the patient to create appointment again" ) ) + if doc.insurance_company and "NHIF" in doc.insurance_company: + validate_nhif_patient_claim_status( + "Patient Encounter", doc.company, doc.appointment, doc.insurance_company + ) if not doc.docstatus == 1 or doc.encounter_type == "Final" or doc.duplicated == 1: frappe.msgprint( @@ -901,7 +913,8 @@ def create_delivery_note_per_encounter(patient_encounter_doc, method): "dosage_form: " + str(row.get("dosage_form") or ""), "interval: " + str(row.get("interval") or ""), "interval_uom: " + str(row.get("interval_uom") or ""), - "medical_code: " + str(row.get("medical_code") or "No medical code"), + "medical_code: " + + str(row.get("medical_code") or "No medical code"), "Doctor's comment: " + (row.get("comment") or "Take medication as per dosage."), ] @@ -1412,6 +1425,11 @@ def enqueue_on_update_after_submit(doc_name): def before_submit(doc, method): + if doc.insurance_company and "NHIF" in doc.insurance_company: + validate_nhif_patient_claim_status( + "Patient Encounter", doc.company, doc.appointment, doc.insurance_company + ) + if not doc.healthcare_package_order: set_amounts(doc) diff --git a/hms_tz/nhif/doctype/medication_change_request/medication_change_request.py b/hms_tz/nhif/doctype/medication_change_request/medication_change_request.py index 79c00142..51801801 100644 --- a/hms_tz/nhif/doctype/medication_change_request/medication_change_request.py +++ b/hms_tz/nhif/doctype/medication_change_request/medication_change_request.py @@ -11,14 +11,15 @@ get_warehouse_from_service_unit, get_template_company_option, msgThrow, + validate_nhif_patient_claim_status, ) -from hms_tz.hms_tz.doctype.patient_encounter.patient_encounter import get_quantity from hms_tz.nhif.api.patient_encounter import validate_stock_item from hms_tz.nhif.api.patient_appointment import get_mop_amount, get_discount_percent from frappe.model.workflow import apply_workflow from frappe.utils import get_url_to_form from hms_tz.nhif.api.patient_encounter import get_drug_quantity + class MedicationChangeRequest(Document): def validate(self): self.title = "{0}/{1}".format(self.patient_encounter, self.delivery_note) @@ -26,19 +27,25 @@ def validate(self): items = [] if self.drug_prescription: for drug in self.drug_prescription: - validate_healthcare_service_unit(self.warehouse, drug, method="validate") - + validate_healthcare_service_unit( + self.warehouse, drug, method="validate" + ) + if drug.drug_code not in items: items.append(drug.drug_code) else: - frappe.throw(_("Drug '{0}' is duplicated in line '{1}' in Drug Prescription").format( - frappe.bold(drug.drug_code), frappe.bold(drug.idx) - )) + frappe.throw( + _( + "Drug '{0}' is duplicated in line '{1}' in Drug Prescription" + ).format(frappe.bold(drug.drug_code), frappe.bold(drug.idx)) + ) if not drug.quantity: - frappe.throw("Please keep quantity for item: {0}, Row#: {1}".format( - frappe.bold(drug.drug_code), frappe.bold(drug.idx) - )) + frappe.throw( + "Please keep quantity for item: {0}, Row#: {1}".format( + frappe.bold(drug.drug_code), frappe.bold(drug.idx) + ) + ) drug.delivered_quantity = drug.quantity - (drug.quantity_returned or 0) template_doc = get_template_company_option(drug.drug_code, self.company) @@ -49,78 +56,107 @@ def validate(self): + drug.drug_code + ", is not available inhouse".format( frappe.bold(drug.drug_code) - )) - + ) + ) + # auto calculating quantity if not drug.quantity: drug.quantity = get_drug_quantity(drug) - - validate_stock_item(drug.drug_code, drug.quantity, self.company, drug.doctype, drug.healthcare_service_unit, caller="unknown", method="validate") - + + validate_stock_item( + drug.drug_code, + drug.quantity, + self.company, + drug.doctype, + drug.healthcare_service_unit, + caller="unknown", + method="validate", + ) + self.validate_item_insurance_coverage(drug, "validate") @frappe.whitelist() def get_warehouse_per_delivery_note(self): return frappe.get_value("Delivery Note", self.delivery_note, "set_warehouse") - def validate_item_insurance_coverage(self, row, method): """Validate if the Item is covered with the insurance coverage plan of a patient""" if row.prescribe: return - + insurance_subscription, insurance_company, mop = get_insurance_details(self) if mop: return - + insurance_coverage_plan = frappe.get_cached_value( "Healthcare Insurance Subscription", {"name": insurance_subscription}, - "healthcare_insurance_coverage_plan" + "healthcare_insurance_coverage_plan", ) if not insurance_coverage_plan: frappe.throw(_("Healthcare Insurance Coverage Plan is Not defiend")) - + coverage_plan_name, is_exclusions = frappe.get_cached_value( "Healthcare Insurance Coverage Plan", insurance_coverage_plan, ["coverage_plan_name", "is_exclusions"], ) - + today = frappe.utils.nowdate() - service_coverage = frappe.get_all("Healthcare Service Insurance Coverage", - filters={"is_active": 1, "start_date": ["<=", today],"end_date": [">=", today], - "healthcare_service_template": row.drug_code, + service_coverage = frappe.get_all( + "Healthcare Service Insurance Coverage", + filters={ + "is_active": 1, + "start_date": ["<=", today], + "end_date": [">=", today], + "healthcare_service_template": row.drug_code, "healthcare_insurance_coverage_plan": insurance_coverage_plan, - }, fields=["name", "approval_mandatory_for_claim", "healthcare_service_template"], + }, + fields=[ + "name", + "approval_mandatory_for_claim", + "healthcare_service_template", + ], ) if service_coverage: row.is_restricted = service_coverage[0].approval_mandatory_for_claim if is_exclusions: - msgThrow(_( + msgThrow( + _( "{0} not covered in Healthcare Insurance Coverage Plan " + str(frappe.bold(coverage_plan_name)) ).format(frappe.bold(row.drug_code)), - method + method, ) - + else: if not is_exclusions: - msgThrow(_( + msgThrow( + _( "{0} not covered in Healthcare Insurance Coverage Plan " + str(frappe.bold(coverage_plan_name)) ).format(frappe.bold(row.drug_code)), - method + method, ) - def before_insert(self): + validate_nhif_patient_claim_status( + "Medication Change Request", self.company, self.appointment + ) + if self.patient_encounter: encounter_doc = get_patient_encounter_doc(self.patient_encounter) - if not encounter_doc.insurance_coverage_plan and not encounter_doc.inpatient_record: - frappe.throw(frappe.bold("Cannot create medication change request for Cash Patient OPD")) - + if ( + not encounter_doc.insurance_coverage_plan + and not encounter_doc.inpatient_record + ): + frappe.throw( + frappe.bold( + "Cannot create medication change request for Cash Patient OPD" + ) + ) + self.warehouse = self.get_warehouse_per_delivery_note() for row in encounter_doc.drug_prescription: @@ -129,9 +165,9 @@ def before_insert(self): ): new_row = row.as_dict() new_row["name"] = None - self.append('original_pharmacy_prescription', new_row) - self.append('drug_prescription', new_row) - + self.append("original_pharmacy_prescription", new_row) + self.append("drug_prescription", new_row) + if not self.patient_encounter_final_diagnosis: for d in encounter_doc.patient_encounter_final_diagnosis: if not isinstance(d, dict): @@ -140,35 +176,47 @@ def before_insert(self): d["name"] = None self.append("patient_encounter_final_diagnosis", d) - + dn_doc = frappe.get_doc("Delivery Note", self.delivery_note) - + if dn_doc.form_sales_invoice: url = get_url_to_form("sales Ivoice", dn_doc.form_sales_invoice) - frappe.throw("Cannot create medicaton change request for items paid in cash
\ + frappe.throw( + "Cannot create medicaton change request for items paid in cash
\ refer sales invoice: {1}".format( url, frappe.bold(dn_doc.form_sales_invoice) - )) - + ) + ) + try: if dn_doc.workflow_state != "Changes Requested": apply_workflow(dn_doc, "Request Changes") dn_doc.reload() - + except Exception: frappe.log_error(frappe.get_traceback(), str(self.doctype)) - frappe.msgprint("Apply workflow error for delivery note: {0}".format(frappe.bold(dn_doc.name))) + frappe.msgprint( + "Apply workflow error for delivery note: {0}".format( + frappe.bold(dn_doc.name) + ) + ) frappe.throw("Medication Change Request was not created, try again") - def before_submit(self): + validate_nhif_patient_claim_status( + "Medication Change Request", self.company, self.appointment + ) + self.warehouse = self.get_warehouse_per_delivery_note() for item in self.drug_prescription: validate_healthcare_service_unit(self.warehouse, item, method="throw") self.validate_item_insurance_coverage(item, "throw") set_amount(self, item) - + def on_submit(self): + validate_nhif_patient_claim_status( + "Medication Change Request", self.company, self.appointment + ) encounter_doc = self.update_encounter() self.update_delivery_note(encounter_doc) @@ -224,14 +272,16 @@ def update_delivery_note(self, encounter_doc): warehouse = get_warehouse_from_service_unit(row.healthcare_service_unit) if warehouse != doc.set_warehouse: continue - + if row.prescribe and not encounter_doc.inpatient_record: continue - + if row.is_not_available_inhouse or row.is_cancelled: continue - item_code, uom = frappe.get_cached_value("Medication", row.drug_code, ["item", "stock_uom"]) + item_code, uom = frappe.get_cached_value( + "Medication", row.drug_code, ["item", "stock_uom"] + ) is_stock, item_name = frappe.get_cached_value( "Item", item_code, ["is_stock_item", "item_name"] ) @@ -257,7 +307,8 @@ def update_delivery_note(self, encounter_doc): "dosage_form: " + str(row.get("dosage_form") or ""), "interval: " + str(row.get("interval") or ""), "interval_uom: " + str(row.get("interval_uom") or ""), - "medical_code: " + str(row.get("medical_code") or "No medical code"), + "medical_code: " + + str(row.get("medical_code") or "No medical code"), "Doctor's comment: " + (row.get("comment") or "Take medication as per dosage."), ] @@ -279,12 +330,24 @@ def update_delivery_note(self, encounter_doc): if doc.workflow_state == "Changes Made": frappe.msgprint( - _("Delivery Note " + self.delivery_note + " has been updated!"), alert=True + _("Delivery Note " + self.delivery_note + " has been updated!"), + alert=True, ) except Exception: - frappe.log_error(frappe.get_traceback(), str("Apply workflow error for delivery note: {0}".format(frappe.bold(doc.name)))) - frappe.throw("Apply workflow error for delivery note: {0}".format(frappe.bold(doc.name))) + frappe.log_error( + frappe.get_traceback(), + str( + "Apply workflow error for delivery note: {0}".format( + frappe.bold(doc.name) + ) + ), + ) + frappe.throw( + "Apply workflow error for delivery note: {0}".format( + frappe.bold(doc.name) + ) + ) @frappe.whitelist() @@ -329,11 +392,13 @@ def get_patient_encounter_doc(patient_encounter): def get_insurance_details(self): insurance_subscription, insurance_company, mop = frappe.get_value( - "Patient Appointment", self.appointment, + "Patient Appointment", + self.appointment, ["insurance_subscription", "insurance_company", "mode_of_payment"], ) return insurance_subscription, insurance_company, mop + def set_amount(self, row): item_code = frappe.get_cached_value("Medication", row.drug_code, "item") inpatient_record = frappe.get_value("Patient", self.patient, "inpatient_record") @@ -348,7 +413,7 @@ def set_amount(self, row): amount = get_item_rate( item_code, self.company, insurance_subscription, insurance_company ) - row.amount = amount - (amount * (discount_percent/100)) + row.amount = amount - (amount * (discount_percent / 100)) if discount_percent > 0: row.hms_tz_is_discount_applied = 1 row.hms_tz_is_discount_percent = discount_percent @@ -356,7 +421,7 @@ def set_amount(self, row): elif mop and inpatient_record: if not row.prescribe: row.prescribe = 1 - row.amount = get_mop_amount(item_code, mop, self.company,self.patient) + row.amount = get_mop_amount(item_code, mop, self.company, self.patient) @frappe.whitelist() @@ -412,31 +477,37 @@ def get_fields_to_clear(): def set_original_items(name, item): new_row = item.as_dict() for fieldname in get_fields_to_clear(): - new_row[fieldname] = None - - new_row.update({ - "parent": name, - "parentfield": "hms_tz_original_items", - "parenttype": "Delivery Note", - "doctype": "Original Delivery Note Item" - }) - + new_row[fieldname] = None + + new_row.update( + { + "parent": name, + "parentfield": "hms_tz_original_items", + "parenttype": "Delivery Note", + "doctype": "Original Delivery Note Item", + } + ) + return new_row @frappe.whitelist() def create_medication_change_request_from_dn(doctype, name): source_doc = frappe.get_doc(doctype, name) - + if source_doc.form_sales_invoice: url = get_url_to_form("sales Ivoice", source_doc.form_sales_invoice) - frappe.throw("Cannot create medicaton change request for items paid in cash,
\ + frappe.throw( + "Cannot create medicaton change request for items paid in cash,
\ please refer sales invoice: {1}".format( url, frappe.bold(source_doc.form_sales_invoice) - )) - + ) + ) + if not source_doc.hms_tz_comment: - frappe.throw("No comment found on the delivery note, Please keep a comment and save the delivery note, before creating med change request") + frappe.throw( + "No comment found on the delivery note, Please keep a comment and save the delivery note, before creating med change request" + ) doc = frappe.new_doc("Medication Change Request") doc.patient = source_doc.patient @@ -448,10 +519,15 @@ def create_medication_change_request_from_dn(doctype, name): doc.healthcare_practitioner = source_doc.healthcare_practitioner doc.hms_tz_comment = source_doc.hms_tz_comment + validate_nhif_patient_claim_status( + "Medication Change Request", doc.company, doc.appointment + ) + doc.save(ignore_permissions=True) url = get_url_to_form(doc.doctype, doc.name) - frappe.msgprint("Draft Medication Change Request: {1} is created".format( - url, frappe.bold(doc.name) - )) + frappe.msgprint( + "Draft Medication Change Request: {1} is created".format( + url, frappe.bold(doc.name) + ) + ) return doc.name - diff --git a/hms_tz/patches.txt b/hms_tz/patches.txt index a76fcb76..2eb53dd8 100644 --- a/hms_tz/patches.txt +++ b/hms_tz/patches.txt @@ -51,3 +51,4 @@ hms_tz.patches.custom_fields.auto_finalize_patient_encounter hms_tz.patches.custom_fields.auto_prescribe_items_on_patient_encounter hms_tz.patches.property_setter.additional_property_setters_for_hms_tz hms_tz.patches.custom_fields.additional_custom_fields_for_hms_tz +hms_tz.patches.custom_fields.stop_change_of_lrpmt_items_after_claim_submission diff --git a/hms_tz/patches/custom_fields/stop_change_of_lrpmt_items_after_claim_submission.py b/hms_tz/patches/custom_fields/stop_change_of_lrpmt_items_after_claim_submission.py new file mode 100644 index 00000000..6c300086 --- /dev/null +++ b/hms_tz/patches/custom_fields/stop_change_of_lrpmt_items_after_claim_submission.py @@ -0,0 +1,18 @@ +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + fields = { + "Company": [ + { + "fieldname": "stop_change_of_lrpmt_items_after_claim_submission", + "label": "Stop Change/Cancel/Return of LRPMT Items After Claim Submission", + "fieldtype": "Check", + "insert_after": "hms_tz_settings_sb", + "default": 1, + "description": "If checked, user will not be able to Change/Cancel/Return LRPMT items after NHIF Claim submission.", + } + ] + } + + create_custom_fields(fields, update=True) From cad2ae5459222bcb3b10a8ff2844bc0f96a9fe6a Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Tue, 28 Nov 2023 16:51:29 +0300 Subject: [PATCH 07/12] chore: unset a property of 'set only once' for a field of 'appointment' on clinical procedure --- hms_tz/nhif/api/delivery_note.py | 28 +++++++++---------- hms_tz/patches.txt | 1 + .../set_only_once_appointment_property.py | 15 ++++++++++ 3 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 hms_tz/patches/property_setter/set_only_once_appointment_property.py diff --git a/hms_tz/nhif/api/delivery_note.py b/hms_tz/nhif/api/delivery_note.py index b8958b09..346477af 100644 --- a/hms_tz/nhif/api/delivery_note.py +++ b/hms_tz/nhif/api/delivery_note.py @@ -61,26 +61,26 @@ def update_dosage_details(item): return drug_doc = frappe.get_doc("Drug Prescription", reference_dn) - + description = ",
".join( - [ - "frequency: " + str(drug_doc.get("dosage") or "No Prescription Dosage"), - "period: " + str(drug_doc.get("period") or "No Prescription Period"), - "dosage_form: " + str(drug_doc.get("dosage_form") or ""), - "interval: " + str(drug_doc.get("interval") or ""), - "interval_uom: " + str(drug_doc.get("interval_uom") or ""), - "medical_code: " + str(drug_doc.get("medical_code") or "No medical code"), - "Doctor's comment: " - + (drug_doc.get("comment") or "Take medication as per dosage."), - ] - ) + [ + "frequency: " + str(drug_doc.get("dosage") or "No Prescription Dosage"), + "period: " + str(drug_doc.get("period") or "No Prescription Period"), + "dosage_form: " + str(drug_doc.get("dosage_form") or ""), + "interval: " + str(drug_doc.get("interval") or ""), + "interval_uom: " + str(drug_doc.get("interval_uom") or ""), + "medical_code: " + + str(drug_doc.get("medical_code") or "No medical code"), + "Doctor's comment: " + + (drug_doc.get("comment") or "Take medication as per dosage."), + ] + ) item.description = description item.reference_doctype = drug_doc.doctype item.reference_name = drug_doc.name - def onload(doc, method): for item in doc.items: if item.last_qty_prescribed: @@ -305,7 +305,7 @@ def update_drug_prescription(doc): item.reference_dn, { "is_not_available_inhouse": 1, - "hms_tz_is_out_of_stcok": 1, + "hms_tz_is_out_of_stock": 1, "is_cancelled": 1, }, ) diff --git a/hms_tz/patches.txt b/hms_tz/patches.txt index a76fcb76..2c0c6a85 100644 --- a/hms_tz/patches.txt +++ b/hms_tz/patches.txt @@ -51,3 +51,4 @@ hms_tz.patches.custom_fields.auto_finalize_patient_encounter hms_tz.patches.custom_fields.auto_prescribe_items_on_patient_encounter hms_tz.patches.property_setter.additional_property_setters_for_hms_tz hms_tz.patches.custom_fields.additional_custom_fields_for_hms_tz +hms_tz.patches.property_setter.set_only_once_appointment_property diff --git a/hms_tz/patches/property_setter/set_only_once_appointment_property.py b/hms_tz/patches/property_setter/set_only_once_appointment_property.py new file mode 100644 index 00000000..f3d40cba --- /dev/null +++ b/hms_tz/patches/property_setter/set_only_once_appointment_property.py @@ -0,0 +1,15 @@ +import frappe +from frappe.custom.doctype.property_setter.property_setter import make_property_setter + + +def execute(): + make_property_setter( + "Clinical Procedure", + "appointment", + "set_only_once", + 0, + "Check", + for_doctype=False, + validate_fields_for_doctype=False, + ) + frappe.db.commit() From 9ab7a06161a3e2a7d66811671359a7f68451f698 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Fri, 1 Dec 2023 06:27:35 +0300 Subject: [PATCH 08/12] feat: show transactions of ongoing ipds and previous transactions before the current month --- .../itemwise_hospital_revenue.js | 19 + .../itemwise_hospital_revenue.py | 1200 +++++++++-------- 2 files changed, 646 insertions(+), 573 deletions(-) diff --git a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js index dc640fad..6d393a7e 100644 --- a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js +++ b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.js @@ -104,5 +104,24 @@ frappe.query_reports["Itemwise Hospital Revenue"] = { "options": "\nLab Test\nRadiology Examination\nClinical Procedure\nDelivery Note", "hidden": 1, }, + { + "fieldname": "show_only_ongoing_ipds", + "label": __("Show Ongoing IPDs"), + "fieldtype": "Check", + "description": "Show only items of ongoing admitted patient based on date range", + }, + { + "fieldname": "show_only_prev_items_for_discharged_ipds", + "label": __("Show Previous Items For Discharged IPDs"), + "fieldtype": "Check", + "description": "Show previous items of discharged patient based before start date", + "on_change": function () { + let show_only_prev_items_for_discharged_ipds = frappe.query_report.get_filter_value('show_only_prev_items_for_discharged_ipds'); + let show_only_ongoing_ipds = frappe.query_report.get_filter_value('show_only_ongoing_ipds'); + if (show_only_prev_items_for_discharged_ipds == 1 && show_only_ongoing_ipds == 1) { + frappe.msgprint("Please select only one option either 'Show Ongoing IPDs' or 'Show Previous Items For Discharged IPDs'"); + } + } + } ] }; \ No newline at end of file diff --git a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py index 9fed9b5c..7f10091b 100644 --- a/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py +++ b/hms_tz/nhif/report/itemwise_hospital_revenue/itemwise_hospital_revenue.py @@ -1,14 +1,25 @@ # Copyright (c) 2023, Aakvatech and contributors # For license information, please see license.txt + import frappe -from pypika import Case, functions as fn from frappe.query_builder import DocType -from frappe.query_builder.functions import Sum, Count -from pypika.terms import ValueWrapper +from pypika.functions import Sum, Count, Max, IfNull, Date +from pypika.terms import Case, Criterion, ValueWrapper, Not def execute(filters=None): + if ( + filters.get("show_only_ongoing_ipds") == 1 + and filters.get("show_only_prev_items_for_discharged_ipds") == 1 + ): + frappe.throw( + "Cannot filter by both Ongoing IPDs and Discharged IPDs
\ + Uncheck one of the filters either 'Show Ongoing IPDs or Show prev items for discharged IPDs\ + and try again" + ) + + data = [] columns = get_columns(filters) if filters.get("show_only_cancelled_items") == 1: @@ -26,7 +37,8 @@ def execute(filters=None): else: data = get_insurance_data(filters) - return columns, data + + return columns, data def get_columns(filters): @@ -215,112 +227,123 @@ def get_columns(filters): def get_cash_insurance_data(filters): cash_insurance_data = [] - cash_insurance_data += get_insurance_appointment_data(filters) - cash_insurance_data += get_cash_appointment_data(filters) - cash_insurance_data += get_insurance_lab_data(filters) - cash_insurance_data += get_cash_lab_data(filters) - cash_insurance_data += get_insurance_radiology_data(filters) - cash_insurance_data += get_cash_radiology_data(filters) - cash_insurance_data += get_insurance_procedure_data(filters) - cash_insurance_data += get_cash_procedure_data(filters) - cash_insurance_data += get_insurance_drug_data(filters) - cash_insurance_data += get_cash_drug_data(filters) - cash_insurance_data += get_direct_sales_drug_data(filters) - cash_insurance_data += get_insurance_therapy_data(filters) - cash_insurance_data += get_cash_therapy_data(filters) - cash_insurance_data += get_insurance_ipd_beds_data(filters) - cash_insurance_data += get_cash_ipd_beds_data(filters) - cash_insurance_data += get_insurance_ipd_cons_data(filters) - cash_insurance_data += get_cash_ipd_cons_data(filters) + + appoints = get_prev_and_ongoing_ipds(filters) + + cash_insurance_data += get_insurance_appointment_data(filters, appoints) + cash_insurance_data += get_cash_appointment_data(filters, appoints) + cash_insurance_data += get_insurance_lab_data(filters, appoints) + cash_insurance_data += get_cash_lab_data(filters, appoints) + cash_insurance_data += get_insurance_radiology_data(filters, appoints) + cash_insurance_data += get_cash_radiology_data(filters, appoints) + cash_insurance_data += get_insurance_procedure_data(filters, appoints) + cash_insurance_data += get_cash_procedure_data(filters, appoints) + cash_insurance_data += get_insurance_drug_data(filters, appoints) + cash_insurance_data += get_cash_drug_data(filters, appoints) + cash_insurance_data += get_insurance_therapy_data(filters, appoints) + cash_insurance_data += get_cash_therapy_data(filters, appoints) + cash_insurance_data += get_insurance_ipd_beds_data(filters, appoints) + cash_insurance_data += get_cash_ipd_beds_data(filters, appoints) + cash_insurance_data += get_insurance_ipd_cons_data(filters, appoints) + cash_insurance_data += get_cash_ipd_cons_data(filters, appoints) + + # Direct cash sales from sales order to sales invoice + cash_insurance_data += get_direct_sales_from_invoices(filters) return cash_insurance_data def get_cash_data(filters): cash_data = [] - cash_data += get_cash_appointment_data(filters) - cash_data += get_cash_lab_data(filters) - cash_data += get_cash_radiology_data(filters) - cash_data += get_cash_procedure_data(filters) - cash_data += get_cash_drug_data(filters) - cash_data += get_direct_sales_drug_data(filters) - cash_data += get_cash_therapy_data(filters) - cash_data += get_cash_ipd_beds_data(filters) - cash_data += get_cash_ipd_cons_data(filters) + + appoints = get_prev_and_ongoing_ipds(filters) + + cash_data += get_cash_appointment_data(filters, appoints) + cash_data += get_cash_lab_data(filters, appoints) + cash_data += get_cash_radiology_data(filters, appoints) + cash_data += get_cash_procedure_data(filters, appoints) + cash_data += get_cash_drug_data(filters, appoints) + cash_data += get_cash_therapy_data(filters, appoints) + cash_data += get_cash_ipd_beds_data(filters, appoints) + cash_data += get_cash_ipd_cons_data(filters, appoints) + + # Direct cash sales from sales order to sales invoice + cash_data += get_direct_sales_from_invoices(filters) return cash_data def get_insurance_data(filters): insurance_data = [] - insurance_data += get_insurance_appointment_data(filters) - insurance_data += get_insurance_lab_data(filters) - insurance_data += get_insurance_radiology_data(filters) - insurance_data += get_insurance_procedure_data(filters) - insurance_data += get_insurance_drug_data(filters) - insurance_data += get_insurance_therapy_data(filters) - insurance_data += get_insurance_ipd_beds_data(filters) - insurance_data += get_insurance_ipd_cons_data(filters) + + appoints = get_prev_and_ongoing_ipds(filters) + + insurance_data += get_insurance_appointment_data(filters, appoints) + insurance_data += get_insurance_lab_data(filters, appoints) + insurance_data += get_insurance_radiology_data(filters, appoints) + insurance_data += get_insurance_procedure_data(filters, appoints) + insurance_data += get_insurance_drug_data(filters, appoints) + insurance_data += get_insurance_therapy_data(filters, appoints) + insurance_data += get_insurance_ipd_beds_data(filters, appoints) + insurance_data += get_insurance_ipd_cons_data(filters, appoints) return insurance_data -def get_insurance_appointment_data(filters): - appointment = DocType("Patient Appointment") +def get_insurance_appointment_data(filters, appoints): + pa = DocType("Patient Appointment") item = DocType("Item") insurance_appointment_query = ( - frappe.qb.from_(appointment) + frappe.qb.from_(pa) .inner_join(item) - .on(appointment.billing_item == item.name) + .on(pa.billing_item == item.name) .select( - appointment.appointment_date.as_("date"), - appointment.name.as_("appointment_no"), - appointment.name.as_("bill_no"), - appointment.patient.as_("patient"), - appointment.patient_name.as_("patient_name"), + pa.appointment_date.as_("date"), + pa.name.as_("appointment_no"), + pa.name.as_("bill_no"), + pa.patient.as_("patient"), + pa.patient_name.as_("patient_name"), Case() - .when(appointment.appointment_type.like("Emergency"), "In-Patient") + .when(pa.appointment_type.like("Emergency"), "In-Patient") .else_("Out-Patient") .as_("patient_type"), item.item_group.as_("service_type"), - appointment.billing_item.as_("service_name"), - appointment.coverage_plan_name.as_("payment_method"), - Count("*").as_("qty"), - fn.Max(appointment.paid_amount).as_("rate"), - Sum(appointment.paid_amount).as_("amount"), + pa.billing_item.as_("service_name"), + pa.coverage_plan_name.as_("payment_method"), + ValueWrapper(1).as_("qty"), + pa.paid_amount.as_("rate"), + pa.paid_amount.as_("amount"), Case() - .when(appointment.status == "Closed", "Submitted") + .when(pa.status == "Closed", "Submitted") .else_("Draft") .as_("status"), - appointment.practitioner.as_("practitioner"), - appointment.department.as_("department"), - appointment.service_unit.as_("service_unit"), - appointment.modified.as_("date_modified"), + pa.practitioner.as_("practitioner"), + pa.department.as_("department"), + pa.service_unit.as_("service_unit"), + pa.modified.as_("date_modified"), ) .where( - (appointment.company == filters.company) - & (appointment.appointment_date.between(filters.from_date, filters.to_date)) - & (appointment.status != "Cancelled") - & (appointment.follow_up == 0) - & (appointment.has_no_consultation_charges == 0) - & (appointment.invoiced == 0) + (pa.company == filters.company) + & (pa.status != "Cancelled") + & (pa.follow_up == 0) + & (pa.has_no_consultation_charges == 0) & ( - (appointment.insurance_subscription.isnotnull()) - & (appointment.insurance_subscription != "") + (pa.insurance_subscription.isnotnull()) + & (pa.insurance_subscription != "") ) ) - .groupby( - appointment.appointment_date, - appointment.patient, - appointment.name, - appointment.billing_item, - appointment.coverage_plan_name, - appointment.practitioner, - appointment.status, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_appointment_query = insurance_appointment_query.where( + (pa.appointment_date < filters.from_date) & (pa.name.isin(appoints)) + ) + else: + insurance_appointment_query = insurance_appointment_query.where( + (pa.appointment_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: insurance_appointment_query = insurance_appointment_query.where( item.item_group == filters.service_type @@ -328,7 +351,12 @@ def get_insurance_appointment_data(filters): if filters.payment_mode and filters.payment_mode != "Cash": insurance_appointment_query = insurance_appointment_query.where( - appointment.insurance_company == filters.payment_mode + pa.insurance_company == filters.payment_mode + ) + + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_appointment_query = insurance_appointment_query.where( + pa.name.isin(appoints) ) insurance_appointment_data = insurance_appointment_query.run(as_dict=True) @@ -336,83 +364,81 @@ def get_insurance_appointment_data(filters): return insurance_appointment_data -def get_cash_appointment_data(filters): - appointment = DocType("Patient Appointment") +def get_cash_appointment_data(filters, appoints): + pa = DocType("Patient Appointment") item = DocType("Item") sii = DocType("Sales Invoice Item") cash_appointment_query = ( - frappe.qb.from_(appointment) + frappe.qb.from_(pa) .inner_join(item) - .on(appointment.billing_item == item.name) + .on(pa.billing_item == item.name) .inner_join(sii) - .on(appointment.name == sii.reference_dn) + .on(pa.name == sii.reference_dn) .select( - appointment.appointment_date.as_("date"), - appointment.name.as_("appointment_no"), - appointment.name.as_("bill_no"), - appointment.patient.as_("patient"), - appointment.patient_name.as_("patient_name"), + pa.appointment_date.as_("date"), + pa.name.as_("appointment_no"), + pa.name.as_("bill_no"), + pa.patient.as_("patient"), + pa.patient_name.as_("patient_name"), Case() - .when(appointment.appointment_type.like("Emergency"), "In-Patient") + .when(pa.appointment_type.like("Emergency"), "In-Patient") .else_("Out-Patient") .as_("patient_type"), item.item_group.as_("service_type"), - appointment.billing_item.as_("service_name"), + pa.billing_item.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(appointment.paid_amount).as_("rate"), - Sum(sii.amount - sii.net_amount).as_("discount_amount"), - Sum(sii.net_amount).as_("amount"), + ValueWrapper(1).as_("qty"), + pa.paid_amount.as_("rate"), + (sii.amount - sii.net_amount).as_("discount_amount"), + sii.net_amount.as_("amount"), Case() - .when(appointment.status == "Closed", "Submitted") + .when(pa.status == "Closed", "Submitted") .else_("Draft") .as_("status"), - appointment.practitioner.as_("practitioner"), - appointment.department.as_("department"), - appointment.service_unit.as_("service_unit"), - appointment.modified.as_("date_modified"), + pa.practitioner.as_("practitioner"), + pa.department.as_("department"), + pa.service_unit.as_("service_unit"), + pa.modified.as_("date_modified"), ) .where( - (appointment.company == filters.company) - & (appointment.appointment_date.between(filters.from_date, filters.to_date)) - & (appointment.status != "Cancelled") - & (appointment.follow_up == 0) - & (appointment.has_no_consultation_charges == 0) - & (appointment.invoiced == 1) - & ( - (appointment.ref_sales_invoice.isnotnull()) - & (appointment.ref_sales_invoice != "") - ) + (pa.company == filters.company) + & (pa.status != "Cancelled") + & (pa.follow_up == 0) + & (pa.has_no_consultation_charges == 0) + & ((pa.ref_sales_invoice.isnotnull()) & (pa.ref_sales_invoice != "")) & (sii.docstatus == 1) & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) ) - .groupby( - appointment.appointment_date, - appointment.patient, - appointment.name, - appointment.billing_item, - appointment.ref_sales_invoice, - appointment.practitioner, - appointment.status, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + cash_appointment_query = cash_appointment_query.where( + (pa.appointment_date < filters.from_date) & (pa.name.isin(appoints)) + ) + else: + cash_appointment_query = cash_appointment_query.where( + (pa.appointment_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: cash_appointment_query = cash_appointment_query.where( item.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + cash_appointment_query = cash_appointment_query.where(pa.name.isin(appoints)) + cash_appointment_data = cash_appointment_query.run(as_dict=True) return cash_appointment_data -def get_insurance_lab_data(filters): +def get_insurance_lab_data(filters, appoints): lab = DocType("Lab Test") lab_prescription = DocType("Lab Prescription") template = DocType("Lab Test Template") - encounter = DocType("Patient Encounter") + pe = DocType("Patient Encounter") insurance_lab_query = ( frappe.qb.from_(lab) @@ -420,11 +446,11 @@ def get_insurance_lab_data(filters): .on(lab.hms_tz_ref_childname == lab_prescription.name) .inner_join(template) .on(lab.template == template.name) - .inner_join(encounter) - .on(lab.ref_docname == encounter.name) + .inner_join(pe) + .on(lab.ref_docname == pe.name) .select( lab.result_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), lab.name.as_("bill_no"), lab.patient.as_("patient"), lab.patient_name.as_("patient_name"), @@ -435,9 +461,9 @@ def get_insurance_lab_data(filters): template.lab_test_group.as_("service_type"), lab.template.as_("service_name"), lab.hms_tz_insurance_coverage_plan.as_("payment_method"), - Count("*").as_("qty"), - fn.Max(lab_prescription.amount).as_("rate"), - Sum(lab_prescription.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + lab_prescription.amount.as_("rate"), + lab_prescription.amount.as_("amount"), Case().when(lab.docstatus == 1, "Submitted").else_("Draft").as_("status"), lab.practitioner.as_("practitioner"), lab.department.as_("department"), @@ -445,9 +471,7 @@ def get_insurance_lab_data(filters): lab.modified.as_("date_modified"), ) .where( - (lab.company == filters.company) - & (lab.result_date.between(filters.from_date, filters.to_date)) - & ~lab.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"]) + (pe.company == filters.company) & ( ( lab.hms_tz_insurance_coverage_plan.isnotnull() @@ -462,16 +486,15 @@ def get_insurance_lab_data(filters): & (lab_prescription.is_cancelled == 0) & (lab_prescription.is_not_available_inhouse == 0) ) - .groupby( - lab.result_date, - lab.patient, - lab.name, - lab.template, - lab.hms_tz_insurance_coverage_plan, - lab.practitioner, - lab.docstatus, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_lab_query = insurance_lab_query.where( + (pe.encounter_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + insurance_lab_query = insurance_lab_query.where( + (pe.encounter_date.between(filters.from_date, filters.to_date)) + ) if filters.service_type: insurance_lab_query = insurance_lab_query.where( @@ -483,17 +506,21 @@ def get_insurance_lab_data(filters): lab.insurance_company == filters.payment_mode ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_lab_query = insurance_lab_query.where(pe.appointment.isin(appoints)) + insurance_lab_data = insurance_lab_query.run(as_dict=True) return insurance_lab_data -def get_cash_lab_data(filters): +def get_cash_lab_data(filters, appoints): lab = DocType("Lab Test") lab_prescription = DocType("Lab Prescription") template = DocType("Lab Test Template") sii = DocType("Sales Invoice Item") - encounter = DocType("Patient Encounter") + si = DocType("Sales Invoice") + pe = DocType("Patient Encounter") # Paid Lab Data for OPD Patients, Admitted and Discharged Patients # Link to Sales invoice @@ -505,11 +532,13 @@ def get_cash_lab_data(filters): .on(lab.template == template.name) .inner_join(sii) .on(lab.hms_tz_ref_childname == sii.reference_dn) - .inner_join(encounter) - .on(lab.ref_docname == encounter.name) + .inner_join(si) + .on(sii.parent == si.name) + .inner_join(pe) + .on(lab.ref_docname == pe.name) .select( lab.result_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), lab.name.as_("bill_no"), lab.patient.as_("patient"), lab.patient_name.as_("patient_name"), @@ -520,10 +549,10 @@ def get_cash_lab_data(filters): template.lab_test_group.as_("service_type"), lab.template.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(lab_prescription.amount).as_("rate"), - Sum(sii.amount - sii.net_amount).as_("discount_amount"), - Sum(sii.net_amount).as_("amount"), + ValueWrapper(1).as_("qty"), + lab_prescription.amount.as_("rate"), + (sii.amount - sii.net_amount).as_("discount_amount"), + sii.net_amount.as_("amount"), Case().when(lab.docstatus == 1, "Submitted").else_("Draft").as_("status"), lab.practitioner.as_("practitioner"), lab.department.as_("department"), @@ -532,35 +561,37 @@ def get_cash_lab_data(filters): ) .where( (lab.company == filters.company) - & (lab.result_date.between(filters.from_date, filters.to_date)) - & (~lab.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) & (lab.docstatus != 2) & (lab.ref_doctype == "Patient Encounter") & (lab.ref_docname == lab_prescription.parent) - & (lab.prescribe == 1) & (lab_prescription.prescribe == 1) & (lab_prescription.invoiced == 1) & (lab_prescription.is_cancelled == 0) & (lab_prescription.is_not_available_inhouse == 0) - & (sii.docstatus == 1) - & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) - ) - .groupby( - lab.result_date, - lab.patient, - lab.name, - lab.template, - lab.prescribe, - lab.practitioner, - lab.docstatus, + & (lab_prescription.lab_test_created == 1) + & ((sii.reference_dn.isnotnull()) & (sii.reference_dn != "")) + & Not(si.status.isin(["Credit Note Issued", "Return"])) + & (si.docstatus == 1) ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + paid_cash_lab_query = paid_cash_lab_query.where( + (lab.result_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + paid_cash_lab_query = paid_cash_lab_query.where( + (lab.result_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: paid_cash_lab_query = paid_cash_lab_query.where( template.lab_test_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + paid_cash_lab_query = paid_cash_lab_query.where(pe.appointment.isin(appoints)) + paid_cash_lab_data = paid_cash_lab_query.run(as_dict=True) # Unpaid Lab Data for ongoing Admitted Patients @@ -571,11 +602,11 @@ def get_cash_lab_data(filters): .on(lab.hms_tz_ref_childname == lab_prescription.name) .inner_join(template) .on(lab.template == template.name) - .inner_join(encounter) - .on(lab.ref_docname == encounter.name) + .inner_join(pe) + .on(lab.ref_docname == pe.name) .select( lab.result_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), lab.name.as_("bill_no"), lab.patient.as_("patient"), lab.patient_name.as_("patient_name"), @@ -583,9 +614,9 @@ def get_cash_lab_data(filters): template.lab_test_group.as_("service_type"), lab.template.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(lab_prescription.amount).as_("rate"), - Sum(lab_prescription.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + lab_prescription.amount.as_("rate"), + lab_prescription.amount.as_("amount"), Case().when(lab.docstatus == 1, "Submitted").else_("Draft").as_("status"), lab.practitioner.as_("practitioner"), lab.department.as_("department"), @@ -594,43 +625,46 @@ def get_cash_lab_data(filters): ) .where( (lab.company == filters.company) - & (lab.result_date.between(filters.from_date, filters.to_date)) - & (~lab.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) & (lab.docstatus != 2) & (lab.ref_doctype == "Patient Encounter") & (lab.ref_docname == lab_prescription.parent) - & ((lab.inpatient_record.isnotnull()) & (lab.inpatient_record != "")) - & (lab.prescribe == 1) & (lab_prescription.prescribe == 1) & (lab_prescription.invoiced == 0) & (lab_prescription.is_cancelled == 0) & (lab_prescription.is_not_available_inhouse == 0) - ) - .groupby( - lab.result_date, - lab.patient, - lab.name, - lab.template, - lab.prescribe, - lab.practitioner, - lab.docstatus, + & (lab_prescription.lab_test_created == 1) + & ((lab.inpatient_record.isnotnull()) & (lab.inpatient_record != "")) ) ) + + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + unpaid_cash_lab_query = unpaid_cash_lab_query.where( + (lab.result_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + unpaid_cash_lab_query = unpaid_cash_lab_query.where( + (lab.result_date.between(filters.from_date, filters.to_date)) + ) if filters.service_type: unpaid_cash_lab_query = unpaid_cash_lab_query.where( template.lab_test_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + unpaid_cash_lab_query = unpaid_cash_lab_query.where( + pe.appointment.isin(appoints) + ) + unpaid_cash_lab_data = unpaid_cash_lab_query.run(as_dict=True) return paid_cash_lab_data + unpaid_cash_lab_data -def get_insurance_radiology_data(filters): +def get_insurance_radiology_data(filters, appoints): rad = DocType("Radiology Examination") rad_prescription = DocType("Radiology Procedure Prescription") template = DocType("Radiology Examination Template") - encounter = DocType("Patient Encounter") + pe = DocType("Patient Encounter") insurance_rad_query = ( frappe.qb.from_(rad) @@ -638,11 +672,11 @@ def get_insurance_radiology_data(filters): .on(rad.hms_tz_ref_childname == rad_prescription.name) .inner_join(template) .on(rad.radiology_examination_template == template.name) - .inner_join(encounter) - .on(rad.ref_docname == encounter.name) + .inner_join(pe) + .on(rad.ref_docname == pe.name) .select( rad.start_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), rad.name.as_("bill_no"), rad.patient.as_("patient"), rad.patient_name.as_("patient_name"), @@ -653,9 +687,9 @@ def get_insurance_radiology_data(filters): template.item_group.as_("service_type"), rad.radiology_examination_template.as_("service_name"), rad.hms_tz_insurance_coverage_plan.as_("payment_method"), - Count("*").as_("qty"), - fn.Max(rad_prescription.amount).as_("rate"), - Sum(rad_prescription.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + rad_prescription.amount.as_("rate"), + rad_prescription.amount.as_("amount"), Case().when(rad.docstatus == 1, "Submitted").else_("Draft").as_("status"), rad.practitioner.as_("practitioner"), rad.medical_department.as_("department"), @@ -663,9 +697,7 @@ def get_insurance_radiology_data(filters): rad.modified.as_("date_modified"), ) .where( - (rad.company == filters.company) - & (rad.start_date.between(filters.from_date, filters.to_date)) - & (~rad.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) + (pe.company == filters.company) & ( (rad.hms_tz_insurance_coverage_plan.isnotnull()) & (rad.hms_tz_insurance_coverage_plan != "") @@ -678,17 +710,17 @@ def get_insurance_radiology_data(filters): & (rad_prescription.is_cancelled == 0) & (rad_prescription.is_not_available_inhouse == 0) ) - .groupby( - rad.start_date, - rad.patient, - rad.name, - rad.radiology_examination_template, - rad.hms_tz_insurance_coverage_plan, - rad.practitioner, - rad.docstatus, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_rad_query = insurance_rad_query.where( + (pe.encounter_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + insurance_rad_query = insurance_rad_query.where( + (pe.encounter_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: insurance_rad_query = insurance_rad_query.where( template.item_group == filters.service_type @@ -699,17 +731,21 @@ def get_insurance_radiology_data(filters): rad.insurance_company == filters.payment_mode ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_rad_query = insurance_rad_query.where(pe.appointment.isin(appoints)) + insurance_rad_data = insurance_rad_query.run(as_dict=True) return insurance_rad_data -def get_cash_radiology_data(filters): +def get_cash_radiology_data(filters, appoints): rad = DocType("Radiology Examination") rad_prescription = DocType("Radiology Procedure Prescription") template = DocType("Radiology Examination Template") sii = DocType("Sales Invoice Item") - encounter = DocType("Patient Encounter") + si = DocType("Sales Invoice") + pe = DocType("Patient Encounter") # Paid Radiology Data for OPD Patients, Admitted and Discharged Patients # Link to Sales invoice @@ -721,11 +757,13 @@ def get_cash_radiology_data(filters): .on(rad.radiology_examination_template == template.name) .inner_join(sii) .on(rad.hms_tz_ref_childname == sii.reference_dn) - .inner_join(encounter) - .on(rad.ref_docname == encounter.name) + .inner_join(si) + .on(sii.parent == si.name) + .inner_join(pe) + .on(rad.ref_docname == pe.name) .select( rad.start_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), rad.name.as_("bill_no"), rad.patient.as_("patient"), rad.patient_name.as_("patient_name"), @@ -736,10 +774,10 @@ def get_cash_radiology_data(filters): template.item_group.as_("service_type"), rad.radiology_examination_template.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(rad_prescription.amount).as_("rate"), - Sum(sii.net_amount).as_("amount"), - Sum(sii.amount - sii.net_amount).as_("discount_amount"), + ValueWrapper(1).as_("qty"), + rad_prescription.amount.as_("rate"), + sii.net_amount.as_("amount"), + (sii.amount - sii.net_amount).as_("discount_amount"), Case().when(rad.docstatus == 1, "Submitted").else_("Draft").as_("status"), rad.practitioner.as_("practitioner"), rad.medical_department.as_("department"), @@ -748,35 +786,35 @@ def get_cash_radiology_data(filters): ) .where( (rad.company == filters.company) - & (rad.start_date.between(filters.from_date, filters.to_date)) - & ~rad.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"]) & (rad.docstatus != 2) & (rad.ref_doctype == "Patient Encounter") & (rad.ref_docname == rad_prescription.parent) - & (rad.prescribe == 1) & (rad_prescription.invoiced == 1) & (rad_prescription.prescribe == 1) & (rad_prescription.is_cancelled == 0) & (rad_prescription.is_not_available_inhouse == 0) - & (sii.docstatus == 1) - & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) - ) - .groupby( - rad.start_date, - rad.patient, - rad.name, - rad.radiology_examination_template, - rad.hms_tz_insurance_coverage_plan, - rad.practitioner, - rad.docstatus, + & Not(si.status.isin(["Credit Note Issued", "Return"])) + & (si.docstatus == 1) ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + paid_cash_rad_query = paid_cash_rad_query.where( + (rad.start_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + paid_cash_rad_query = paid_cash_rad_query.where( + (rad.start_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: paid_cash_rad_query = paid_cash_rad_query.where( template.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + paid_cash_rad_query = paid_cash_rad_query.where(pe.appointment.isin(appoints)) + paid_cash_rad_data = paid_cash_rad_query.run(as_dict=True) # Unpaid Radiology Data for ongoing Admitted Patients @@ -787,11 +825,11 @@ def get_cash_radiology_data(filters): .on(rad.hms_tz_ref_childname == rad_prescription.name) .inner_join(template) .on(rad.radiology_examination_template == template.name) - .inner_join(encounter) - .on(rad.ref_docname == encounter.name) + .inner_join(pe) + .on(rad.ref_docname == pe.name) .select( rad.start_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), rad.name.as_("bill_no"), rad.patient.as_("patient"), rad.patient_name.as_("patient_name"), @@ -799,9 +837,9 @@ def get_cash_radiology_data(filters): template.item_group.as_("service_type"), rad.radiology_examination_template.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(rad_prescription.amount).as_("rate"), - Sum(rad_prescription.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + rad_prescription.amount.as_("rate"), + rad_prescription.amount.as_("amount"), Case().when(rad.docstatus == 1, "Submitted").else_("Draft").as_("status"), rad.practitioner.as_("practitioner"), rad.medical_department.as_("department"), @@ -810,44 +848,47 @@ def get_cash_radiology_data(filters): ) .where( (rad.company == filters.company) - & (rad.start_date.between(filters.from_date, filters.to_date)) - & (~rad.workflow_state.isin(["Not Serviced", "Submitted but Not Serviced"])) & (rad.docstatus != 2) & (rad.ref_doctype == "Patient Encounter") & (rad.ref_docname == rad_prescription.parent) & ((rad.inpatient_record.isnotnull()) & (rad.inpatient_record != "")) - & (rad.prescribe == 1) & (rad_prescription.prescribe == 1) & (rad_prescription.invoiced == 0) & (rad_prescription.is_cancelled == 0) & (rad_prescription.is_not_available_inhouse == 0) - ) - .groupby( - rad.start_date, - rad.patient, - rad.name, - rad.radiology_examination_template, - rad.hms_tz_insurance_coverage_plan, - rad.practitioner, - rad.docstatus, + & (rad_prescription.radiology_examination_created == 1) ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + unpaid_cash_rad_query = unpaid_cash_rad_query.where( + (rad.start_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + unpaid_cash_rad_query = unpaid_cash_rad_query.where( + (rad.start_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: unpaid_cash_rad_query = unpaid_cash_rad_query.where( template.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + unpaid_cash_rad_query = unpaid_cash_rad_query.where( + pe.appointment.isin(appoints) + ) + unpaid_cash_rad_data = unpaid_cash_rad_query.run(as_dict=True) return paid_cash_rad_data + unpaid_cash_rad_data -def get_insurance_procedure_data(filters): +def get_insurance_procedure_data(filters, appoints): procedure = DocType("Clinical Procedure") pp = DocType("Procedure Prescription") template = DocType("Clinical Procedure Template") - encounter = DocType("Patient Encounter") + pe = DocType("Patient Encounter") insurance_procedure_query = ( frappe.qb.from_(procedure) @@ -855,11 +896,11 @@ def get_insurance_procedure_data(filters): .on(procedure.hms_tz_ref_childname == pp.name) .inner_join(template) .on(procedure.procedure_template == template.name) - .inner_join(encounter) - .on(procedure.ref_docname == encounter.name) + .inner_join(pe) + .on(procedure.ref_docname == pe.name) .select( procedure.start_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), procedure.name.as_("bill_no"), procedure.patient.as_("patient"), procedure.patient_name.as_("patient_name"), @@ -870,9 +911,9 @@ def get_insurance_procedure_data(filters): template.item_group.as_("service_type"), procedure.procedure_template.as_("service_name"), procedure.hms_tz_insurance_coverage_plan.as_("payment_method"), - Count("*").as_("qty"), - fn.Max(pp.amount).as_("rate"), - Sum(pp.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + pp.amount.as_("rate"), + pp.amount.as_("amount"), Case() .when(procedure.docstatus == 1, "Submitted") .else_("Draft") @@ -883,13 +924,7 @@ def get_insurance_procedure_data(filters): procedure.modified.as_("date_modified"), ) .where( - (procedure.company == filters.company) - & (procedure.start_date.between(filters.from_date, filters.to_date)) - & ( - ~procedure.workflow_state.isin( - ["Not Serviced", "Submitted but Not Serviced"] - ) - ) + (pe.company == filters.company) & ( (procedure.hms_tz_insurance_coverage_plan.isnotnull()) & (procedure.hms_tz_insurance_coverage_plan != "") @@ -897,22 +932,22 @@ def get_insurance_procedure_data(filters): & (procedure.docstatus != 2) & (procedure.ref_doctype == "Patient Encounter") & (procedure.ref_docname == pp.parent) + & (procedure.prescribe == 0) & (pp.prescribe == 0) & (pp.is_cancelled == 0) & (pp.is_not_available_inhouse == 0) - & (procedure.prescribe == 0) - ) - .groupby( - procedure.start_date, - procedure.patient, - procedure.name, - procedure.procedure_template, - procedure.hms_tz_insurance_coverage_plan, - procedure.practitioner, - procedure.docstatus, ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_procedure_query = insurance_procedure_query.where( + (pe.encounter_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + insurance_procedure_query = insurance_procedure_query.where( + (pe.encounter_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: insurance_procedure_query = insurance_procedure_query.where( template.item_group == filters.service_type @@ -920,7 +955,12 @@ def get_insurance_procedure_data(filters): if filters.payment_mode and filters.payment_mode != "Cash": insurance_procedure_query = insurance_procedure_query.where( - procedure.insurance_company == filters.payment_mode + pe.insurance_company == filters.payment_mode + ) + + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_procedure_query = insurance_procedure_query.where( + pe.appointment.isin(appoints) ) insurance_procedure_data = insurance_procedure_query.run(as_dict=True) @@ -928,12 +968,13 @@ def get_insurance_procedure_data(filters): return insurance_procedure_data -def get_cash_procedure_data(filters): +def get_cash_procedure_data(filters, appoints): procedure = DocType("Clinical Procedure") pp = DocType("Procedure Prescription") template = DocType("Clinical Procedure Template") sii = DocType("Sales Invoice Item") - encounter = DocType("Patient Encounter") + si = DocType("Sales Invoice") + pe = DocType("Patient Encounter") # Paid Procedure Data for OPD Patients, Admitted and Discharged Patients # Link to Sales invoice @@ -945,11 +986,13 @@ def get_cash_procedure_data(filters): .on(procedure.procedure_template == template.name) .inner_join(sii) .on(procedure.hms_tz_ref_childname == sii.reference_dn) - .inner_join(encounter) - .on(procedure.ref_docname == encounter.name) + .inner_join(si) + .on(sii.parent == si.name) + .inner_join(pe) + .on(procedure.ref_docname == pe.name) .select( procedure.start_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), procedure.name.as_("bill_no"), procedure.patient.as_("patient"), procedure.patient_name.as_("patient_name"), @@ -960,10 +1003,10 @@ def get_cash_procedure_data(filters): template.item_group.as_("service_type"), procedure.procedure_template.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(pp.amount).as_("rate"), - Sum(sii.amount - sii.net_amount).as_("discount_amount"), - Sum(sii.net_amount).as_("amount"), + ValueWrapper(1).as_("qty"), + pp.amount.as_("rate"), + (sii.amount - sii.net_amount).as_("discount_amount"), + sii.net_amount.as_("amount"), Case() .when(procedure.docstatus == 1, "Submitted") .else_("Draft") @@ -975,39 +1018,36 @@ def get_cash_procedure_data(filters): ) .where( (procedure.company == filters.company) - & (procedure.start_date.between(filters.from_date, filters.to_date)) - & ( - ~procedure.workflow_state.isin( - ["Not Serviced", "Submitted but Not Serviced"] - ) - ) & (procedure.docstatus != 2) & (procedure.ref_doctype == "Patient Encounter") & (procedure.ref_docname.isnotnull()) & (procedure.ref_docname == pp.parent) & (pp.invoiced == 1) - & (procedure.prescribe == 1) & (pp.prescribe == 1) & (pp.is_cancelled == 0) & (pp.is_not_available_inhouse == 0) - & (sii.docstatus == 1) - & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) - ) - .groupby( - procedure.start_date, - procedure.patient, - procedure.name, - procedure.procedure_template, - procedure.prescribe, - procedure.practitioner, - procedure.docstatus, + & Not(si.status.isin(["Credit Note Issued", "Return"])) + & (si.docstatus == 1) ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + paid_procedure_query = paid_procedure_query.where( + (procedure.start_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + paid_procedure_query = paid_procedure_query.where( + (procedure.start_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: paid_procedure_query = paid_procedure_query.where( template.item_group == filters.service_type ) + + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + paid_procedure_query = paid_procedure_query.where(pe.appointment.isin(appoints)) + paid_procedure_data = paid_procedure_query.run(as_dict=True) # Unpaid Radiology Data for ongoing Admitted Patients @@ -1018,11 +1058,11 @@ def get_cash_procedure_data(filters): .on(procedure.hms_tz_ref_childname == pp.name) .inner_join(template) .on(procedure.procedure_template == template.name) - .inner_join(encounter) - .on(procedure.ref_docname == encounter.name) + .inner_join(pe) + .on(procedure.ref_docname == pe.name) .select( procedure.start_date.as_("date"), - encounter.appointment.as_("appointment_no"), + pe.appointment.as_("appointment_no"), procedure.name.as_("bill_no"), procedure.patient.as_("patient"), procedure.patient_name.as_("patient_name"), @@ -1030,9 +1070,9 @@ def get_cash_procedure_data(filters): template.item_group.as_("service_type"), procedure.procedure_template.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(pp.amount).as_("rate"), - Sum(pp.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + pp.amount.as_("rate"), + pp.amount.as_("amount"), Case() .when(procedure.docstatus == 1, "Submitted") .else_("Draft") @@ -1044,12 +1084,6 @@ def get_cash_procedure_data(filters): ) .where( (procedure.company == filters.company) - & (procedure.start_date.between(filters.from_date, filters.to_date)) - & ( - ~procedure.workflow_state.isin( - ["Not Serviced", "Submitted but Not Serviced"] - ) - ) & (procedure.docstatus != 2) & (procedure.ref_doctype == "Patient Encounter") & (procedure.ref_docname.isnotnull()) @@ -1058,44 +1092,50 @@ def get_cash_procedure_data(filters): (procedure.inpatient_record.isnotnull()) & (procedure.inpatient_record != "") ) - & (procedure.prescribe == 1) & (pp.prescribe == 1) & (pp.invoiced == 0) & (pp.is_cancelled == 0) & (pp.is_not_available_inhouse == 0) - ) - .groupby( - procedure.start_date, - procedure.patient, - procedure.name, - procedure.procedure_template, - procedure.prescribe, - procedure.practitioner, - procedure.docstatus, + & (pp.procedure_created == 1) ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + unpaid_procedure_query = unpaid_procedure_query.where( + (procedure.start_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + unpaid_procedure_query = unpaid_procedure_query.where( + (procedure.start_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: unpaid_procedure_query = unpaid_procedure_query.where( template.item_group == filters.service_type ) + + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + unpaid_procedure_query = unpaid_procedure_query.where( + pe.appointment.isin(appoints) + ) + unpaid_procedure_data = unpaid_procedure_query.run(as_dict=True) return paid_procedure_data + unpaid_procedure_data -def get_insurance_drug_data(filters): +def get_insurance_drug_data(filters, appoints): pe = DocType("Patient Encounter") dp = DocType("Drug Prescription") dn = DocType("Delivery Note") md = DocType("Medication") insurance_drug_query = ( - frappe.qb.from_(pe) - .inner_join(dp) - .on(dp.parent == pe.name) + frappe.qb.from_(dp) .inner_join(md) .on(dp.drug_code == md.name) + .inner_join(pe) + .on(dp.parent == pe.name) .inner_join(dn) .on(pe.name == dn.reference_name) .select( @@ -1110,10 +1150,10 @@ def get_insurance_drug_data(filters): .as_("patient_type"), md.item_group.as_("service_type"), dp.drug_code.as_("service_name"), - dn.coverage_plan_name.as_("payment_method"), - Sum(dp.quantity - dp.quantity_returned).as_("qty"), - fn.Max(dp.amount).as_("rate"), - Sum((dp.quantity - dp.quantity_returned) * dp.amount).as_("amount"), + pe.insurance_coverage_plan.as_("payment_method"), + (dp.quantity - dp.quantity_returned).as_("qty"), + dp.amount.as_("rate"), + ((dp.quantity - dp.quantity_returned) * dp.amount).as_("amount"), Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), pe.practitioner.as_("practitioner"), md.healthcare_service_order_category.as_("department"), @@ -1122,25 +1162,24 @@ def get_insurance_drug_data(filters): ) .where( (pe.company == filters.company) - & (pe.encounter_date.between(filters.from_date, filters.to_date)) & (dp.prescribe == 0) & (dp.is_cancelled == 0) & (dp.is_not_available_inhouse == 0) & (dn.docstatus != 2) & (dn.is_return == 0) - & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) - & ((dn.coverage_plan_name.isnotnull()) & (dn.coverage_plan_name != "")) - ) - .groupby( - pe.encounter_date, - pe.patient, - dn.name, - dp.drug_code, - pe.insurance_coverage_plan, - pe.practitioner, - dn.docstatus, + & (dn.reference_doctype == "Patient Encounter") ) ) + + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_drug_query = insurance_drug_query.where( + (pe.encounter_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + insurance_drug_query = insurance_drug_query.where( + (pe.encounter_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: insurance_drug_query = insurance_drug_query.where( md.item_group == filters.service_type @@ -1151,17 +1190,21 @@ def get_insurance_drug_data(filters): pe.insurance_company == filters.payment_mode ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_drug_query = insurance_drug_query.where(pe.appointment.isin(appoints)) + insurance_drug_data = insurance_drug_query.run(as_dict=True) return insurance_drug_data -def get_cash_drug_data(filters): +def get_cash_drug_data(filters, appoints): pe = DocType("Patient Encounter") dp = DocType("Drug Prescription") dn = DocType("Delivery Note") md = DocType("Medication") sii = DocType("Sales Invoice Item") + si = DocType("Sales Invoice") # Paid Drug Data for OPD Patients, Admitted and Discharged Patients # Link to Sales invoice @@ -1173,6 +1216,8 @@ def get_cash_drug_data(filters): .on(dp.drug_code == md.name) .inner_join(sii) .on(dp.name == sii.reference_dn) + # .inner_join(si) + # .on(sii.parent == si.name) .inner_join(dn) .on(pe.name == dn.reference_name) .select( @@ -1188,12 +1233,12 @@ def get_cash_drug_data(filters): md.item_group.as_("service_type"), dp.drug_code.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Sum(dp.quantity - dp.quantity_returned).as_("qty"), - fn.Max(dp.amount).as_("rate"), - Sum( - ((dp.quantity - dp.quantity_returned) * dp.amount) - sii.net_amount - ).as_("discount_amount"), - Sum((dp.quantity - dp.quantity_returned) * sii.net_rate).as_("amount"), + (dp.quantity - dp.quantity_returned).as_("qty"), + dp.amount.as_("rate"), + (((dp.quantity - dp.quantity_returned) * dp.amount) - sii.net_amount).as_( + "discount_amount" + ), + ((dp.quantity - dp.quantity_returned) * sii.net_rate).as_("amount"), Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), pe.practitioner.as_("practitioner"), md.healthcare_service_order_category.as_("department"), @@ -1202,31 +1247,34 @@ def get_cash_drug_data(filters): ) .where( (pe.company == filters.company) - & (pe.encounter_date.between(filters.from_date, filters.to_date)) & (dp.prescribe == 1) & (dp.invoiced == 1) & (dp.is_cancelled == 0) & (dp.is_not_available_inhouse == 0) - & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) - & ((dn.coverage_plan_name.isnull()) | (dn.coverage_plan_name == "")) & (dn.docstatus != 2) & (dn.is_return == 0) & (sii.docstatus == 1) & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) - ) - .groupby( - pe.encounter_date, - pe.patient, - dn.name, - dp.drug_code, - ValueWrapper("Cash").as_("payment_method"), - pe.practitioner, - dn.docstatus, + # & (si.docstatus == 1) + # & Not(si.status.isin(["Credit Note Issued", "Return"])) ) ) + + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + paid_drug_query = paid_drug_query.where( + (pe.encounter_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + paid_drug_query = paid_drug_query.where( + (pe.encounter_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: paid_drug_query = paid_drug_query.where(md.item_group == filters.service_type) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + paid_drug_query = paid_drug_query.where(pe.appointment.isin(appoints)) + paid_drug_data = paid_drug_query.run(as_dict=True) # Unpaid Drug Data for ongoing Admitted Patients @@ -1249,9 +1297,9 @@ def get_cash_drug_data(filters): md.item_group.as_("service_type"), dp.drug_code.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Sum(dp.quantity - dp.quantity_returned).as_("qty"), - fn.Max(dp.amount).as_("rate"), - Sum((dp.quantity - dp.quantity_returned) * dp.amount).as_("amount"), + (dp.quantity - dp.quantity_returned).as_("qty"), + dp.amount.as_("rate"), + ((dp.quantity - dp.quantity_returned) * dp.amount).as_("amount"), Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), pe.practitioner.as_("practitioner"), md.healthcare_service_order_category.as_("department"), @@ -1260,93 +1308,40 @@ def get_cash_drug_data(filters): ) .where( (pe.company == filters.company) - & (pe.encounter_date.between(filters.from_date, filters.to_date)) & ((pe.inpatient_record.isnotnull()) & (pe.inpatient_record != "")) & (dp.prescribe == 1) & (dp.invoiced == 0) & (dp.is_cancelled == 0) & (dp.is_not_available_inhouse == 0) - & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) - & ((dn.coverage_plan_name.isnull()) | (dn.coverage_plan_name == "")) + & (dp.drug_prescription_created == 1) & (dn.docstatus != 2) & (dn.is_return == 0) ) - .groupby( - pe.encounter_date, - pe.patient, - dn.name, - dp.drug_code, - ValueWrapper("Cash").as_("payment_method"), - pe.practitioner, - dn.docstatus, - ) ) + + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + unpaid_drug_query = unpaid_drug_query.where( + (pe.encounter_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + unpaid_drug_query = unpaid_drug_query.where( + (pe.encounter_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: unpaid_drug_query = unpaid_drug_query.where( md.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + unpaid_drug_query = unpaid_drug_query.where(pe.appointment.isin(appoints)) + unpaid_drug_data = unpaid_drug_query.run(as_dict=True) return paid_drug_data + unpaid_drug_data -def get_direct_sales_drug_data(filters): - dn = DocType("Delivery Note") - dni = DocType("Delivery Note Item") - - direct_sales_drug_query = ( - frappe.qb.from_(dn) - .inner_join(dni) - .on(dn.name == dni.parent) - .select( - dn.posting_date.as_("date"), - dn.name.as_("bill_no"), - ValueWrapper("Outsider Customer").as_("patient"), - dn.customer_name.as_("patient_name"), - ValueWrapper("Out-Patient").as_("patient_type"), - dni.item_group.as_("service_type"), - dni.item_code.as_("service_name"), - ValueWrapper("Cash").as_("payment_method"), - Sum(dni.qty).as_("qty"), - fn.Max(dni.net_rate).as_("rate"), - Sum(dni.net_amount).as_("amount"), - Sum(dni.amount - dni.net_amount).as_("discount_amount"), - Case().when(dn.docstatus == 1, "Submitted").else_("Draft").as_("status"), - dni.healthcare_practitioner.as_("practitioner"), - ValueWrapper("Pharmacy").as_("department"), - dni.healthcare_service_unit.as_("service_unit"), - dn.modified.as_("date_modified"), - ) - .where( - (dn.company == filters.company) - & (dn.posting_date.between(filters.from_date, filters.to_date)) - & (~dn.workflow_state.isin(["Not Serviced", "Is Return"])) - & (dn.docstatus != 2) - & (dn.is_return == 0) - & (dn.patient.isnull() | (dn.patient == "")) - & ((dn.form_sales_invoice.isnotnull()) & (dn.form_sales_invoice != "")) - ) - .groupby( - dn.posting_date, - dn.customer_name, - dn.name, - dni.item_code, - dn.form_sales_invoice, - dni.healthcare_practitioner, - dn.docstatus, - ) - ) - if filters.service_type: - direct_sales_drug_query = direct_sales_drug_query.where( - dni.item_group == filters.service_type - ) - direct_sales_drug_data = direct_sales_drug_query.run(as_dict=True) - - return direct_sales_drug_data - - -def get_insurance_therapy_data(filters): +def get_insurance_therapy_data(filters, appoints): tp = DocType("Therapy Plan") tpd = DocType("Therapy Plan Detail") tt = DocType("Therapy Type") @@ -1373,9 +1368,9 @@ def get_insurance_therapy_data(filters): tt.item_group.as_("service_type"), tpd.therapy_type.as_("service_name"), tp.hms_tz_insurance_coverage_plan.as_("payment_method"), - Count("*").as_("qty"), - fn.Max(tpd.amount).as_("rate"), - Sum(tpd.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + tpd.amount.as_("rate"), + tpd.amount.as_("amount"), Case() .when(tp.status == "Completed", "Submitted") .else_("Draft") @@ -1386,8 +1381,7 @@ def get_insurance_therapy_data(filters): tp.modified.as_("date_modified"), ) .where( - (tp.company == filters.company) - & (tp.start_date.between(filters.from_date, filters.to_date)) + (pe.company == filters.company) & (tpd.is_cancelled == 0) & (tpd.is_not_available_inhouse == 0) & (tpd.invoiced == 0) @@ -1396,17 +1390,17 @@ def get_insurance_therapy_data(filters): & (tp.hms_tz_insurance_coverage_plan != "") ) ) - .groupby( - tp.start_date, - tp.patient, - tp.name, - tpd.therapy_type, - tp.hms_tz_insurance_coverage_plan, - pe.practitioner, - tp.status, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_therapy_query = insurance_therapy_query.where( + (pe.encounter_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + insurance_therapy_query = insurance_therapy_query.where( + (pe.encounter_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: insurance_therapy_query = insurance_therapy_query.where( tt.item_group == filters.service_type @@ -1417,16 +1411,22 @@ def get_insurance_therapy_data(filters): pe.insurance_company == filters.payment_mode ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_therapy_query = insurance_therapy_query.where( + pe.appointment.isin(appoints) + ) + insurance_therapy_data = insurance_therapy_query.run(as_dict=True) return insurance_therapy_data -def get_cash_therapy_data(filters): +def get_cash_therapy_data(filters, appoints): tp = DocType("Therapy Plan") tpd = DocType("Therapy Plan Detail") tt = DocType("Therapy Type") sii = DocType("Sales Invoice Item") + si = DocType("Sales Invoice") pe = DocType("Patient Encounter") # Paid Therapy Data for OPD Patients, Admitted and Discharged Patients @@ -1441,6 +1441,8 @@ def get_cash_therapy_data(filters): .on(tpd.therapy_type == tt.name) .inner_join(sii) .on(tpd.name == sii.reference_dn) + .inner_join(si) + .on(sii.parent == si.name) .select( tp.start_date.as_("date"), tp.hms_tz_appointment.as_("appointment_no"), @@ -1454,10 +1456,10 @@ def get_cash_therapy_data(filters): tt.item_group.as_("service_type"), tpd.therapy_type.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(tpd.amount).as_("rate"), + ValueWrapper(1).as_("qty"), + tpd.amount.as_("rate"), (sii.amount - sii.net_amount).as_("discount_amount"), - Sum(sii.net_amount).as_("amount"), + sii.net_amount.as_("amount"), Case() .when(tp.status == "Completed", "Submitted") .else_("Draft") @@ -1469,30 +1471,32 @@ def get_cash_therapy_data(filters): ) .where( (tp.company == filters.company) - & (tp.start_date.between(filters.from_date, filters.to_date)) & (tpd.prescribe == 1) & (tpd.is_cancelled == 0) & (tpd.is_not_available_inhouse == 0) & (tpd.invoiced == 1) - & (sii.docstatus == 1) - & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) - ) - .groupby( - tp.start_date, - tp.patient, - tp.name, - tpd.therapy_type, - ValueWrapper("Cash").as_("payment_method"), - pe.practitioner, - tp.status, + & Not(si.status.isin(["Credit Note Issued", "Return"])) + & (si.docstatus == 1) ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + paid_therapy_query = paid_therapy_query.where( + (tp.start_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + paid_therapy_query = paid_therapy_query.where( + (tp.start_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: paid_therapy_query = paid_therapy_query.where( tt.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + paid_therapy_query = paid_therapy_query.where(pe.appointment.isin(appoints)) + paid_therapy_data = paid_therapy_query.run(as_dict=True) # Unpaid Therapy Data for ongoing Admitted Patients @@ -1518,9 +1522,9 @@ def get_cash_therapy_data(filters): tt.item_group.as_("service_type"), tpd.therapy_type.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(tpd.amount).as_("rate"), - Sum(tpd.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + tpd.amount.as_("rate"), + tpd.amount.as_("amount"), Case() .when(tp.status == "Completed", "Submitted") .else_("Draft") @@ -1532,35 +1536,37 @@ def get_cash_therapy_data(filters): ) .where( (tp.company == filters.company) - & (tp.start_date.between(filters.from_date, filters.to_date)) & (tpd.prescribe == 1) & (tpd.is_cancelled == 0) & (tpd.is_not_available_inhouse == 0) & (tpd.invoiced == 0) & ((pe.inpatient_record.isnotnull()) & (pe.inpatient_record != "")) ) - .groupby( - tp.start_date, - tp.patient, - tp.name, - tpd.therapy_type, - ValueWrapper("Cash").as_("payment_method"), - pe.practitioner, - tp.status, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + unpaid_therapy_query = unpaid_therapy_query.where( + (tp.start_date < filters.from_date) & (pe.appointment.isin(appoints)) + ) + else: + unpaid_therapy_query = unpaid_therapy_query.where( + (tp.start_date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: unpaid_therapy_query = unpaid_therapy_query.where( tt.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + unpaid_therapy_query = unpaid_therapy_query.where(pe.appointment.isin(appoints)) + unpaid_therapy_data = unpaid_therapy_query.run(as_dict=True) return paid_therapy_data + unpaid_therapy_data -def get_insurance_ipd_beds_data(filters): +def get_insurance_ipd_beds_data(filters, appoints): io = DocType("Inpatient Occupancy") ip = DocType("Inpatient Record") hsu = DocType("Healthcare Service Unit") @@ -1575,7 +1581,7 @@ def get_insurance_ipd_beds_data(filters): .inner_join(hsut) .on(hsu.service_unit_type == hsut.name) .select( - fn.Date(io.check_in).as_("date"), + Date(io.check_in).as_("date"), ip.patient_appointment.as_("appointment_no"), ip.name.as_("bill_no"), ip.patient.as_("patient"), @@ -1584,32 +1590,32 @@ def get_insurance_ipd_beds_data(filters): hsut.item_group.as_("service_type"), hsu.service_unit_type.as_("service_name"), ip.insurance_coverage_plan.as_("payment_method"), - Count("*").as_("qty"), - fn.Max(io.amount).as_("rate"), - Sum(io.amount).as_("amount"), + ValueWrapper(1).as_("qty"), + io.amount.as_("rate"), + io.amount.as_("amount"), hsu.service_unit_type.as_("department"), hsu.parent_healthcare_service_unit.as_("service_unit"), io.modified.as_("date_modified"), ) .where( (ip.company == filters.company) - & (io.check_in.between(filters.from_date, filters.to_date)) & (io.is_confirmed == 1) & ( (ip.insurance_coverage_plan.isnotnull()) & (ip.insurance_coverage_plan != "") ) ) - .groupby( - fn.Date(io.check_in), - ip.patient, - ip.name, - io.name, - hsu.service_unit_type, - ip.insurance_coverage_plan, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_ipd_beds_query = insurance_ipd_beds_query.where( + (io.check_in < filters.from_date) & (ip.patient_appointment.isin(appoints)) + ) + else: + insurance_ipd_beds_query = insurance_ipd_beds_query.where( + (io.check_in.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: insurance_ipd_beds_query = insurance_ipd_beds_query.where( hsut.item_group == filters.service_type @@ -1620,12 +1626,17 @@ def get_insurance_ipd_beds_data(filters): ip.insurance_company == filters.payment_mode ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_ipd_beds_query = insurance_ipd_beds_query.where( + ip.patient_appointment.isin(appoints) + ) + insurance_ipd_beds_data = insurance_ipd_beds_query.run(as_dict=True) return insurance_ipd_beds_data -def get_cash_ipd_beds_data(filters): +def get_cash_ipd_beds_data(filters, appoints): io = DocType("Inpatient Occupancy") ip = DocType("Inpatient Record") hsu = DocType("Healthcare Service Unit") @@ -1643,7 +1654,7 @@ def get_cash_ipd_beds_data(filters): .inner_join(sii) .on(io.name == sii.reference_dn) .select( - fn.Date(io.check_in).as_("date"), + Date(io.check_in).as_("date"), ip.patient_appointment.as_("appointment_no"), ip.name.as_("bill_no"), ip.patient.as_("patient"), @@ -1652,43 +1663,49 @@ def get_cash_ipd_beds_data(filters): hsut.item_group.as_("service_type"), hsu.service_unit_type.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(io.amount).as_("rate"), - Sum(sii.net_amount).as_("amount"), - Sum(sii.amount - sii.net_amount).as_("discount_amount"), + ValueWrapper(1).as_("qty"), + io.amount.as_("rate"), + sii.net_amount.as_("amount"), + (sii.amount - sii.net_amount).as_("discount_amount"), hsu.service_unit_type.as_("department"), hsu.parent_healthcare_service_unit.as_("service_unit"), io.modified.as_("date_modified"), ) .where( (ip.company == filters.company) - & (io.check_in.between(filters.from_date, filters.to_date)) & (io.is_confirmed == 1) & (io.invoiced == 1) & (ip.insurance_coverage_plan.isnull() | (ip.insurance_coverage_plan == "")) & (sii.docstatus == 1) & ((sii.sales_invoice_item.isnull()) | (sii.sales_invoice_item == "")) ) - .groupby( - fn.Date(io.check_in), - ip.patient, - ip.name, - io.name, - hsu.service_unit_type, - ip.insurance_coverage_plan, - ) ) + + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + cash_ipd_beds_query = cash_ipd_beds_query.where( + (io.check_in < filters.from_date) & (ip.patient_appointment.isin(appoints)) + ) + else: + cash_ipd_beds_query = cash_ipd_beds_query.where( + (io.check_in.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: cash_ipd_beds_query = cash_ipd_beds_query.where( hsut.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + cash_ipd_beds_query = cash_ipd_beds_query.where( + ip.patient_appointment.isin(appoints) + ) + cash_ipd_beds_data = cash_ipd_beds_query.run(as_dict=True) return cash_ipd_beds_data -def get_insurance_ipd_cons_data(filters): +def get_insurance_ipd_cons_data(filters, appoints): ic = DocType("Inpatient Consultancy") ip = DocType("Inpatient Record") it = DocType("Item") @@ -1712,9 +1729,9 @@ def get_insurance_ipd_cons_data(filters): it.item_group.as_("service_type"), ic.consultation_item.as_("service_name"), ip.insurance_coverage_plan.as_("payment_method"), - Count("*").as_("qty"), - fn.Max(ic.rate).as_("rate"), - Sum(ic.rate).as_("amount"), + ValueWrapper(1).as_("qty"), + ic.rate.as_("rate"), + ic.rate.as_("amount"), pe.medical_department.as_("department"), ic.healthcare_practitioner.as_("practitioner"), pe.healthcare_service_unit.as_("service_unit"), @@ -1722,24 +1739,23 @@ def get_insurance_ipd_cons_data(filters): ) .where( (ip.company == filters.company) - & (ic.date.between(filters.from_date, filters.to_date)) & (ic.is_confirmed == 1) & ( (ip.insurance_coverage_plan.isnotnull()) & (ip.insurance_coverage_plan != "") ) ) - .groupby( - ic.date, - ip.patient, - ip.name, - ic.name, - ic.consultation_item, - ip.insurance_coverage_plan, - ic.healthcare_practitioner, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + insurance_ipd_cons_query = insurance_ipd_cons_query.where( + (ic.date < filters.from_date) & (ip.patient_appointment.isin(appoints)) + ) + else: + insurance_ipd_cons_query = insurance_ipd_cons_query.where( + (ic.date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: insurance_ipd_cons_query = insurance_ipd_cons_query.where( it.item_group == filters.service_type @@ -1750,12 +1766,17 @@ def get_insurance_ipd_cons_data(filters): ip.insurance_company == filters.payment_mode ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + insurance_ipd_cons_query = insurance_ipd_cons_query.where( + ip.patient_appointment.isin(appoints) + ) + insurance_ipd_cons_data = insurance_ipd_cons_query.run(as_dict=True) return insurance_ipd_cons_data -def get_cash_ipd_cons_data(filters): +def get_cash_ipd_cons_data(filters, appoints): ic = DocType("Inpatient Consultancy") ip = DocType("Inpatient Record") it = DocType("Item") @@ -1782,10 +1803,10 @@ def get_cash_ipd_cons_data(filters): it.item_group.as_("service_type"), ic.consultation_item.as_("service_name"), ValueWrapper("Cash").as_("payment_method"), - Count("*").as_("qty"), - fn.Max(ic.rate).as_("rate"), - Sum(sii.net_amount).as_("amount"), - Sum(sii.amount - sii.net_amount).as_("discount_amount"), + ValueWrapper(1).as_("qty"), + ic.rate.as_("rate"), + sii.net_amount.as_("amount"), + (sii.amount - sii.net_amount).as_("discount_amount"), pe.medical_department.as_("department"), ic.healthcare_practitioner.as_("practitioner"), pe.healthcare_service_unit.as_("service_unit"), @@ -1801,92 +1822,73 @@ def get_cash_ipd_cons_data(filters): | (ip.insurance_coverage_plan == "") ) ) - .groupby( - ic.date, - ip.patient, - ip.name, - ic.name, - ic.consultation_item, - ip.insurance_coverage_plan, - ic.healthcare_practitioner, - ) ) + if filters.show_only_prev_items_for_discharged_ipds == 1 and len(appoints) > 0: + cash_ipd_cons_query = cash_ipd_cons_query.where( + (ic.date < filters.from_date) & (ip.patient_appointment.isin(appoints)) + ) + else: + cash_ipd_cons_query = cash_ipd_cons_query.where( + (ic.date.between(filters.from_date, filters.to_date)) + ) + if filters.service_type: cash_ipd_cons_query = cash_ipd_cons_query.where( it.item_group == filters.service_type ) + if filters.show_only_ongoing_ipds == 1 and len(appoints) > 0: + cash_ipd_cons_query = cash_ipd_cons_query.where( + ip.patient_appointment.isin(appoints) + ) + cash_ipd_cons_data = cash_ipd_cons_query.run(as_dict=True) return cash_ipd_cons_data -def get_procedural_charges(filters): +def get_direct_sales_from_invoices(filters): si = DocType("Sales Invoice") sii = DocType("Sales Invoice Item") - sip = DocType("Sales Invoice Payment") - pt = DocType("Patient") - service_type_map = None - if filters.service_type: - service_type_map = sii.item_group == filters.service_type - else: - service_type_map = sii.item_group.isnotnull() - procedural_charges = ( + + invoice_query = ( frappe.qb.from_(si) .inner_join(sii) .on(si.name == sii.parent) - .inner_join(sip) - .on(si.name == sip.parent) - .left_join(pt) - .on(si.patient == pt.name) .select( si.posting_date.as_("date"), si.name.as_("bill_no"), - Case() - .when(si.patient.isnull(), "Outsider Customer") - .else_(si.patient) - .as_("patient"), - Case() - .when(si.patient.isnull(), si.customer_name) - .else_(si.patient_name) - .as_("patient_name"), - Case() - .when(pt.inpatient_record.isnull(), "Out-Patient") - .else_("In-Patient") - .as_("patient_type"), + IfNull(si.patient, "Outsider Customer").as_("patient"), + IfNull(si.patient_name, si.customer_name).as_("patient_name"), + ValueWrapper("Out-Patient").as_("patient_type"), sii.item_group.as_("service_type"), sii.item_code.as_("service_name"), - sip.mode_of_payment.as_("payment_method"), - Sum(sii.qty).as_("qty"), - Sum(sii.amount).as_("rate"), - Sum(sii.amount).as_("amount"), - Sum((sii.amount - sii.net_amount)).as_("discount_amount"), - sii.net_amount.as_("net_amount"), - Case().when(si.docstatus == 1, "Submitted").else_("Draft").as_("status"), - sii.modified.as_("date_modified"), + ValueWrapper("Cash").as_("payment_method"), + sii.qty.as_("qty"), + sii.rate.as_("rate"), + sii.net_amount.as_("amount"), + (sii.amount - sii.net_amount).as_("discount_amount"), + ValueWrapper("Submitted").as_("status"), + si.modified.as_("date_modified"), ) .where( (si.company == filters.company) - & si.posting_date.between(filters.from_date, filters.to_date) - & (~si.status.isin(["Credit Note Issued", "Return"])) + & (si.posting_date.between(filters.from_date, filters.to_date)) + & Not(si.status.isin(["Credit Note Issued", "Return"])) & (si.docstatus == 1) & (si.is_return == 0) - & (sii.reference_dt.isnull()) - & (sii.reference_dn.isnull()) - & (sii.healthcare_practitioner.isnull()) - & (sii.sales_order.isnotnull()) - & service_type_map + & ((sii.reference_dn.isnull()) | (sii.reference_dn == "")) + & ((sii.sales_order.isnotnull()) & (sii.sales_order != "")) ) - .groupby( - si.posting_date, - si.patient, - si.name, - sii.item_code, - sip.mode_of_payment, - ) - ).run(as_dict=True) - return procedural_charges + ) + + if filters.service_type: + invoice_query = invoice_query.where(sii.item_group == filters.service_type) + + invoice_data = invoice_query.run(as_dict=True) + + return invoice_data def get_cancelled_lab_items(filters): @@ -2191,6 +2193,58 @@ def get_cancelled_data(filters): return cancelled_data +def get_prev_and_ongoing_ipds(filters): + ip = DocType("Inpatient Record") + ipd_query = ( + frappe.qb.from_(ip) + .select( + ip.patient_appointment.as_("patient_appointment"), + ) + .where((ip.company == filters.company)) + ) + + if filters.payment_mode and filters.payment_mode == "Cash": + ipd_query = ipd_query.where( + (ip.insurance_subscription.isnull()) | (ip.insurance_subscription == "") + ) + + if filters.payment_mode and filters.payment_mode != "Cash": + ipd_query = ipd_query.where(ip.insurance_company == filters.payment_mode) + + if filters.get("show_only_ongoing_ipds") == 1: + ipd_query = ipd_query.where( + Criterion.any( + [ + # Get all inpatient records that are not discharged within filters selected date range: + ( + (ip.scheduled_date <= filters.to_date) + & (ip.status != "Discharged") + ), + # Get all inpatient records that were discharged next month of the filters selected date range: + # Assumption: Filters date range is always for a month + ( + (ip.scheduled_date <= filters.to_date) + & (ip.discharge_date > filters.to_date) + & (ip.status == "Discharged") + ), + ] + ) + ) + + if filters.get("show_only_prev_items_for_discharged_ipds") == 1: + ipd_query = ipd_query.where( + (ip.scheduled_date < filters.from_date) + & (ip.discharge_date <= filters.to_date) + & (ip.status == "Discharged") + ) + + inpatient_records = ipd_query.run(as_dict=True) + + appoints = list(set([d.patient_appointment for d in inpatient_records])) + + return appoints + + @frappe.whitelist() def get_payment_modes(company): payment_modes = ["", "Cash"] From 455baf28a0b037761b3427e11995f55f1649d965 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Thu, 21 Dec 2023 17:55:30 +0300 Subject: [PATCH 09/12] chore: show draft or unclaimable items on the report --- .../monthly_nhif_detail.json | 51 ----- .../report/monthly_nhif_detail/__init__.py | 0 .../monthly_nhif_detail.js | 29 +++ .../monthly_nhif_detail.json | 27 +++ .../monthly_nhif_detail.py | 191 ++++++++++++++++++ 5 files changed, 247 insertions(+), 51 deletions(-) delete mode 100644 hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json rename hms_tz/{hms_tz => nhif}/report/monthly_nhif_detail/__init__.py (100%) create mode 100644 hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.js create mode 100644 hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.json create mode 100644 hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.py diff --git a/hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json b/hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json deleted file mode 100644 index 9ee8cf9b..00000000 --- a/hms_tz/hms_tz/report/monthly_nhif_detail/monthly_nhif_detail.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "add_total_row": 1, - "columns": [], - "creation": "2023-11-10 00:32:31.598540", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "filters": [ - { - "fieldname": "claim_month", - "fieldtype": "Int", - "label": "Claim Month", - "mandatory": 1, - "wildcard_filter": 0 - }, - { - "fieldname": "claim_year", - "fieldtype": "Int", - "label": "Claim Year", - "mandatory": 1, - "wildcard_filter": 0 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "mandatory": 1, - "options": "Company", - "wildcard_filter": 0 - } - ], - "idx": 0, - "is_standard": "Yes", - "letter_head": "", - "modified": "2023-11-10 00:32:54.106583", - "modified_by": "Administrator", - "module": "Hms Tz", - "name": "Monthly NHIF Detail", - "owner": "Administrator", - "prepared_report": 1, - "query": "SELECT\n npc.name AS \"NHIF Patient Claim:Link/NHIF Patient Claim:\",\n npc.patient AS \"Patient:Link/Patient:150\",\n npc.patient_name AS \"Patient Name:Data:\",\n npc.patient_appointment AS \"Patient Appointment:Link/Patient Appointment:150\",\n npci.ref_doctype AS \"Service Type:Data:\",\n npci.item_name,\n npc.gender AS \"Gender::\",\n npc.attendance_date AS \"Appointment Date:Date:\",\n npc.date_discharge AS \"Discharge Date:Date:\",\n SUM(npci.amount_claimed) AS \"Amount Claimed:Currency:\",\n SUM(npci.item_quantity) AS \"Total Quantity:Int:\",\n npc.folio_no AS \"Folio No::\"\nFROM `tabNHIF Patient Claim` npc\nINNER JOIN `tabNHIF Patient Claim Item` npci ON npc.name = npci.parent\nWHERE npc.docstatus = 1\n AND npc.claim_month = %(claim_month)s\n AND npc.claim_year = %(claim_year)s\n AND npc.company = %(company)s\nGROUP BY npc.patient, npc.patient_appointment, npci.ref_doctype, npci.item_name, npc.gender,npc.attendance_date, npc.date_discharge", - "ref_doctype": "NHIF Patient Claim", - "report_name": "Monthly NHIF Detail", - "report_type": "Query Report", - "roles": [ - { - "role": "System Manager" - } - ] -} \ No newline at end of file diff --git a/hms_tz/hms_tz/report/monthly_nhif_detail/__init__.py b/hms_tz/nhif/report/monthly_nhif_detail/__init__.py similarity index 100% rename from hms_tz/hms_tz/report/monthly_nhif_detail/__init__.py rename to hms_tz/nhif/report/monthly_nhif_detail/__init__.py diff --git a/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.js b/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.js new file mode 100644 index 00000000..b9553e5a --- /dev/null +++ b/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.js @@ -0,0 +1,29 @@ +frappe.query_reports["Monthly NHIF Detail"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1, + }, + { + "fieldname": "claim_month", + "label": __("Claim Month"), + "fieldtype": "Int", + "reqd": 1 + }, + { + "fieldname": "claim_year", + "label": __("Claim Year"), + "fieldtype": "Int", + "reqd": 1 + }, + { + "fieldname": "drafts_unclaimable", + "label": __("Show Drafts/Unclaimable"), + "fieldtype": "Check", + } + ] +}; \ No newline at end of file diff --git a/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.json b/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.json new file mode 100644 index 00000000..11fd67ba --- /dev/null +++ b/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 1, + "columns": [], + "creation": "2023-11-10 00:32:31.598540", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letter_head": "", + "modified": "2023-11-10 00:32:54.106583", + "modified_by": "Administrator", + "module": "NHIF", + "name": "Monthly NHIF Detail", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "NHIF Patient Claim", + "report_name": "Monthly NHIF Detail", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + } + ] +} diff --git a/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.py b/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.py new file mode 100644 index 00000000..8f94a1c7 --- /dev/null +++ b/hms_tz/nhif/report/monthly_nhif_detail/monthly_nhif_detail.py @@ -0,0 +1,191 @@ +import frappe +from frappe import _ +from frappe.utils import flt, getdate, cint +from frappe.query_builder import DocType + + +def execute(filters): + columns = get_columns() + data = get_data(filters) + + return columns, data + + +def get_columns(): + columns = [ + { + "fieldname": "nhif_patient_claim", + "label": _("NHIF Patient Claim"), + "fieldtype": "Link", + "options": "NHIF Patient Claim", + "width": 130, + }, + { + "fieldname": "patient", + "label": _("Patient"), + "fieldtype": "Data", + "width": 100, + }, + { + "fieldname": "patient_name", + "label": _("Patient Name"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "patient_appointment", + "label": _("Patient Appointment"), + "fieldtype": "Link", + "options": "Patient Appointment", + "width": 150, + }, + { + "fieldname": "service_type", + "label": _("Service Type"), + "fieldtype": "Data", + "width": 120, + }, + { + "fieldname": "service_name", + "label": _("Service Name"), + "fieldtype": "Data", + "width": 150, + }, + { + "fieldname": "qty", + "label": _("Qty"), + "fieldtype": "Int", + "width": 70, + }, + { + "fieldname": "unit_price", + "label": _("Unit Price"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "amount_claimed", + "label": _("Amount Claimed"), + "fieldtype": "Currency", + "width": 130, + }, + { + "fieldname": "gender", + "label": _("Gender"), + "fieldtype": "Data", + "width": 100, + }, + { + "fieldname": "appointment_date", + "label": _("Appointment Date"), + "fieldtype": "Date", + "width": 120, + }, + { + "fieldname": "admitted_date", + "label": _("Admitted Date"), + "fieldtype": "Date", + "width": 120, + }, + { + "fieldname": "discharge_date", + "label": _("Discharge Date"), + "fieldtype": "Date", + "width": 120, + }, + { + "fieldname": "folio_no", + "label": _("Folio No"), + "fieldtype": "Data", + "width": 120, + }, + ] + return columns + + +def get_data(filters): + npc = DocType("NHIF Patient Claim") + npci = DocType("NHIF Patient Claim Item") + + data_query = ( + frappe.qb.from_(npc) + .inner_join(npci) + .on(npc.name == npci.parent) + .select( + npc.name.as_("nhif_patient_claim"), + npc.patient.as_("patient"), + npc.patient_name.as_("patient_name"), + npc.patient_appointment.as_("patient_appointment"), + npci.ref_doctype.as_("service_type"), + npci.item_name.as_("service_name"), + npc.gender.as_("gender"), + npci.item_quantity.as_("qty"), + npci.unit_price.as_("unit_price"), + npci.amount_claimed.as_("amount_claimed"), + npc.attendance_date.as_("appointment_date"), + npc.date_admitted.as_("admitted_date"), + npc.date_discharge.as_("discharge_date"), + npc.folio_no.as_("folio_no"), + ) + .where( + (npc.company == filters.get("company")) + & (npc.claim_month == filters.get("claim_month")) + & (npc.claim_year == filters.get("claim_year")) + ) + ) + + if filters.get("drafts_unclaimable") == 1: + data_query = data_query.where(npc.docstatus == 0) + else: + data_query = data_query.where(npc.docstatus == 1) + + return data_query.run(as_dict=True) + + +# SELECT +# npc.name AS "NHIF Patient Claim:Link/NHIF Patient Claim:", +# npc.patient AS "Patient:Link/Patient:150", +# npc.patient_name AS "Patient Name:Data:", +# npc.patient_appointment AS "Patient Appointment:Link/Patient Appointment:150", +# npci.ref_doctype AS "Service Type:Data:", +# npci.item_name, +# npc.gender AS "Gender::", +# npc.attendance_date AS "Appointment Date:Date:", +# npc.date_discharge AS "Discharge Date:Date:", +# SUM(npci.amount_claimed) AS "Amount Claimed:Currency:", +# SUM(npci.item_quantity) AS "Total Quantity:Int:", +# npc.folio_no AS "Folio No::" +# FROM `tabNHIF Patient Claim` npc +# INNER JOIN `tabNHIF Patient Claim Item` npci ON npc.name = npci.parent +# WHERE npc.docstatus = 1 +# AND npc.claim_month = %(claim_month)s +# AND npc.claim_year = %(claim_year)s +# AND npc.company = %(company)s +# GROUP BY npc.patient, npc.patient_appointment, npci.ref_doctype, npci.item_name, npc.gender,npc.attendance_date, npc.date_discharge + + +# "filters": [ +# { +# "fieldname": "claim_month", +# "fieldtype": "Int", +# "label": "Claim Month", +# "mandatory": 1, +# "wildcard_filter": 0 +# }, +# { +# "fieldname": "claim_year", +# "fieldtype": "Int", +# "label": "Claim Year", +# "mandatory": 1, +# "wildcard_filter": 0 +# }, +# { +# "fieldname": "company", +# "fieldtype": "Link", +# "label": "Company", +# "mandatory": 1, +# "options": "Company", +# "wildcard_filter": 0 +# } +# ], +# "query": "SELECT\n npc.name AS \"NHIF Patient Claim:Link/NHIF Patient Claim:\",\n npc.patient AS \"Patient:Link/Patient:150\",\n npc.patient_name AS \"Patient Name:Data:\",\n npc.patient_appointment AS \"Patient Appointment:Link/Patient Appointment:150\",\n npci.ref_doctype AS \"Service Type:Data:\",\n npci.item_name,\n npc.gender AS \"Gender::\",\n npc.attendance_date AS \"Appointment Date:Date:\",\n npc.date_discharge AS \"Discharge Date:Date:\",\n SUM(npci.amount_claimed) AS \"Amount Claimed:Currency:\",\n SUM(npci.item_quantity) AS \"Total Quantity:Int:\",\n npc.folio_no AS \"Folio No::\"\nFROM `tabNHIF Patient Claim` npc\nINNER JOIN `tabNHIF Patient Claim Item` npci ON npc.name = npci.parent\nWHERE npc.docstatus = 1\n AND npc.claim_month = %(claim_month)s\n AND npc.claim_year = %(claim_year)s\n AND npc.company = %(company)s\nGROUP BY npc.patient, npc.patient_appointment, npci.ref_doctype, npci.item_name, npc.gender,npc.attendance_date, npc.date_discharge", From 8df0e0d0a4c15ba5b0ca00bf088d0eb60319511a Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Mon, 8 Jan 2024 16:34:54 +0300 Subject: [PATCH 10/12] fix: alerting or stopping of patient transactions when costs exceeds the deposits/advances/cash limit of admitted patient --- .../ipd_billing_report.html | 2 +- .../ipd_billing_report/ipd_billing_report.py | 26 +++++-- hms_tz/nhif/api/inpatient_record.js | 68 +++++++++++-------- hms_tz/nhif/api/inpatient_record.py | 43 ++++++++++-- hms_tz/nhif/api/patient_encounter.py | 60 ++++++++-------- 5 files changed, 128 insertions(+), 71 deletions(-) diff --git a/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.html b/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.html index 0373dc69..1b506f09 100644 --- a/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.html +++ b/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.html @@ -209,7 +209,7 @@ - + diff --git a/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.py b/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.py index 2c1866ca..2f20675b 100644 --- a/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.py +++ b/hms_tz/hms_tz/report/ipd_billing_report/ipd_billing_report.py @@ -124,16 +124,16 @@ def get_summarized_data(args): date_list = [] single_transaction_per_day = [] mult_transaction_per_day = [] - + transactions = get_transaction_data(args) if len(transactions) == 0: frappe.msgprint( - f"No Record found for the Fitlters: Patient: {frappe.bold(args.patient)}, Appointment: {frappe.bold(args.appointment_no)},\ + f"No Record found for the Fitlters: Patient: {frappe.bold(args.patient)}, Appointment: {frappe.bold(args.appointment_no)},\ Patient Type: {frappe.bold(args.patient_type)} From Date: {frappe.bold(args.from_date)} and To Date: {frappe.bold(args.to_date)}\ you specified..., Please change your filters and try again..!!" - ) + ) return [], [] - + for record in transactions: total_amount = ( flt(record["bed_charges"]) @@ -331,6 +331,9 @@ def get_transaction_data(args): def get_report_summary(args, summary_data, is_summary=False): customer = frappe.get_value("Patient", {"name": args.patient}, ["customer"]) + cash_limit = frappe.get_value( + "Inpatient Record", args.inpatient_record, "cash_limit" + ) deposit_balance = get_balance_on( party_type="Customer", party=customer, company=args.company @@ -412,23 +415,32 @@ def get_report_summary(args, summary_data, is_summary=False): currency = frappe.get_cached_value("Company", args.company, "default_currency") return sorted_data, [ + { + "value": cash_limit, + "label": _("Current Cash Limit"), + "datatype": "Currency", + "currency": currency, + }, { "value": total_deposited_amount, "label": _("Total Deposited Amount"), "datatype": "Currency", "currency": currency, + "color": "blue", }, { "value": total_amount_used, "label": _("Total Amount Used"), "datatype": "Currency", "currency": currency, + "color": "blue", }, { "value": current_balance, "label": _("Current Balance"), "datatype": "Currency", "currency": currency, + "color": "blue", }, ] @@ -439,11 +451,11 @@ def get_data(args): if args.get("patient_type") == "In-Patient": cash_lrpmt_data = get_cash_lrpmt_transaction(args) if len(cash_lrpmt_data) > 0: - data += cash_lrpmt_data - + data += cash_lrpmt_data + # insurance_lrpmt_data = get_insurance_lrpmt_transaction(args) # if insurance_lrpmt_data: data += insurance_lrpmt_data - + ipd_beds = get_ipd_occupancy_transactions(args) if len(ipd_beds) > 0: data += ipd_beds diff --git a/hms_tz/nhif/api/inpatient_record.js b/hms_tz/nhif/api/inpatient_record.js index 1b44144d..8dafb720 100644 --- a/hms_tz/nhif/api/inpatient_record.js +++ b/hms_tz/nhif/api/inpatient_record.js @@ -44,20 +44,8 @@ frappe.ui.form.on('Inpatient Occupancy', { control_inpatient_record_move(frm, cdt, cdn); }, is_confirmed: (frm, cdt, cdn) => { - frappe.call({ - method: 'hms_tz.nhif.api.inpatient_record.confirmed', - args: { - company: frm.doc.company, - appointment: frm.doc.patient_appointment, - insurance_company: frm.doc.insurance_company, - }, - callback: function (r) { - if (r.message) { - frm.refresh_field("inpatient_consultancies"); - frm.reload_doc(); - } - } - }); + isConfirmed(frm, "inpatient_occupancies"); + validate_inpatient_balance_vs_inpatient_cost(frm); }, }); @@ -79,20 +67,8 @@ frappe.ui.form.on('Inpatient Consultancy', { }, is_confirmed: (frm, cdt, cdn) => { - frappe.call({ - method: 'hms_tz.nhif.api.inpatient_record.confirmed', - args: { - company: frm.doc.company, - appointment: frm.doc.patient_appointment, - insurance_company: frm.doc.insurance_company, - }, - callback: function (r) { - if (r.message) { - frm.refresh_field("inpatient_consultancy"); - frm.reload_doc(); - } - } - }); + isConfirmed(frm, "inpatient_consultancy"); + validate_inpatient_balance_vs_inpatient_cost(frm); }, }); @@ -202,3 +178,39 @@ var create_sales_invoice = (frm) => { } }); } + +var isConfirmed = (frm, fieldname) => { + frappe.call({ + method: 'hms_tz.nhif.api.inpatient_record.confirmed', + args: { + company: frm.doc.company, + appointment: frm.doc.patient_appointment, + insurance_company: frm.doc.insurance_company, + }, + callback: function (r) { + if (r.message) { + frm.refresh_field(fieldname); + frm.reload_doc(); + } + } + }); +} + +var validate_inpatient_balance_vs_inpatient_cost = (frm) => { + if (!frm.doc.insurance_subscription){ + frappe.call({ + method: 'hms_tz.nhif.api.inpatient_record.validate_inpatient_balance_vs_inpatient_cost', + args: { + patient: frm.doc.patient, + inpatient_record: frm.doc.name, + patient_appointment: frm.doc.patient_appointment, + }, + callback: function (r) { + if (!r.message) { + frm.refresh_field("inpatient_consultancies"); + frm.reload_doc(); + } + } + }); + } +} \ No newline at end of file diff --git a/hms_tz/nhif/api/inpatient_record.py b/hms_tz/nhif/api/inpatient_record.py index 027bd9af..2da3c32c 100644 --- a/hms_tz/nhif/api/inpatient_record.py +++ b/hms_tz/nhif/api/inpatient_record.py @@ -7,7 +7,10 @@ from frappe import _ from frappe.utils import nowdate, nowtime, get_url_to_form from hms_tz.nhif.api.patient_appointment import get_mop_amount -from hms_tz.nhif.api.patient_encounter import create_healthcare_docs_from_name +from hms_tz.nhif.api.patient_encounter import ( + create_healthcare_docs_from_name, + validate_patient_balance_vs_patient_costs, +) from hms_tz.nhif.api.patient_appointment import get_discount_percent from erpnext.accounts.party import get_party_account from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account @@ -24,6 +27,7 @@ def validate(doc, method): set_beds_price(doc) validate_inpatient_occupancies(doc) + validate_inpatient_balance_vs_inpatient_cost(doc.patient, doc.patient_appointment, doc.name) def validate_inpatient_occupancies(doc): @@ -79,11 +83,17 @@ def daily_update_inpatient_occupancies(): @frappe.whitelist() -def confirmed(company, appointment, insurance_company): +def confirmed(company, appointment, insurance_company=None): if insurance_company and "NHIF" in insurance_company: - validate_nhif_patient_claim_status("Inpatient Record", company, appointment, insurance_company, "inpatient_record") + validate_nhif_patient_claim_status( + "Inpatient Record", + company, + appointment, + insurance_company, + "inpatient_record", + ) return True - + def create_delivery_note(encounter, item_code, item_rate, warehouse, row, practitioner): insurance_subscription = encounter.insurance_subscription @@ -335,3 +345,28 @@ def create_sales_invoice(args): invoice_doc.save() return invoice_doc.name + + +@frappe.whitelist() +def validate_inpatient_balance_vs_inpatient_cost( + patient, patient_appointment, inpatient_record +): + patient_encounters = frappe.get_all( + "Patient Encounter", + filters={ + "patient": patient, + "appointment": patient_appointment, + "inpatient_record": inpatient_record, + }, + fields=["name"], + order_by="encounter_date desc", + pluck="name", + ) + if not patient_encounters or len(patient_encounters) == 0: + return + + encounter_doc = frappe.get_doc("Patient Encounter", patient_encounters[0]) + validate_patient_balance_vs_patient_costs( + encounter_doc, encounters=patient_encounters + ) + return True diff --git a/hms_tz/nhif/api/patient_encounter.py b/hms_tz/nhif/api/patient_encounter.py index 62527bae..2e4523b1 100644 --- a/hms_tz/nhif/api/patient_encounter.py +++ b/hms_tz/nhif/api/patient_encounter.py @@ -12,6 +12,7 @@ nowtime, cint, cstr, + flt, date_diff, ) from hms_tz.nhif.api.healthcare_utils import ( @@ -1731,10 +1732,12 @@ def update_drug_prescription(patient_encounter_doc, name): ) -def validate_patient_balance_vs_patient_costs(doc): - encounters = get_patient_encounters(doc) +def validate_patient_balance_vs_patient_costs(doc, encounters=None): if not encounters or len(encounters) == 0: - return + encounters = get_patient_encounters(doc) + + if not encounters or len(encounters) == 0: + return total_amount_billed = 0 @@ -1763,7 +1766,7 @@ def validate_patient_balance_vs_patient_costs(doc): child.quantity - child.quantity_returned ) * child.amount else: - total_amount_billed = child.amount + total_amount_billed += child.amount inpatient_record_doc = frappe.get_doc("Inpatient Record", doc.inpatient_record) @@ -1807,17 +1810,19 @@ def make_cash_limit_alert(doc, cash_limit_percent, cash_limit_details): if cash_limit_percent > 0 and cash_limit_percent <= cash_limit_details.get( "hms_tz_minimum_cash_limit_percent" ): + msg_per = f"""
+

The patient: {doc.patient} - {doc.patient_name}\ + has reached {100 - flt(cash_limit_percent, 2)}% of his/her cash limit.

+

please request patient to deposit advances or request patient cash limit adjustment

+
""" + if ( cash_limit_details.get("hms_tz_limit_under_minimum_percent_action") == "Warn" ): frappe.msgprint( - frappe.bold( - "The patient {0} - {1} has reached 90% of their cash limit.\ - Please request patient to deposit advances or request patient cash limit adjustment".format( - doc.patient, doc.patient_name - ) - ) + title="Cash Limit Exceeded", + msg=msg_per, ) elif ( @@ -1825,35 +1830,27 @@ def make_cash_limit_alert(doc, cash_limit_percent, cash_limit_details): == "Stop" ): frappe.throw( - frappe.bold( - "The patient {0} - {1} has reached 90% of their cash limit.\ - Please request patient to deposit advances or request patient cash limit adjustment".format( - doc.patient, doc.patient_name - ) - ) + title="Cash Limit Exceeded", + msg=msg_per, ) elif cash_limit_percent <= 0: + msg = f"""
+

The deposit balance of this patient: {doc.patient} - {doc.patient_name}\ + is not enough or Patient has reached the cash limit.

+

please request patient to deposit advances or request patient cash limit adjustment

+
""" + if cash_limit_details.get("hms_tz_limit_exceed_action") == "Warn": frappe.msgprint( - frappe.bold( - "The deposit balance of this patient {0} - {1} is not enough or\ - the patient has reached the cash limit. In order to submit this encounter,\ - please request patient to deposit advances or request patient cash limit adjustment".format( - doc.patient, doc.patient_name - ) - ) + title="Cash Limit Exceeded", + msg=msg, ) if cash_limit_details.get("hms_tz_limit_exceed_action") == "Stop": frappe.throw( - frappe.bold( - "The deposit balance of this patient {0} - {1} is not enough or\ - the patient has reached the cash limit. In order to submit this encounter,\ - please request patient to deposit advances or request patient cash limit adjustment".format( - doc.patient, doc.patient_name - ) - ) + title="Cash Limit Exceeded", + msg=msg, ) @@ -2492,7 +2489,8 @@ def get_filtered_dosage(doctype, txt, searchfield, start, page_len, filters): if ( frappe.get_cached_value( "Dosage Form", filters.get("dosage_form"), "has_restricted_qty" - ) == 1 + ) + == 1 ): return frappe.get_all( doctype, From 609a9c9cb8c721022ae84aff6a673e7819b7e180 Mon Sep 17 00:00:00 2001 From: av-dev2 Date: Tue, 9 Jan 2024 12:09:48 +0300 Subject: [PATCH 11/12] feat: showing items which exceeds Non NHIF Insurance daily limit --- .../itemized_bill_report.html | 67 +++++++ .../itemized_bill_report.py | 169 ++++++++++++++++++ 2 files changed, 236 insertions(+) diff --git a/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.html b/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.html index 028a7dcf..c36e8219 100644 --- a/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.html +++ b/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.html @@ -16,6 +16,10 @@ vertical-align: top !important; padding: 1px !important; } + .print-format th { + font-weight: bold; + color: black; + } hr { margin-top: 10px; @@ -42,6 +46,10 @@ vertical-align: top !important; padding: 1px !important; } + .print-format th { + font-weight: bold; + color: black; + } hr { margin-top: 10px; @@ -177,6 +185,65 @@
+ {% for(var d=0, h=data.length; d 0) { %} + + +
+
+
{%= __("Daily Limit Exceeded Items:") %}
+ + + + + + + + + + + + + + + + + + + + + + + {% for(var i=0, l=exceeded_items.length; i + + + + + + + + + + {% } %} + + + + + + + +
{%= __("SrNo") %}{%= __("Date") %}{%= __("Category") %}{%= __("Description") %}{%= __("Quantity") %}{%= __("Rate") %}{%= __("Amount") %}
{%= i+1 %}{%= exceeded_items[i].date %}{%= exceeded_items[i].category %}{%= exceeded_items[i].description %}{%= exceeded_items[i].quantity %}{%= exceeded_items[i][("rate")].toLocaleString(undefined, + {minimumFractionDigits: 2, maximumFractionDigits: 2}) %}{%= exceeded_items[i][("amount")].toLocaleString(undefined, + {minimumFractionDigits: 2, maximumFractionDigits: 2}) %}
Total{%= data[d].total_limit_exceeded_amount.toLocaleString(undefined, {minimumFractionDigits: 2, + maximumFractionDigits: 2}) %}
+ + + {% } %} + {% } %} + {% } %}
diff --git a/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.py b/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.py index 4fcd1b90..e54a880f 100644 --- a/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.py +++ b/hms_tz/hms_tz/report/itemized_bill_report/itemized_bill_report.py @@ -6,6 +6,8 @@ from frappe import _ from frappe.utils import cint, flt from erpnext.accounts.utils import get_balance_on +from frappe.query_builder import DocType, functions as fn +from pypika.terms import ValueWrapper # Case, Criterion, , Not def execute(filters=None): @@ -110,6 +112,12 @@ def execute(filters=None): last_row["printed_by"] = print_person + exceeded_items = get_daily_limit_exceeded_items(filters) + if len(exceeded_items) > 0: + last_row["limit_exceeded_items"] = exceeded_items + last_row["total_limit_exceeded_amount"] = sum( + [d.amount for d in exceeded_items] + ) data.append(last_row) return columns, data @@ -175,6 +183,13 @@ def execute(filters=None): last_row["printed_by"] = print_person + exceeded_items = get_daily_limit_exceeded_items(filters) + if len(exceeded_items) > 0: + last_row["limit_exceeded_items"] = exceeded_items + last_row["total_limit_exceeded_amount"] = sum( + [d.amount for d in exceeded_items] + ) + data.append(last_row) return columns, data @@ -249,6 +264,160 @@ def execute(filters=None): return columns, data # , None, None, summary_view +def get_daily_limit_exceeded_items(filters): + if filters.get("patient_type") == "In-Patient": + return [] + + pe = DocType("Patient Encounter") + + encounters_query = ( + frappe.qb.from_(pe) + .select("name") + .where( + (pe.patient == filters.patient) + & (pe.appointment == filters.patient_appointment) + & (pe.inpatient_record.isnull() | pe.inpatient_record == "") + ) + ) + + if filters.get("from_date"): + encounters_query.where((pe.encounter_date >= filters.from_date)) + + if filters.get("to_date"): + encounters_query.where((pe.encounter_date <= filters.to_date)) + encounters = [d.name for d in encounters_query.run(as_dict=True)] + + if not encounters or len(encounters) == 0: + return [] + + data = get_exceeded_lab_items(encounters) + data += get_exceeded_radiology_items(encounters) + data += get_exceeded_procedure_items(encounters) + data += get_exceeded_drug_items(encounters) + data += get_exceeded_therapy_items(encounters) + return data + + +def get_exceeded_lab_items(encounters): + lab = DocType("Lab Prescription") + temp = DocType("Lab Test Template") + lab_items = ( + frappe.qb.from_(lab) + .inner_join(temp) + .on(lab.lab_test_code == temp.name) + .select( + fn.Date(lab.creation).as_("date"), + temp.lab_test_group.as_("category"), + lab.lab_test_name.as_("description"), + ValueWrapper(1).as_("quantity"), + lab.amount.as_("rate"), + lab.amount.as_("amount"), + ) + .where( + (lab.prescribe == 0) + & (lab.hms_tz_is_limit_exceeded == 1) + & (lab.parent.isin(encounters)) + ) + ).run(as_dict=True) + return lab_items + + +def get_exceeded_radiology_items(encounters): + rad = DocType("Radiology Procedure Prescription") + temp = DocType("Radiology Examination Template") + rad_items = ( + frappe.qb.from_(rad) + .inner_join(temp) + .on(rad.radiology_examination_template == temp.name) + .select( + fn.Date(rad.creation).as_("date"), + temp.item_group.as_("category"), + rad.radiology_procedure_name.as_("description"), + ValueWrapper(1).as_("quantity"), + rad.amount.as_("rate"), + rad.amount.as_("amount"), + ) + .where( + (rad.prescribe == 0) + & (rad.hms_tz_is_limit_exceeded == 1) + & (rad.parent.isin(encounters)) + ) + ).run(as_dict=True) + return rad_items + + +def get_exceeded_procedure_items(encounters): + proc = DocType("Procedure Prescription") + temp = DocType("Clinical Procedure Template") + proc_items = ( + frappe.qb.from_(proc) + .inner_join(temp) + .on(proc.procedure == temp.name) + .select( + fn.Date(proc.creation).as_("date"), + temp.item_group.as_("category"), + proc.procedure_name.as_("description"), + ValueWrapper(1).as_("quantity"), + proc.amount.as_("rate"), + proc.amount.as_("amount"), + ) + .where( + (proc.prescribe == 0) + & (proc.hms_tz_is_limit_exceeded == 1) + & (proc.parent.isin(encounters)) + ) + ).run(as_dict=True) + return proc_items + + +def get_exceeded_drug_items(encounters): + drug = DocType("Drug Prescription") + temp = DocType("Medication") + drug_items = ( + frappe.qb.from_(drug) + .inner_join(temp) + .on(drug.drug_code == temp.name) + .select( + fn.Date(drug.creation).as_("date"), + temp.item_group.as_("category"), + drug.drug_name.as_("description"), + (drug.quantity - drug.quantity_returned).as_("quantity"), + drug.amount.as_("rate"), + ((drug.quantity - drug.quantity_returned) * drug.amount).as_("amount"), + ) + .where( + (drug.prescribe == 0) + & (drug.hms_tz_is_limit_exceeded == 1) + & (drug.parent.isin(encounters)) + ) + ).run(as_dict=True) + return drug_items + + +def get_exceeded_therapy_items(encounters): + therapy = DocType("Therapy Plan Detail") + temp = DocType("Therapy Type") + therapy_items = ( + frappe.qb.from_(therapy) + .inner_join(temp) + .on(therapy.therapy_type == temp.name) + .select( + fn.Date(therapy.creation).as_("date"), + temp.item_group.as_("category"), + therapy.therapy_type.as_("description"), + ValueWrapper(1).as_("quantity"), + therapy.amount.as_("rate"), + therapy.amount.as_("amount"), + ) + .where( + (therapy.prescribe == 0) + & (therapy.hms_tz_is_limit_exceeded == 1) + & (therapy.parent.isin(encounters)) + ) + ).run(as_dict=True) + return therapy_items + + def get_columns(filters): columns = [ {"fieldname": "date", "fieldtype": "date", "label": _("Date")}, From 5fcf547815420006469bea8dc30adc38929b8ba2 Mon Sep 17 00:00:00 2001 From: aakvatech <35020381+aakvatech@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:30:55 +0300 Subject: [PATCH 12/12] chore: Bumped to v14.12.0 --- hms_tz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hms_tz/__init__.py b/hms_tz/__init__.py index 41a5810b..6308a2cf 100644 --- a/hms_tz/__init__.py +++ b/hms_tz/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -__version__ = "14.11.0" +__version__ = "14.12.0"