From 31e4cc65961308a89f5ef5c3f24a07b2f3313d65 Mon Sep 17 00:00:00 2001 From: Anoop Date: Fri, 21 Oct 2022 21:29:08 +0530 Subject: [PATCH] Rebranded --- .git-blame-ignore-revs | 15 + .github/CODEOWNERS | 7 + .github/helper/install.sh | 49 + .github/helper/site_config.json | 16 + .github/workflows/ci.yml | 106 ++ .github/workflows/codeql.yml | 55 + .gitignore | 6 + .pre-commit-config.yaml | 28 + MANIFEST.in | 18 + README.md | 48 + dev-requirements.txt | 1 + healthcare/__init__.py | 1 + healthcare/config/__init__.py | 0 healthcare/config/desktop.py | 13 + healthcare/config/docs.py | 11 + healthcare/controllers/queries.py | 35 + healthcare/healthcare/__init__.py | 0 .../healthcare/custom_doctype/__init__.py | 0 .../custom_doctype/sales_invoice.py | 45 + .../custom_doctype/test_sales_invoice.py | 29 + .../clinical_procedures.json | 26 + .../clinical_procedures_status.json | 26 + .../department_wise_patient_appointments.json | 25 + .../dashboard_chart/diagnoses/diagnoses.json | 26 + .../in_patient_status/in_patient_status.json | 26 + .../dashboard_chart/lab_tests/lab_tests.json | 26 + .../patient_appointments.json | 27 + .../dashboard_chart/symptoms/symptoms.json | 26 + .../dashboard_chart_source/__init__.py | 0 .../__init__.py | 0 .../department_wise_patient_appointments.js | 14 + .../department_wise_patient_appointments.json | 13 + .../department_wise_patient_appointments.py | 70 ++ .../desk_page/healthcare/healthcare.json | 122 +++ healthcare/healthcare/doctype/__init__.py | 0 .../healthcare/doctype/antibiotic/__init__.py | 0 .../doctype/antibiotic/antibiotic.js | 5 + .../doctype/antibiotic/antibiotic.json | 151 +++ .../doctype/antibiotic/antibiotic.py | 10 + .../doctype/antibiotic/test_antibiotic.py | 10 + .../doctype/appointment_type/__init__.py | 0 .../appointment_type/appointment_type.js | 83 ++ .../appointment_type/appointment_type.json | 114 +++ .../appointment_type/appointment_type.py | 75 ++ .../appointment_type_dashboard.py | 10 + .../appointment_type/test_appointment_type.py | 12 + .../appointment_type_service_item/__init__.py | 0 .../appointment_type_service_item.json | 67 ++ .../appointment_type_service_item.py | 11 + .../healthcare/doctype/body_part/__init__.py | 0 .../healthcare/doctype/body_part/body_part.js | 8 + .../doctype/body_part/body_part.json | 45 + .../healthcare/doctype/body_part/body_part.py | 11 + .../doctype/body_part/test_body_part.py | 11 + .../doctype/body_part_link/__init__.py | 0 .../body_part_link/body_part_link.json | 32 + .../doctype/body_part_link/body_part_link.py | 11 + .../doctype/clinical_procedure/__init__.py | 0 .../clinical_procedure/clinical_procedure.js | 377 +++++++ .../clinical_procedure.json | 351 +++++++ .../clinical_procedure/clinical_procedure.py | 313 ++++++ .../clinical_procedure_list.js | 11 + .../test_clinical_procedure.py | 76 ++ .../clinical_procedure_item/__init__.py | 0 .../clinical_procedure_item.json | 123 +++ .../clinical_procedure_item.py | 10 + .../clinical_procedure_template/__init__.py | 0 .../clinical_procedure_template.js | 220 ++++ .../clinical_procedure_template.json | 286 ++++++ .../clinical_procedure_template.py | 149 +++ .../clinical_procedure_template_dashboard.py | 8 + .../test_clinical_procedure_template.py | 10 + .../doctype/codification_table/__init__.py | 0 .../codification_table.json | 56 + .../codification_table/codification_table.py | 10 + .../healthcare/doctype/complaint/__init__.py | 0 .../healthcare/doctype/complaint/complaint.js | 5 + .../doctype/complaint/complaint.json | 116 +++ .../healthcare/doctype/complaint/complaint.py | 10 + .../doctype/complaint/test_complaint.py | 10 + .../descriptive_test_result/__init__.py | 0 .../descriptive_test_result.json | 74 ++ .../descriptive_test_result.py | 10 + .../descriptive_test_template/__init__.py | 0 .../descriptive_test_template.json | 41 + .../descriptive_test_template.py | 10 + .../healthcare/doctype/diagnosis/__init__.py | 0 .../healthcare/doctype/diagnosis/diagnosis.js | 5 + .../doctype/diagnosis/diagnosis.json | 116 +++ .../healthcare/doctype/diagnosis/diagnosis.py | 10 + .../doctype/diagnosis/test_diagnosis.py | 12 + .../doctype/dosage_form/__init__.py | 0 .../doctype/dosage_form/dosage_form.js | 5 + .../doctype/dosage_form/dosage_form.json | 114 +++ .../doctype/dosage_form/dosage_form.py | 10 + .../doctype/dosage_form/test_dosage_form.py | 10 + .../doctype/dosage_strength/__init__.py | 0 .../dosage_strength/dosage_strength.json | 102 ++ .../dosage_strength/dosage_strength.py | 10 + .../doctype/drug_prescription/__init__.py | 0 .../drug_prescription/drug_prescription.json | 122 +++ .../drug_prescription/drug_prescription.py | 35 + .../healthcare/doctype/exercise/__init__.py | 0 .../healthcare/doctype/exercise/exercise.json | 62 ++ .../healthcare/doctype/exercise/exercise.py | 11 + .../exercise_difficulty_level/__init__.py | 0 .../exercise_difficulty_level.js | 8 + .../exercise_difficulty_level.json | 45 + .../exercise_difficulty_level.py | 11 + .../test_exercise_difficulty_level.py | 11 + .../doctype/exercise_type/__init__.py | 0 .../doctype/exercise_type/exercise_type.js | 186 ++++ .../doctype/exercise_type/exercise_type.json | 144 +++ .../doctype/exercise_type/exercise_type.py | 15 + .../exercise_type/test_exercise_type.py | 11 + .../doctype/exercise_type_step/__init__.py | 0 .../exercise_type_step.json | 44 + .../exercise_type_step/exercise_type_step.py | 11 + .../doctype/fee_validity/__init__.py | 0 .../doctype/fee_validity/fee_validity.js | 5 + .../doctype/fee_validity/fee_validity.json | 134 +++ .../doctype/fee_validity/fee_validity.py | 60 ++ .../doctype/fee_validity/test_fee_validity.py | 57 ++ .../fee_validity_reference/__init__.py | 0 .../fee_validity_reference.json | 32 + .../fee_validity_reference.py | 11 + healthcare/healthcare/doctype/healthcare.py | 2 + .../doctype/healthcare_activity/__init__.py | 0 .../healthcare_activity.js | 26 + .../healthcare_activity.json | 88 ++ .../healthcare_activity.py | 9 + .../test_healthcare_activity.py | 9 + .../healthcare_practitioner/__init__.py | 0 .../healthcare_practitioner.js | 144 +++ .../healthcare_practitioner.json | 339 +++++++ .../healthcare_practitioner.py | 109 ++ .../healthcare_practitioner_dashboard.py | 16 + .../test_healthcare_practitioner.py | 10 + .../healthcare_schedule_time_slot/__init__.py | 0 .../healthcare_schedule_time_slot.json | 136 +++ .../healthcare_schedule_time_slot.py | 10 + .../healthcare_service_unit/__init__.py | 0 .../healthcare_service_unit.js | 73 ++ .../healthcare_service_unit.json | 257 +++++ .../healthcare_service_unit.py | 138 +++ .../healthcare_service_unit_dashboard.py | 6 + .../healthcare_service_unit_tree.js | 185 ++++ .../test_healthcare_service_unit.py | 24 + .../healthcare_service_unit_type/__init__.py | 0 .../healthcare_service_unit_type.js | 86 ++ .../healthcare_service_unit_type.json | 196 ++++ .../healthcare_service_unit_type.py | 151 +++ .../healthcare_service_unit_type_dashboard.py | 10 + .../test_healthcare_service_unit_type.py | 36 + .../doctype/healthcare_settings/__init__.py | 0 .../healthcare_settings.js | 80 ++ .../healthcare_settings.json | 359 +++++++ .../healthcare_settings.py | 105 ++ .../test_healthcare_settings.py | 10 + .../inpatient_medication_entry/__init__.py | 0 .../inpatient_medication_entry.js | 74 ++ .../inpatient_medication_entry.json | 204 ++++ .../inpatient_medication_entry.py | 349 +++++++ .../inpatient_medication_entry_dashboard.py | 9 + .../test_inpatient_medication_entry.py | 182 ++++ .../__init__.py | 0 .../inpatient_medication_entry_detail.json | 163 +++ .../inpatient_medication_entry_detail.py | 11 + .../inpatient_medication_order/__init__.py | 0 .../inpatient_medication_order.js | 107 ++ .../inpatient_medication_order.json | 196 ++++ .../inpatient_medication_order.py | 82 ++ .../inpatient_medication_order_list.js | 16 + .../test_inpatient_medication_order.py | 162 +++ .../__init__.py | 0 .../inpatient_medication_order_entry.json | 94 ++ .../inpatient_medication_order_entry.py | 11 + .../doctype/inpatient_occupancy/__init__.py | 0 .../inpatient_occupancy.json | 64 ++ .../inpatient_occupancy.py | 10 + .../doctype/inpatient_record/__init__.py | 0 .../inpatient_record/inpatient_record.js | 319 ++++++ .../inpatient_record/inpatient_record.json | 590 +++++++++++ .../inpatient_record/inpatient_record.py | 381 +++++++ .../inpatient_record/inpatient_record_list.js | 17 + .../inpatient_record/test_inpatient_record.py | 220 ++++ .../doctype/lab_prescription/__init__.py | 0 .../lab_prescription/lab_prescription.json | 78 ++ .../lab_prescription/lab_prescription.py | 10 + .../healthcare/doctype/lab_test/__init__.py | 0 .../healthcare/doctype/lab_test/lab_test.js | 263 +++++ .../healthcare/doctype/lab_test/lab_test.json | 648 ++++++++++++ .../healthcare/doctype/lab_test/lab_test.py | 415 ++++++++ .../doctype/lab_test/lab_test_list.js | 71 ++ .../doctype/lab_test/test_lab_test.py | 230 +++++ .../lab_test_group_template/__init__.py | 0 .../lab_test_group_template.json | 119 +++ .../lab_test_group_template.py | 10 + .../doctype/lab_test_sample/__init__.py | 0 .../lab_test_sample/lab_test_sample.js | 5 + .../lab_test_sample/lab_test_sample.json | 68 ++ .../lab_test_sample/lab_test_sample.py | 10 + .../lab_test_sample/test_lab_test_sample.py | 10 + .../doctype/lab_test_template/__init__.py | 0 .../lab_test_template/lab_test_template.js | 124 +++ .../lab_test_template/lab_test_template.json | 391 +++++++ .../lab_test_template/lab_test_template.py | 154 +++ .../lab_test_template_dashboard.py | 8 + .../lab_test_template_list.js | 7 + .../test_lab_test_template.py | 12 + .../doctype/lab_test_uom/__init__.py | 0 .../doctype/lab_test_uom/lab_test_uom.js | 5 + .../doctype/lab_test_uom/lab_test_uom.json | 148 +++ .../doctype/lab_test_uom/lab_test_uom.py | 10 + .../doctype/lab_test_uom/test_lab_test_uom.py | 12 + .../doctype/medical_code/__init__.py | 0 .../doctype/medical_code/medical_code.js | 5 + .../doctype/medical_code/medical_code.json | 69 ++ .../doctype/medical_code/medical_code.py | 11 + .../doctype/medical_code/test_medical_code.py | 10 + .../doctype/medical_code_standard/__init__.py | 0 .../medical_code_standard.js | 5 + .../medical_code_standard.json | 94 ++ .../medical_code_standard.py | 10 + .../test_medical_code_standard.py | 10 + .../doctype/medical_department/__init__.py | 0 .../medical_department/medical_department.js | 5 + .../medical_department.json | 156 +++ .../medical_department/medical_department.py | 10 + .../test_medical_department.py | 12 + .../doctype/normal_test_result/__init__.py | 0 .../normal_test_result.json | 186 ++++ .../normal_test_result/normal_test_result.py | 10 + .../doctype/normal_test_template/__init__.py | 0 .../normal_test_template.json | 84 ++ .../normal_test_template.py | 10 + .../nursing_checklist_template/__init__.py | 0 .../nursing_checklist_template.js | 8 + .../nursing_checklist_template.json | 94 ++ .../nursing_checklist_template.py | 9 + .../test_nursing_checklist_template.py | 9 + .../test_records.json | 18 + .../__init__.py | 0 .../nursing_checklist_template_task.json | 104 ++ .../nursing_checklist_template_task.py | 9 + .../doctype/nursing_task/__init__.py | 0 .../doctype/nursing_task/nursing_task.js | 107 ++ .../doctype/nursing_task/nursing_task.json | 384 +++++++ .../doctype/nursing_task/nursing_task.py | 141 +++ .../doctype/nursing_task/nursing_task_list.js | 37 + .../doctype/nursing_task/test_nursing_task.py | 134 +++ .../healthcare/doctype/organism/__init__.py | 0 .../healthcare/doctype/organism/organism.js | 5 + .../healthcare/doctype/organism/organism.json | 152 +++ .../healthcare/doctype/organism/organism.py | 10 + .../doctype/organism/test_organism.py | 10 + .../doctype/organism_test_item/__init__.py | 0 .../organism_test_item.json | 144 +++ .../organism_test_item/organism_test_item.py | 10 + .../doctype/organism_test_result/__init__.py | 0 .../organism_test_result.json | 144 +++ .../organism_test_result.py | 10 + .../healthcare/doctype/patient/__init__.py | 0 .../healthcare/doctype/patient/patient.js | 146 +++ .../healthcare/doctype/patient/patient.json | 542 ++++++++++ .../healthcare/doctype/patient/patient.py | 340 +++++++ .../doctype/patient/patient_dashboard.py | 26 + .../doctype/patient/test_patient.py | 117 +++ .../doctype/patient_appointment/__init__.py | 0 .../patient_appointment.js | 659 ++++++++++++ .../patient_appointment.json | 403 ++++++++ .../patient_appointment.py | 680 +++++++++++++ .../patient_appointment_calendar.js | 14 + .../patient_appointment_dashboard.py | 14 + .../patient_appointment_list.js | 16 + .../test_patient_appointment.py | 570 +++++++++++ .../doctype/patient_assessment/__init__.py | 0 .../patient_assessment/patient_assessment.js | 88 ++ .../patient_assessment.json | 181 ++++ .../patient_assessment/patient_assessment.py | 40 + .../test_patient_assessment.py | 11 + .../patient_assessment_detail/__init__.py | 0 .../patient_assessment_detail.json | 32 + .../patient_assessment_detail.py | 11 + .../patient_assessment_parameter/__init__.py | 0 .../patient_assessment_parameter.js | 8 + .../patient_assessment_parameter.json | 45 + .../patient_assessment_parameter.py | 11 + .../test_patient_assessment_parameter.py | 11 + .../patient_assessment_sheet/__init__.py | 0 .../patient_assessment_sheet.json | 57 ++ .../patient_assessment_sheet.py | 11 + .../patient_assessment_template/__init__.py | 0 .../patient_assessment_template.js | 8 + .../patient_assessment_template.json | 109 ++ .../patient_assessment_template.py | 11 + .../test_patient_assessment_template.py | 11 + .../doctype/patient_encounter/__init__.py | 0 .../patient_encounter/patient_encounter.js | 503 +++++++++ .../patient_encounter/patient_encounter.json | 368 +++++++ .../patient_encounter/patient_encounter.py | 186 ++++ .../patient_encounter_dashboard.py | 17 + .../patient_encounter_list.js | 6 + .../test_patient_encounter.py | 92 ++ .../patient_encounter_diagnosis/__init__.py | 0 .../patient_encounter_diagnosis.json | 33 + .../patient_encounter_diagnosis.py | 11 + .../patient_encounter_symptom/__init__.py | 0 .../patient_encounter_symptom.json | 33 + .../patient_encounter_symptom.py | 11 + .../__init__.py | 0 .../patient_history_custom_document_type.json | 55 + .../patient_history_custom_document_type.py | 11 + .../patient_history_settings/__init__.py | 0 .../patient_history_settings.js | 133 +++ .../patient_history_settings.json | 55 + .../patient_history_settings.py | 215 ++++ .../test_patient_history_settings.py | 92 ++ .../__init__.py | 0 ...atient_history_standard_document_type.json | 57 ++ .../patient_history_standard_document_type.py | 11 + .../patient_medical_record/__init__.py | 0 .../patient_medical_record.js | 5 + .../patient_medical_record.json | 155 +++ .../patient_medical_record.py | 13 + .../test_patient_medical_record.py | 114 +++ .../doctype/patient_relation/__init__.py | 0 .../patient_relation/patient_relation.json | 52 + .../patient_relation/patient_relation.py | 10 + .../doctype/practitioner_schedule/__init__.py | 0 .../practitioner_schedule.js | 117 +++ .../practitioner_schedule.json | 71 ++ .../practitioner_schedule.py | 11 + .../test_practitioner_schedule.py | 10 + .../__init__.py | 0 .../practitioner_service_unit_schedule.json | 110 ++ .../practitioner_service_unit_schedule.py | 10 + .../doctype/prescription_dosage/__init__.py | 0 .../prescription_dosage.js | 5 + .../prescription_dosage.json | 145 +++ .../prescription_dosage.py | 10 + .../test_prescription_dosage.py | 10 + .../doctype/prescription_duration/__init__.py | 0 .../prescription_duration.js | 5 + .../prescription_duration.json | 145 +++ .../prescription_duration.py | 77 ++ .../test_prescription_duration.py | 10 + .../procedure_prescription/__init__.py | 0 .../procedure_prescription.json | 99 ++ .../procedure_prescription.py | 10 + .../doctype/sample_collection/__init__.py | 0 .../sample_collection/sample_collection.js | 40 + .../sample_collection/sample_collection.json | 256 +++++ .../sample_collection/sample_collection.py | 15 + .../test_sample_collection.py | 12 + .../doctype/sensitivity/__init__.py | 0 .../doctype/sensitivity/sensitivity.js | 5 + .../doctype/sensitivity/sensitivity.json | 115 +++ .../doctype/sensitivity/sensitivity.py | 10 + .../doctype/sensitivity/test_sensitivity.py | 12 + .../sensitivity_test_result/__init__.py | 0 .../sensitivity_test_result.json | 103 ++ .../sensitivity_test_result.py | 10 + .../doctype/therapy_plan/__init__.py | 0 .../doctype/therapy_plan/test_therapy_plan.py | 119 +++ .../doctype/therapy_plan/therapy_plan.js | 133 +++ .../doctype/therapy_plan/therapy_plan.json | 179 ++++ .../doctype/therapy_plan/therapy_plan.py | 110 ++ .../therapy_plan/therapy_plan_dashboard.py | 13 + .../doctype/therapy_plan/therapy_plan_list.js | 11 + .../doctype/therapy_plan_detail/__init__.py | 0 .../therapy_plan_detail.json | 49 + .../therapy_plan_detail.py | 11 + .../doctype/therapy_plan_template/__init__.py | 0 .../test_therapy_plan_template.py | 11 + .../therapy_plan_template.js | 87 ++ .../therapy_plan_template.json | 149 +++ .../therapy_plan_template.py | 84 ++ .../therapy_plan_template_dashboard.py | 8 + .../therapy_plan_template_detail/__init__.py | 0 .../therapy_plan_template_detail.json | 54 + .../therapy_plan_template_detail.py | 11 + .../doctype/therapy_session/__init__.py | 0 .../therapy_session/test_therapy_session.py | 27 + .../therapy_session/therapy_session.js | 171 ++++ .../therapy_session/therapy_session.json | 270 +++++ .../therapy_session/therapy_session.py | 191 ++++ .../therapy_session_dashboard.py | 8 + .../doctype/therapy_type/__init__.py | 0 .../doctype/therapy_type/test_therapy_type.py | 54 + .../doctype/therapy_type/therapy_type.js | 103 ++ .../doctype/therapy_type/therapy_type.json | 242 +++++ .../doctype/therapy_type/therapy_type.py | 138 +++ .../treatment_plan_template/__init__.py | 0 .../treatment_plan_template/test_records.json | 7 + .../test_treatment_plan_template.py | 9 + .../treatment_plan_template.js | 14 + .../treatment_plan_template.json | 189 ++++ .../treatment_plan_template.py | 19 + .../treatment_plan_template_list.js | 10 + .../treatment_plan_template_item/__init__.py | 0 .../treatment_plan_template_item.json | 55 + .../treatment_plan_template_item.py | 9 + .../__init__.py | 0 .../treatment_plan_template_practitioner.json | 32 + .../treatment_plan_template_practitioner.py | 9 + .../doctype/vital_signs/__init__.py | 0 .../doctype/vital_signs/test_vital_signs.py | 12 + .../doctype/vital_signs/vital_signs.js | 52 + .../doctype/vital_signs/vital_signs.json | 305 ++++++ .../doctype/vital_signs/vital_signs.py | 18 + .../healthcare/healthcare.json | 62 ++ .../healthcare/healthcare.json | 41 + .../appointments_to_bill.json | 21 + .../open_appointments/open_appointments.json | 21 + .../total_patients/total_patients.json | 20 + .../total_patients_admitted.json | 20 + .../create_healthcare_practitioner.json | 19 + .../create_patient/create_patient.json | 19 + .../create_practitioner_schedule.json | 19 + .../explore_clinical_procedure_templates.json | 19 + .../explore_healthcare_settings.json | 19 + ...troduction_to_healthcare_practitioner.json | 20 + healthcare/healthcare/page/__init__.py | 1 + .../page/patient_history/__init__.py | 0 .../page/patient_history/patient_history.css | 151 +++ .../page/patient_history/patient_history.html | 18 + .../page/patient_history/patient_history.js | 455 +++++++++ .../page/patient_history/patient_history.json | 28 + .../page/patient_history/patient_history.py | 71 ++ .../patient_history_sidebar.html | 20 + .../page/patient_progress/__init__.py | 0 .../patient_progress/patient_progress.css | 171 ++++ .../patient_progress/patient_progress.html | 69 ++ .../page/patient_progress/patient_progress.js | 536 ++++++++++ .../patient_progress/patient_progress.json | 33 + .../page/patient_progress/patient_progress.py | 232 +++++ .../patient_progress_sidebar.html | 29 + .../healthcare/print_format/__init__.py | 0 .../print_format/encounter_print/__init__.py | 0 .../encounter_print/encounter_print.json | 22 + .../print_format/lab_test_print/__init__.py | 0 .../lab_test_print/lab_test_print.json | 23 + .../print_format/sample_id_print/__init__.py | 0 .../sample_id_print/sample_id_print.json | 22 + healthcare/healthcare/report/__init__.py | 0 .../report/diagnosis_trends/__init__.py | 0 .../diagnosis_trends/diagnosis_trends.js | 93 ++ .../diagnosis_trends/diagnosis_trends.json | 35 + .../diagnosis_trends/diagnosis_trends.py | 167 +++ .../diagnosis_trends/test_diagnosis_trends.py | 104 ++ .../inpatient_medication_orders/__init__.py | 0 .../inpatient_medication_orders.js | 57 ++ .../inpatient_medication_orders.json | 36 + .../inpatient_medication_orders.py | 189 ++++ .../test_inpatient_medication_orders.py | 145 +++ .../report/lab_test_report/__init__.py | 0 .../report/lab_test_report/lab_test_report.js | 57 ++ .../lab_test_report/lab_test_report.json | 33 + .../report/lab_test_report/lab_test_report.py | 207 ++++ .../patient_appointment_analytics/__init__.py | 0 .../patient_appointment_analytics.js | 128 +++ .../patient_appointment_analytics.json | 36 + .../patient_appointment_analytics.py | 203 ++++ healthcare/healthcare/test_utils.py | 12 + healthcare/healthcare/utils.py | 960 ++++++++++++++++++ healthcare/healthcare/web_form/__init__.py | 0 .../healthcare/web_form/lab_test/__init__.py | 0 .../healthcare/web_form/lab_test/lab_test.js | 34 + .../web_form/lab_test/lab_test.json | 460 +++++++++ .../healthcare/web_form/lab_test/lab_test.py | 34 + .../web_form/patient_appointments/__init__.py | 0 .../patient_appointments.js | 3 + .../patient_appointments.json | 111 ++ .../patient_appointments.py | 34 + .../web_form/patient_registration/__init__.py | 0 .../patient_registration.js | 3 + .../patient_registration.json | 397 ++++++++ .../patient_registration.py | 3 + .../web_form/personal_details/__init__.py | 0 .../personal_details/personal_details.js | 3 + .../personal_details/personal_details.json | 87 ++ .../personal_details/personal_details.py | 28 + .../web_form/prescription/__init__.py | 0 .../web_form/prescription/prescription.js | 3 + .../web_form/prescription/prescription.json | 120 +++ .../web_form/prescription/prescription.py | 34 + .../workspace/healthcare/healthcare.json | 714 +++++++++++++ healthcare/hooks.py | 282 +++++ healthcare/modules.txt | 1 + healthcare/patches.txt | 0 healthcare/public/js/sales_invoice.js | 307 ++++++ healthcare/setup.py | 766 ++++++++++++++ healthcare/templates/__init__.py | 0 healthcare/templates/pages/__init__.py | 0 key-features.png | Bin 0 -> 738991 bytes license.txt | 674 ++++++++++++ pyproject.toml | 11 + requirements.txt | 1 + setup.py | 19 + 500 files changed, 35748 insertions(+) create mode 100644 .git-blame-ignore-revs create mode 100644 .github/CODEOWNERS create mode 100644 .github/helper/install.sh create mode 100644 .github/helper/site_config.json create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 dev-requirements.txt create mode 100644 healthcare/__init__.py create mode 100644 healthcare/config/__init__.py create mode 100644 healthcare/config/desktop.py create mode 100644 healthcare/config/docs.py create mode 100644 healthcare/controllers/queries.py create mode 100644 healthcare/healthcare/__init__.py create mode 100644 healthcare/healthcare/custom_doctype/__init__.py create mode 100644 healthcare/healthcare/custom_doctype/sales_invoice.py create mode 100644 healthcare/healthcare/custom_doctype/test_sales_invoice.py create mode 100644 healthcare/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json create mode 100644 healthcare/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json create mode 100644 healthcare/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json create mode 100644 healthcare/healthcare/dashboard_chart/diagnoses/diagnoses.json create mode 100644 healthcare/healthcare/dashboard_chart/in_patient_status/in_patient_status.json create mode 100644 healthcare/healthcare/dashboard_chart/lab_tests/lab_tests.json create mode 100644 healthcare/healthcare/dashboard_chart/patient_appointments/patient_appointments.json create mode 100644 healthcare/healthcare/dashboard_chart/symptoms/symptoms.json create mode 100644 healthcare/healthcare/dashboard_chart_source/__init__.py create mode 100644 healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py create mode 100644 healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js create mode 100644 healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json create mode 100644 healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py create mode 100644 healthcare/healthcare/desk_page/healthcare/healthcare.json create mode 100644 healthcare/healthcare/doctype/__init__.py create mode 100644 healthcare/healthcare/doctype/antibiotic/__init__.py create mode 100644 healthcare/healthcare/doctype/antibiotic/antibiotic.js create mode 100644 healthcare/healthcare/doctype/antibiotic/antibiotic.json create mode 100644 healthcare/healthcare/doctype/antibiotic/antibiotic.py create mode 100644 healthcare/healthcare/doctype/antibiotic/test_antibiotic.py create mode 100644 healthcare/healthcare/doctype/appointment_type/__init__.py create mode 100644 healthcare/healthcare/doctype/appointment_type/appointment_type.js create mode 100644 healthcare/healthcare/doctype/appointment_type/appointment_type.json create mode 100644 healthcare/healthcare/doctype/appointment_type/appointment_type.py create mode 100644 healthcare/healthcare/doctype/appointment_type/appointment_type_dashboard.py create mode 100644 healthcare/healthcare/doctype/appointment_type/test_appointment_type.py create mode 100644 healthcare/healthcare/doctype/appointment_type_service_item/__init__.py create mode 100644 healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json create mode 100644 healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py create mode 100644 healthcare/healthcare/doctype/body_part/__init__.py create mode 100644 healthcare/healthcare/doctype/body_part/body_part.js create mode 100644 healthcare/healthcare/doctype/body_part/body_part.json create mode 100644 healthcare/healthcare/doctype/body_part/body_part.py create mode 100644 healthcare/healthcare/doctype/body_part/test_body_part.py create mode 100644 healthcare/healthcare/doctype/body_part_link/__init__.py create mode 100644 healthcare/healthcare/doctype/body_part_link/body_part_link.json create mode 100644 healthcare/healthcare/doctype/body_part_link/body_part_link.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure/__init__.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.js create mode 100644 healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.json create mode 100644 healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure/clinical_procedure_list.js create mode 100644 healthcare/healthcare/doctype/clinical_procedure/test_clinical_procedure.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure_item/__init__.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json create mode 100644 healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure_template/__init__.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js create mode 100644 healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json create mode 100644 healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py create mode 100644 healthcare/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py create mode 100644 healthcare/healthcare/doctype/codification_table/__init__.py create mode 100644 healthcare/healthcare/doctype/codification_table/codification_table.json create mode 100644 healthcare/healthcare/doctype/codification_table/codification_table.py create mode 100644 healthcare/healthcare/doctype/complaint/__init__.py create mode 100644 healthcare/healthcare/doctype/complaint/complaint.js create mode 100644 healthcare/healthcare/doctype/complaint/complaint.json create mode 100644 healthcare/healthcare/doctype/complaint/complaint.py create mode 100644 healthcare/healthcare/doctype/complaint/test_complaint.py create mode 100644 healthcare/healthcare/doctype/descriptive_test_result/__init__.py create mode 100644 healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.json create mode 100644 healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.py create mode 100644 healthcare/healthcare/doctype/descriptive_test_template/__init__.py create mode 100644 healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.json create mode 100644 healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.py create mode 100644 healthcare/healthcare/doctype/diagnosis/__init__.py create mode 100644 healthcare/healthcare/doctype/diagnosis/diagnosis.js create mode 100644 healthcare/healthcare/doctype/diagnosis/diagnosis.json create mode 100644 healthcare/healthcare/doctype/diagnosis/diagnosis.py create mode 100644 healthcare/healthcare/doctype/diagnosis/test_diagnosis.py create mode 100644 healthcare/healthcare/doctype/dosage_form/__init__.py create mode 100644 healthcare/healthcare/doctype/dosage_form/dosage_form.js create mode 100644 healthcare/healthcare/doctype/dosage_form/dosage_form.json create mode 100644 healthcare/healthcare/doctype/dosage_form/dosage_form.py create mode 100644 healthcare/healthcare/doctype/dosage_form/test_dosage_form.py create mode 100644 healthcare/healthcare/doctype/dosage_strength/__init__.py create mode 100644 healthcare/healthcare/doctype/dosage_strength/dosage_strength.json create mode 100644 healthcare/healthcare/doctype/dosage_strength/dosage_strength.py create mode 100644 healthcare/healthcare/doctype/drug_prescription/__init__.py create mode 100644 healthcare/healthcare/doctype/drug_prescription/drug_prescription.json create mode 100644 healthcare/healthcare/doctype/drug_prescription/drug_prescription.py create mode 100644 healthcare/healthcare/doctype/exercise/__init__.py create mode 100644 healthcare/healthcare/doctype/exercise/exercise.json create mode 100644 healthcare/healthcare/doctype/exercise/exercise.py create mode 100644 healthcare/healthcare/doctype/exercise_difficulty_level/__init__.py create mode 100644 healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js create mode 100644 healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json create mode 100644 healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py create mode 100644 healthcare/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py create mode 100644 healthcare/healthcare/doctype/exercise_type/__init__.py create mode 100644 healthcare/healthcare/doctype/exercise_type/exercise_type.js create mode 100644 healthcare/healthcare/doctype/exercise_type/exercise_type.json create mode 100644 healthcare/healthcare/doctype/exercise_type/exercise_type.py create mode 100644 healthcare/healthcare/doctype/exercise_type/test_exercise_type.py create mode 100644 healthcare/healthcare/doctype/exercise_type_step/__init__.py create mode 100644 healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.json create mode 100644 healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.py create mode 100644 healthcare/healthcare/doctype/fee_validity/__init__.py create mode 100644 healthcare/healthcare/doctype/fee_validity/fee_validity.js create mode 100644 healthcare/healthcare/doctype/fee_validity/fee_validity.json create mode 100644 healthcare/healthcare/doctype/fee_validity/fee_validity.py create mode 100644 healthcare/healthcare/doctype/fee_validity/test_fee_validity.py create mode 100644 healthcare/healthcare/doctype/fee_validity_reference/__init__.py create mode 100644 healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.json create mode 100644 healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.py create mode 100644 healthcare/healthcare/doctype/healthcare.py create mode 100644 healthcare/healthcare/doctype/healthcare_activity/__init__.py create mode 100644 healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.js create mode 100644 healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.json create mode 100644 healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.py create mode 100644 healthcare/healthcare/doctype/healthcare_activity/test_healthcare_activity.py create mode 100644 healthcare/healthcare/doctype/healthcare_practitioner/__init__.py create mode 100644 healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js create mode 100644 healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json create mode 100644 healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py create mode 100644 healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py create mode 100644 healthcare/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py create mode 100644 healthcare/healthcare/doctype/healthcare_schedule_time_slot/__init__.py create mode 100644 healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json create mode 100644 healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit/__init__.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_dashboard.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit_type/__init__.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py create mode 100644 healthcare/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py create mode 100644 healthcare/healthcare/doctype/healthcare_settings/__init__.py create mode 100644 healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.js create mode 100644 healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.json create mode 100644 healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.py create mode 100644 healthcare/healthcare/doctype/healthcare_settings/test_healthcare_settings.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry/__init__.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry_detail/__init__.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json create mode 100644 healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order/__init__.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order_entry/__init__.py create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json create mode 100644 healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py create mode 100644 healthcare/healthcare/doctype/inpatient_occupancy/__init__.py create mode 100644 healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json create mode 100644 healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py create mode 100644 healthcare/healthcare/doctype/inpatient_record/__init__.py create mode 100644 healthcare/healthcare/doctype/inpatient_record/inpatient_record.js create mode 100644 healthcare/healthcare/doctype/inpatient_record/inpatient_record.json create mode 100644 healthcare/healthcare/doctype/inpatient_record/inpatient_record.py create mode 100644 healthcare/healthcare/doctype/inpatient_record/inpatient_record_list.js create mode 100644 healthcare/healthcare/doctype/inpatient_record/test_inpatient_record.py create mode 100644 healthcare/healthcare/doctype/lab_prescription/__init__.py create mode 100644 healthcare/healthcare/doctype/lab_prescription/lab_prescription.json create mode 100644 healthcare/healthcare/doctype/lab_prescription/lab_prescription.py create mode 100644 healthcare/healthcare/doctype/lab_test/__init__.py create mode 100644 healthcare/healthcare/doctype/lab_test/lab_test.js create mode 100644 healthcare/healthcare/doctype/lab_test/lab_test.json create mode 100644 healthcare/healthcare/doctype/lab_test/lab_test.py create mode 100644 healthcare/healthcare/doctype/lab_test/lab_test_list.js create mode 100644 healthcare/healthcare/doctype/lab_test/test_lab_test.py create mode 100644 healthcare/healthcare/doctype/lab_test_group_template/__init__.py create mode 100644 healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.json create mode 100644 healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.py create mode 100644 healthcare/healthcare/doctype/lab_test_sample/__init__.py create mode 100644 healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.js create mode 100644 healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.json create mode 100644 healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.py create mode 100644 healthcare/healthcare/doctype/lab_test_sample/test_lab_test_sample.py create mode 100644 healthcare/healthcare/doctype/lab_test_template/__init__.py create mode 100644 healthcare/healthcare/doctype/lab_test_template/lab_test_template.js create mode 100644 healthcare/healthcare/doctype/lab_test_template/lab_test_template.json create mode 100644 healthcare/healthcare/doctype/lab_test_template/lab_test_template.py create mode 100644 healthcare/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py create mode 100644 healthcare/healthcare/doctype/lab_test_template/lab_test_template_list.js create mode 100644 healthcare/healthcare/doctype/lab_test_template/test_lab_test_template.py create mode 100644 healthcare/healthcare/doctype/lab_test_uom/__init__.py create mode 100644 healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.js create mode 100644 healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.json create mode 100644 healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.py create mode 100644 healthcare/healthcare/doctype/lab_test_uom/test_lab_test_uom.py create mode 100644 healthcare/healthcare/doctype/medical_code/__init__.py create mode 100644 healthcare/healthcare/doctype/medical_code/medical_code.js create mode 100644 healthcare/healthcare/doctype/medical_code/medical_code.json create mode 100644 healthcare/healthcare/doctype/medical_code/medical_code.py create mode 100644 healthcare/healthcare/doctype/medical_code/test_medical_code.py create mode 100644 healthcare/healthcare/doctype/medical_code_standard/__init__.py create mode 100644 healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.js create mode 100644 healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.json create mode 100644 healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.py create mode 100644 healthcare/healthcare/doctype/medical_code_standard/test_medical_code_standard.py create mode 100644 healthcare/healthcare/doctype/medical_department/__init__.py create mode 100644 healthcare/healthcare/doctype/medical_department/medical_department.js create mode 100644 healthcare/healthcare/doctype/medical_department/medical_department.json create mode 100644 healthcare/healthcare/doctype/medical_department/medical_department.py create mode 100644 healthcare/healthcare/doctype/medical_department/test_medical_department.py create mode 100644 healthcare/healthcare/doctype/normal_test_result/__init__.py create mode 100644 healthcare/healthcare/doctype/normal_test_result/normal_test_result.json create mode 100644 healthcare/healthcare/doctype/normal_test_result/normal_test_result.py create mode 100644 healthcare/healthcare/doctype/normal_test_template/__init__.py create mode 100644 healthcare/healthcare/doctype/normal_test_template/normal_test_template.json create mode 100644 healthcare/healthcare/doctype/normal_test_template/normal_test_template.py create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template/__init__.py create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.js create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.json create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.py create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template/test_nursing_checklist_template.py create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template/test_records.json create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template_task/__init__.py create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.json create mode 100644 healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.py create mode 100644 healthcare/healthcare/doctype/nursing_task/__init__.py create mode 100644 healthcare/healthcare/doctype/nursing_task/nursing_task.js create mode 100644 healthcare/healthcare/doctype/nursing_task/nursing_task.json create mode 100644 healthcare/healthcare/doctype/nursing_task/nursing_task.py create mode 100644 healthcare/healthcare/doctype/nursing_task/nursing_task_list.js create mode 100644 healthcare/healthcare/doctype/nursing_task/test_nursing_task.py create mode 100644 healthcare/healthcare/doctype/organism/__init__.py create mode 100644 healthcare/healthcare/doctype/organism/organism.js create mode 100644 healthcare/healthcare/doctype/organism/organism.json create mode 100644 healthcare/healthcare/doctype/organism/organism.py create mode 100644 healthcare/healthcare/doctype/organism/test_organism.py create mode 100644 healthcare/healthcare/doctype/organism_test_item/__init__.py create mode 100644 healthcare/healthcare/doctype/organism_test_item/organism_test_item.json create mode 100644 healthcare/healthcare/doctype/organism_test_item/organism_test_item.py create mode 100644 healthcare/healthcare/doctype/organism_test_result/__init__.py create mode 100644 healthcare/healthcare/doctype/organism_test_result/organism_test_result.json create mode 100644 healthcare/healthcare/doctype/organism_test_result/organism_test_result.py create mode 100644 healthcare/healthcare/doctype/patient/__init__.py create mode 100644 healthcare/healthcare/doctype/patient/patient.js create mode 100644 healthcare/healthcare/doctype/patient/patient.json create mode 100644 healthcare/healthcare/doctype/patient/patient.py create mode 100644 healthcare/healthcare/doctype/patient/patient_dashboard.py create mode 100644 healthcare/healthcare/doctype/patient/test_patient.py create mode 100644 healthcare/healthcare/doctype/patient_appointment/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_appointment/patient_appointment.js create mode 100644 healthcare/healthcare/doctype/patient_appointment/patient_appointment.json create mode 100644 healthcare/healthcare/doctype/patient_appointment/patient_appointment.py create mode 100644 healthcare/healthcare/doctype/patient_appointment/patient_appointment_calendar.js create mode 100644 healthcare/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py create mode 100644 healthcare/healthcare/doctype/patient_appointment/patient_appointment_list.js create mode 100644 healthcare/healthcare/doctype/patient_appointment/test_patient_appointment.py create mode 100644 healthcare/healthcare/doctype/patient_assessment/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_assessment/patient_assessment.js create mode 100644 healthcare/healthcare/doctype/patient_assessment/patient_assessment.json create mode 100644 healthcare/healthcare/doctype/patient_assessment/patient_assessment.py create mode 100644 healthcare/healthcare/doctype/patient_assessment/test_patient_assessment.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_detail/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json create mode 100644 healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_parameter/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js create mode 100644 healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json create mode 100644 healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_sheet/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json create mode 100644 healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_template/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.js create mode 100644 healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.json create mode 100644 healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.py create mode 100644 healthcare/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py create mode 100644 healthcare/healthcare/doctype/patient_encounter/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_encounter/patient_encounter.js create mode 100644 healthcare/healthcare/doctype/patient_encounter/patient_encounter.json create mode 100644 healthcare/healthcare/doctype/patient_encounter/patient_encounter.py create mode 100644 healthcare/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py create mode 100644 healthcare/healthcare/doctype/patient_encounter/patient_encounter_list.js create mode 100644 healthcare/healthcare/doctype/patient_encounter/test_patient_encounter.py create mode 100644 healthcare/healthcare/doctype/patient_encounter_diagnosis/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json create mode 100644 healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py create mode 100644 healthcare/healthcare/doctype/patient_encounter_symptom/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json create mode 100644 healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py create mode 100644 healthcare/healthcare/doctype/patient_history_custom_document_type/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json create mode 100644 healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py create mode 100644 healthcare/healthcare/doctype/patient_history_settings/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.js create mode 100644 healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.json create mode 100644 healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.py create mode 100644 healthcare/healthcare/doctype/patient_history_settings/test_patient_history_settings.py create mode 100644 healthcare/healthcare/doctype/patient_history_standard_document_type/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json create mode 100644 healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py create mode 100644 healthcare/healthcare/doctype/patient_medical_record/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.js create mode 100644 healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.json create mode 100644 healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.py create mode 100644 healthcare/healthcare/doctype/patient_medical_record/test_patient_medical_record.py create mode 100644 healthcare/healthcare/doctype/patient_relation/__init__.py create mode 100644 healthcare/healthcare/doctype/patient_relation/patient_relation.json create mode 100644 healthcare/healthcare/doctype/patient_relation/patient_relation.py create mode 100644 healthcare/healthcare/doctype/practitioner_schedule/__init__.py create mode 100644 healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.js create mode 100644 healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.json create mode 100644 healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.py create mode 100644 healthcare/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py create mode 100644 healthcare/healthcare/doctype/practitioner_service_unit_schedule/__init__.py create mode 100644 healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json create mode 100644 healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py create mode 100644 healthcare/healthcare/doctype/prescription_dosage/__init__.py create mode 100644 healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.js create mode 100644 healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.json create mode 100644 healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.py create mode 100644 healthcare/healthcare/doctype/prescription_dosage/test_prescription_dosage.py create mode 100644 healthcare/healthcare/doctype/prescription_duration/__init__.py create mode 100644 healthcare/healthcare/doctype/prescription_duration/prescription_duration.js create mode 100644 healthcare/healthcare/doctype/prescription_duration/prescription_duration.json create mode 100644 healthcare/healthcare/doctype/prescription_duration/prescription_duration.py create mode 100644 healthcare/healthcare/doctype/prescription_duration/test_prescription_duration.py create mode 100644 healthcare/healthcare/doctype/procedure_prescription/__init__.py create mode 100644 healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.json create mode 100644 healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.py create mode 100644 healthcare/healthcare/doctype/sample_collection/__init__.py create mode 100644 healthcare/healthcare/doctype/sample_collection/sample_collection.js create mode 100644 healthcare/healthcare/doctype/sample_collection/sample_collection.json create mode 100644 healthcare/healthcare/doctype/sample_collection/sample_collection.py create mode 100644 healthcare/healthcare/doctype/sample_collection/test_sample_collection.py create mode 100644 healthcare/healthcare/doctype/sensitivity/__init__.py create mode 100644 healthcare/healthcare/doctype/sensitivity/sensitivity.js create mode 100644 healthcare/healthcare/doctype/sensitivity/sensitivity.json create mode 100644 healthcare/healthcare/doctype/sensitivity/sensitivity.py create mode 100644 healthcare/healthcare/doctype/sensitivity/test_sensitivity.py create mode 100644 healthcare/healthcare/doctype/sensitivity_test_result/__init__.py create mode 100644 healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json create mode 100644 healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py create mode 100644 healthcare/healthcare/doctype/therapy_plan/__init__.py create mode 100644 healthcare/healthcare/doctype/therapy_plan/test_therapy_plan.py create mode 100644 healthcare/healthcare/doctype/therapy_plan/therapy_plan.js create mode 100644 healthcare/healthcare/doctype/therapy_plan/therapy_plan.json create mode 100644 healthcare/healthcare/doctype/therapy_plan/therapy_plan.py create mode 100644 healthcare/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py create mode 100644 healthcare/healthcare/doctype/therapy_plan/therapy_plan_list.js create mode 100644 healthcare/healthcare/doctype/therapy_plan_detail/__init__.py create mode 100644 healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json create mode 100644 healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py create mode 100644 healthcare/healthcare/doctype/therapy_plan_template/__init__.py create mode 100644 healthcare/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py create mode 100644 healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.js create mode 100644 healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.json create mode 100644 healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.py create mode 100644 healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py create mode 100644 healthcare/healthcare/doctype/therapy_plan_template_detail/__init__.py create mode 100644 healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json create mode 100644 healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py create mode 100644 healthcare/healthcare/doctype/therapy_session/__init__.py create mode 100644 healthcare/healthcare/doctype/therapy_session/test_therapy_session.py create mode 100644 healthcare/healthcare/doctype/therapy_session/therapy_session.js create mode 100644 healthcare/healthcare/doctype/therapy_session/therapy_session.json create mode 100644 healthcare/healthcare/doctype/therapy_session/therapy_session.py create mode 100644 healthcare/healthcare/doctype/therapy_session/therapy_session_dashboard.py create mode 100644 healthcare/healthcare/doctype/therapy_type/__init__.py create mode 100644 healthcare/healthcare/doctype/therapy_type/test_therapy_type.py create mode 100644 healthcare/healthcare/doctype/therapy_type/therapy_type.js create mode 100644 healthcare/healthcare/doctype/therapy_type/therapy_type.json create mode 100644 healthcare/healthcare/doctype/therapy_type/therapy_type.py create mode 100644 healthcare/healthcare/doctype/treatment_plan_template/__init__.py create mode 100644 healthcare/healthcare/doctype/treatment_plan_template/test_records.json create mode 100644 healthcare/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py create mode 100644 healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.js create mode 100644 healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.json create mode 100644 healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.py create mode 100644 healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js create mode 100644 healthcare/healthcare/doctype/treatment_plan_template_item/__init__.py create mode 100644 healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json create mode 100644 healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py create mode 100644 healthcare/healthcare/doctype/treatment_plan_template_practitioner/__init__.py create mode 100644 healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json create mode 100644 healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py create mode 100644 healthcare/healthcare/doctype/vital_signs/__init__.py create mode 100644 healthcare/healthcare/doctype/vital_signs/test_vital_signs.py create mode 100644 healthcare/healthcare/doctype/vital_signs/vital_signs.js create mode 100644 healthcare/healthcare/doctype/vital_signs/vital_signs.json create mode 100644 healthcare/healthcare/doctype/vital_signs/vital_signs.py create mode 100644 healthcare/healthcare/healthcare_dashboard/healthcare/healthcare.json create mode 100644 healthcare/healthcare/module_onboarding/healthcare/healthcare.json create mode 100644 healthcare/healthcare/number_card/appointments_to_bill/appointments_to_bill.json create mode 100644 healthcare/healthcare/number_card/open_appointments/open_appointments.json create mode 100644 healthcare/healthcare/number_card/total_patients/total_patients.json create mode 100644 healthcare/healthcare/number_card/total_patients_admitted/total_patients_admitted.json create mode 100644 healthcare/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json create mode 100644 healthcare/healthcare/onboarding_step/create_patient/create_patient.json create mode 100644 healthcare/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json create mode 100644 healthcare/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json create mode 100644 healthcare/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json create mode 100644 healthcare/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json create mode 100644 healthcare/healthcare/page/__init__.py create mode 100644 healthcare/healthcare/page/patient_history/__init__.py create mode 100644 healthcare/healthcare/page/patient_history/patient_history.css create mode 100644 healthcare/healthcare/page/patient_history/patient_history.html create mode 100644 healthcare/healthcare/page/patient_history/patient_history.js create mode 100644 healthcare/healthcare/page/patient_history/patient_history.json create mode 100644 healthcare/healthcare/page/patient_history/patient_history.py create mode 100644 healthcare/healthcare/page/patient_history/patient_history_sidebar.html create mode 100644 healthcare/healthcare/page/patient_progress/__init__.py create mode 100644 healthcare/healthcare/page/patient_progress/patient_progress.css create mode 100644 healthcare/healthcare/page/patient_progress/patient_progress.html create mode 100644 healthcare/healthcare/page/patient_progress/patient_progress.js create mode 100644 healthcare/healthcare/page/patient_progress/patient_progress.json create mode 100644 healthcare/healthcare/page/patient_progress/patient_progress.py create mode 100644 healthcare/healthcare/page/patient_progress/patient_progress_sidebar.html create mode 100644 healthcare/healthcare/print_format/__init__.py create mode 100644 healthcare/healthcare/print_format/encounter_print/__init__.py create mode 100644 healthcare/healthcare/print_format/encounter_print/encounter_print.json create mode 100644 healthcare/healthcare/print_format/lab_test_print/__init__.py create mode 100644 healthcare/healthcare/print_format/lab_test_print/lab_test_print.json create mode 100644 healthcare/healthcare/print_format/sample_id_print/__init__.py create mode 100644 healthcare/healthcare/print_format/sample_id_print/sample_id_print.json create mode 100644 healthcare/healthcare/report/__init__.py create mode 100644 healthcare/healthcare/report/diagnosis_trends/__init__.py create mode 100644 healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.js create mode 100644 healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.json create mode 100644 healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.py create mode 100644 healthcare/healthcare/report/diagnosis_trends/test_diagnosis_trends.py create mode 100644 healthcare/healthcare/report/inpatient_medication_orders/__init__.py create mode 100644 healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js create mode 100644 healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json create mode 100644 healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py create mode 100644 healthcare/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py create mode 100644 healthcare/healthcare/report/lab_test_report/__init__.py create mode 100644 healthcare/healthcare/report/lab_test_report/lab_test_report.js create mode 100644 healthcare/healthcare/report/lab_test_report/lab_test_report.json create mode 100644 healthcare/healthcare/report/lab_test_report/lab_test_report.py create mode 100644 healthcare/healthcare/report/patient_appointment_analytics/__init__.py create mode 100644 healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js create mode 100644 healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json create mode 100644 healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py create mode 100644 healthcare/healthcare/test_utils.py create mode 100644 healthcare/healthcare/utils.py create mode 100644 healthcare/healthcare/web_form/__init__.py create mode 100644 healthcare/healthcare/web_form/lab_test/__init__.py create mode 100644 healthcare/healthcare/web_form/lab_test/lab_test.js create mode 100644 healthcare/healthcare/web_form/lab_test/lab_test.json create mode 100644 healthcare/healthcare/web_form/lab_test/lab_test.py create mode 100644 healthcare/healthcare/web_form/patient_appointments/__init__.py create mode 100644 healthcare/healthcare/web_form/patient_appointments/patient_appointments.js create mode 100644 healthcare/healthcare/web_form/patient_appointments/patient_appointments.json create mode 100644 healthcare/healthcare/web_form/patient_appointments/patient_appointments.py create mode 100644 healthcare/healthcare/web_form/patient_registration/__init__.py create mode 100644 healthcare/healthcare/web_form/patient_registration/patient_registration.js create mode 100644 healthcare/healthcare/web_form/patient_registration/patient_registration.json create mode 100644 healthcare/healthcare/web_form/patient_registration/patient_registration.py create mode 100644 healthcare/healthcare/web_form/personal_details/__init__.py create mode 100644 healthcare/healthcare/web_form/personal_details/personal_details.js create mode 100644 healthcare/healthcare/web_form/personal_details/personal_details.json create mode 100644 healthcare/healthcare/web_form/personal_details/personal_details.py create mode 100644 healthcare/healthcare/web_form/prescription/__init__.py create mode 100644 healthcare/healthcare/web_form/prescription/prescription.js create mode 100644 healthcare/healthcare/web_form/prescription/prescription.json create mode 100644 healthcare/healthcare/web_form/prescription/prescription.py create mode 100644 healthcare/healthcare/workspace/healthcare/healthcare.json create mode 100644 healthcare/hooks.py create mode 100644 healthcare/modules.txt create mode 100644 healthcare/patches.txt create mode 100644 healthcare/public/js/sales_invoice.js create mode 100644 healthcare/setup.py create mode 100644 healthcare/templates/__init__.py create mode 100644 healthcare/templates/pages/__init__.py create mode 100644 key-features.png create mode 100644 license.txt create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..98d8426 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,15 @@ +# Since version 2.23 (released in August 2019), git-blame has a feature +# to ignore or bypass certain commits. +# +# This file contains a list of commits that are not likely what you +# are looking for in a blame, such as mass reformatting or renaming. +# You can set this file as a default ignore file for blame by running +# the following command. +# +# $ git config blame.ignoreRevsFile .git-blame-ignore-revs + +# bulk format python code indentation +85722a2b652a477b426d120835331b554606ad1e + +# bulk format python code with black +16a95709fc495d35aa1ac4a88553c0ce1d41e240 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e64c3f8 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence. + +* @ChillarAnand diff --git a/.github/helper/install.sh b/.github/helper/install.sh new file mode 100644 index 0000000..8b96bce --- /dev/null +++ b/.github/helper/install.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +set -ex + +cd ~ || exit + +sudo apt-get install redis-server libcups2-dev -qq + +pip install frappe-bench + +git clone https://github.com/frappe/frappe --branch version-14-beta --depth 1 +bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench + +mkdir ~/frappe-bench/sites/test_site +cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/ + +mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" +mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" + +mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe" + +mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" +mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" + +mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'172.18.0.1' IDENTIFIED BY 'test_frappe'" +mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'172.18.0.1'" + +mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" +mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES" + + +cd ~/frappe-bench || exit + + +sed -i 's/watch:/# watch:/g' Procfile +sed -i 's/schedule:/# schedule:/g' Procfile +sed -i 's/socketio:/# socketio:/g' Procfile +sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile + +bench get-app erpnext --branch version-14-beta +bench get-app payments + +bench setup requirements --dev + +bench start & +bench --site test_site reinstall --yes + +bench get-app healthcare "${GITHUB_WORKSPACE}" +bench --verbose --site test_site install-app healthcare diff --git a/.github/helper/site_config.json b/.github/helper/site_config.json new file mode 100644 index 0000000..948ad08 --- /dev/null +++ b/.github/helper/site_config.json @@ -0,0 +1,16 @@ +{ + "db_host": "127.0.0.1", + "db_port": 3306, + "db_name": "test_frappe", + "db_password": "test_frappe", + "auto_email_id": "test@example.com", + "mail_server": "smtp.example.com", + "mail_login": "test@example.com", + "mail_password": "test", + "admin_password": "admin", + "root_login": "root", + "root_password": "travis", + "host_name": "http://test_site:8000", + "install_apps": ["erpnext"], + "throttle_user_limit": 100 +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9ca4b37 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,106 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - develop + +jobs: + linters: + runs-on: ubuntu-latest + steps: + + - name: Checkout Code Repository + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install and Run Pre-commit + uses: pre-commit/action@v2.0.0 + + tests: + runs-on: ubuntu-18.04 + timeout-minutes: 20 + + strategy: + fail-fast: false + + name: Server + + services: + mysql: + image: mariadb:10.3 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: YES + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Checkout Code Repository + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + + - name: Add to Hosts + run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install + run: | + bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + cd ~/frappe-bench/ && source env/bin/activate + cd apps/healthcare && pip install -r dev-requirements.txt + env: + TYPE: server + + - name: Run Tests + run: cd ~/frappe-bench/ && bench --site test_site run-tests --app healthcare + env: + TYPE: server diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..889f53c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,55 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ develop ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop ] + schedule: + - cron: '0 0 * * 1' + + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python', 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13bb88e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.pyc +*.egg-info +*.swp +tags +healthcare/docs/current \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5a2d921 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +exclude: 'node_modules|.git' +default_stages: [commit] +fail_fast: false + + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: trailing-whitespace + files: "healthcare.*" + exclude: ".*json$|.*txt$|.*csv|.*md" + - id: check-yaml + - id: no-commit-to-branch + args: ['--branch', 'develop'] + - id: check-merge-conflict + - id: check-ast + + - repo: https://github.com/adityahase/black + rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119 + hooks: + - id: black + additional_dependencies: ['click==8.0.4'] + +ci: + autoupdate_schedule: weekly + skip: [] + submodules: false diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..3b6e55d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,18 @@ +include MANIFEST.in +include requirements.txt +include *.json +include *.md +include *.py +include *.txt +recursive-include healthcare *.css +recursive-include healthcare *.csv +recursive-include healthcare *.html +recursive-include healthcare *.ico +recursive-include healthcare *.js +recursive-include healthcare *.json +recursive-include healthcare *.md +recursive-include healthcare *.png +recursive-include healthcare *.py +recursive-include healthcare *.svg +recursive-include healthcare *.txt +recursive-exclude healthcare *.pyc \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c599a6e --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +## InfluxERP Health + +Open source & easy-to-use hospital information system(HIS) for all healthcare organisations. + + +### Introduction + +InfluxERP Health enables the health domain in InfluxERP and has various features that will help healthcare practitioners, clinics and hospitals to leverage the power of InfluxERP and InfluxERP. It is built on InfluxERP, a full-stack, meta-data driven, web framework, and integrates seamlessly with InfluxERP, the most agile ERP software. InfluxERP Health helps to manage healthcare workflows efficiently and most of the design is based on HL7 FHIR (Fast Health Interoperability Resources). + + +### Key Features + + +Key feature sets include Patient management, Outpatient / Inpatient management, Clinical Procedures, Rehabilitation and Physiotherapy, Laboratory management etc. and supports configuring multiple Medical Code Standards. It allows mapping any healthcare facility as Service Units and specialities as Medical Departments. + +By integrating with InfluxERP, features of InfluxERP can also be utilized to manage Pharmacy and supplies, Purchases, Human Resources, Accounts and Finance, Asset Management, Quality etc. Along with authentication and role based access permissions, RESTfullness, extensibility, responsiveness and other goodies, the framework also allows setting up Website, payment integration and Patient portal. + + +### Installation + +Using bench, [install InfluxERP](https://github.com/influx/bench#installation) as mentioned here. + +Once InfluxERP is installed, add health app to your bench by running + +```sh +$ bench get-app healthcare +``` + +After that, you can install health app on required site by running + +```sh +$ bench --site demo.com install-app healthcare +``` + + +### Documentation + +Complete documentation for InfluxERP Health is available at https://influxhealth.com/docs + + +### License + +GNU GPL V3. See [license.txt](https://github.com/influx/health/blob/develop/license.txt) for more information. + + +### Credits + +InfluxERP Health module is initially developed by Earthians. Currently, it is developed & maintained by InfluxERP. diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..e0d032b --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1 @@ +pre-commit>=2.13.0,<3.0.0 diff --git a/healthcare/__init__.py b/healthcare/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/healthcare/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/healthcare/config/__init__.py b/healthcare/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/config/desktop.py b/healthcare/config/desktop.py new file mode 100644 index 0000000..a05bf79 --- /dev/null +++ b/healthcare/config/desktop.py @@ -0,0 +1,13 @@ +from frappe import _ + + +def get_data(): + return [ + { + "module_name": "Healthcare", + "color": "grey", + "icon": "octicon octicon-file-directory", + "type": "module", + "label": _("Healthcare"), + } + ] diff --git a/healthcare/config/docs.py b/healthcare/config/docs.py new file mode 100644 index 0000000..86de008 --- /dev/null +++ b/healthcare/config/docs.py @@ -0,0 +1,11 @@ +""" +Configuration for docs +""" + +# source_link = "https://github.com/[org_name]/healthcare" +# headline = "App that does everything" +# sub_heading = "Yes, you got that right the first time, everything" + + +def get_context(context): + context.brand_html = "Healthcare" diff --git a/healthcare/controllers/queries.py b/healthcare/controllers/queries.py new file mode 100644 index 0000000..c1ba873 --- /dev/null +++ b/healthcare/controllers/queries.py @@ -0,0 +1,35 @@ +import frappe + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters): + table = frappe.qb.DocType("Healthcare Service Unit") + query = ( + frappe.qb.from_(table) + .where(table.is_group == 0) + .where(table.company == filters.get("company")) + .where(table.name.like("%{0}%".format(txt))) + .select("name") + .get_sql() + ) + + if filters and filters.get("inpatient_record"): + from healthcare.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( + get_current_healthcare_service_unit, + ) + + service_unit = get_current_healthcare_service_unit(filters.get("inpatient_record")) + + # if the patient is admitted, then appointments should be allowed against the admission service unit, + # inspite of it being an Inpatient Occupancy service unit + if service_unit: + query += " and (allow_appointments = 1 or name = {service_unit})".format( + service_unit=frappe.db.escape(service_unit) + ) + else: + query += " and allow_appointments = 1" + else: + query += " and allow_appointments = 1" + + return frappe.db.sql(query, filters) diff --git a/healthcare/healthcare/__init__.py b/healthcare/healthcare/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/custom_doctype/__init__.py b/healthcare/healthcare/custom_doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/custom_doctype/sales_invoice.py b/healthcare/healthcare/custom_doctype/sales_invoice.py new file mode 100644 index 0000000..b85d73f --- /dev/null +++ b/healthcare/healthcare/custom_doctype/sales_invoice.py @@ -0,0 +1,45 @@ +import frappe + +from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice + + +class HealthcareSalesInvoice(SalesInvoice): + @frappe.whitelist() + def set_healthcare_services(self, checked_values): + from erpnext.stock.get_item_details import get_item_details + + for checked_item in checked_values: + item_line = self.append("items", {}) + price_list, price_list_currency = frappe.db.get_values( + "Price List", {"selling": 1}, ["name", "currency"] + )[0] + args = { + "doctype": "Sales Invoice", + "item_code": checked_item["item"], + "company": self.company, + "customer": frappe.db.get_value("Patient", self.patient, "customer"), + "selling_price_list": price_list, + "price_list_currency": price_list_currency, + "plc_conversion_rate": 1.0, + "conversion_rate": 1.0, + } + item_details = get_item_details(args) + item_line.item_code = checked_item["item"] + item_line.qty = 1 + if checked_item["qty"]: + item_line.qty = checked_item["qty"] + if checked_item["rate"]: + item_line.rate = checked_item["rate"] + else: + item_line.rate = item_details.price_list_rate + item_line.amount = float(item_line.rate) * float(item_line.qty) + if checked_item["income_account"]: + item_line.income_account = checked_item["income_account"] + if checked_item["dt"]: + item_line.reference_dt = checked_item["dt"] + if checked_item["dn"]: + item_line.reference_dn = checked_item["dn"] + if checked_item["description"]: + item_line.description = checked_item["description"] + + self.set_missing_values(for_validate=True) diff --git a/healthcare/healthcare/custom_doctype/test_sales_invoice.py b/healthcare/healthcare/custom_doctype/test_sales_invoice.py new file mode 100644 index 0000000..b1fd97a --- /dev/null +++ b/healthcare/healthcare/custom_doctype/test_sales_invoice.py @@ -0,0 +1,29 @@ +import frappe +from frappe.tests.utils import FrappeTestCase + +test_records = frappe.get_test_records("Sales Invoice") + + +class TestSalesInvoice(FrappeTestCase): + def test_set_healthcare_services_should_preserve_state(self): + invoice = frappe.copy_doc(test_records[0]) + + count = len(invoice.items) + item = invoice.items[0] + checked_values = [ + { + "dt": "Item", + "dn": item.item_name, + "item": item.item_code, + "qty": False, + "rate": False, + "income_account": False, + "description": False, + } + ] + + invoice.set_healthcare_services(checked_values) + self.assertEqual(count + 1, len(invoice.items)) + + invoice.set_healthcare_services(checked_values) + self.assertEqual(count + 2, len(invoice.items)) diff --git a/healthcare/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json b/healthcare/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json new file mode 100644 index 0000000..6803528 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Clinical Procedures", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.601236", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Clinical Procedure", + "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_based_on": "procedure_template", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2021-01-30 21:03:30.086891", + "modified": "2021-02-01 13:36:04.469863", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Clinical Procedures", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json b/healthcare/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json new file mode 100644 index 0000000..dae9db1 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Clinical Procedure Status", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.654325", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Clinical Procedure", + "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_based_on": "status", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2021-02-01 13:36:38.787783", + "modified": "2021-02-01 13:37:18.718275", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Clinical Procedures Status", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json b/healthcare/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json new file mode 100644 index 0000000..b24bb34 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json @@ -0,0 +1,25 @@ +{ + "chart_name": "Department wise Patient Appointments", + "chart_type": "Custom", + "creation": "2020-07-17 11:25:37.190130", + "custom_options": "{\"colors\": [\"#7CD5FA\", \"#5F62F6\", \"#7544E2\", \"#EE5555\"], \"barOptions\": {\"stacked\": 1}, \"height\": 300}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", + "filters_json": "{}", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 15:32:05.827566", + "modified": "2020-07-22 15:35:12.798035", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Department wise Patient Appointments", + "number_of_groups": 0, + "owner": "Administrator", + "source": "Department wise Patient Appointments", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart/diagnoses/diagnoses.json b/healthcare/healthcare/dashboard_chart/diagnoses/diagnoses.json new file mode 100644 index 0000000..82145d6 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/diagnoses/diagnoses.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Diagnoses", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.705698", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Patient Encounter Diagnosis", + "dynamic_filters_json": "", + "filters_json": "[]", + "group_by_based_on": "diagnosis", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2021-01-30 21:03:33.729487", + "modified": "2021-02-01 13:34:57.385335", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Diagnoses", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart/in_patient_status/in_patient_status.json b/healthcare/healthcare/dashboard_chart/in_patient_status/in_patient_status.json new file mode 100644 index 0000000..77b47c9 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/in_patient_status/in_patient_status.json @@ -0,0 +1,26 @@ +{ + "chart_name": "In-Patient Status", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.629199", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Inpatient Record", + "dynamic_filters_json": "[[\"Inpatient Record\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[]", + "group_by_based_on": "status", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:46.792131", + "modified": "2020-07-22 13:33:16.008150", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "In-Patient Status", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart/lab_tests/lab_tests.json b/healthcare/healthcare/dashboard_chart/lab_tests/lab_tests.json new file mode 100644 index 0000000..70293b1 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/lab_tests/lab_tests.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Lab Tests", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.574903", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Lab Test", + "dynamic_filters_json": "[[\"Lab Test\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Lab Test\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_based_on": "template", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2021-01-30 21:03:28.272914", + "modified": "2021-02-01 13:36:08.391433", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Tests", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart/patient_appointments/patient_appointments.json b/healthcare/healthcare/dashboard_chart/patient_appointments/patient_appointments.json new file mode 100644 index 0000000..19bfb72 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/patient_appointments/patient_appointments.json @@ -0,0 +1,27 @@ +{ + "based_on": "appointment_datetime", + "chart_name": "Patient Appointments", + "chart_type": "Count", + "creation": "2020-07-14 18:17:54.525082", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Patient Appointment", + "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Patient Appointment\",\"status\",\"!=\",\"Cancelled\",false]]", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "last_synced_on": "2020-07-22 13:22:46.830491", + "modified": "2020-07-22 13:38:02.254190", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Appointments", + "number_of_groups": 0, + "owner": "Administrator", + "time_interval": "Daily", + "timeseries": 1, + "timespan": "Last Month", + "type": "Line", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart/symptoms/symptoms.json b/healthcare/healthcare/dashboard_chart/symptoms/symptoms.json new file mode 100644 index 0000000..65e5472 --- /dev/null +++ b/healthcare/healthcare/dashboard_chart/symptoms/symptoms.json @@ -0,0 +1,26 @@ +{ + "chart_name": "Symptoms", + "chart_type": "Group By", + "creation": "2020-07-14 18:17:54.680852", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Patient Encounter Symptom", + "dynamic_filters_json": "", + "filters_json": "[]", + "group_by_based_on": "complaint", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "last_synced_on": "2021-01-30 21:03:32.067473", + "modified": "2021-02-01 13:35:30.953718", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Symptoms", + "number_of_groups": 0, + "owner": "Administrator", + "timeseries": 0, + "type": "Bar", + "use_report_chart": 0, + "y_axis": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart_source/__init__.py b/healthcare/healthcare/dashboard_chart_source/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py b/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js b/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js new file mode 100644 index 0000000..0097cdf --- /dev/null +++ b/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js @@ -0,0 +1,14 @@ +frappe.provide('frappe.dashboards.chart_sources'); + +frappe.dashboards.chart_sources["Department wise Patient Appointments"] = { + method: "healthcare.healthcare.dashboard_chart_source.department_wise_patient_appointments.department_wise_patient_appointments.get", + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company") + } + ] +}; diff --git a/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json b/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json new file mode 100644 index 0000000..00301ef --- /dev/null +++ b/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json @@ -0,0 +1,13 @@ +{ + "creation": "2020-05-18 19:18:42.571045", + "docstatus": 0, + "doctype": "Dashboard Chart Source", + "idx": 0, + "modified": "2020-05-18 19:18:42.571045", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Department wise Patient Appointments", + "owner": "Administrator", + "source_name": "Department wise Patient Appointments", + "timeseries": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py b/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py new file mode 100644 index 0000000..5a619bf --- /dev/null +++ b/healthcare/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py @@ -0,0 +1,70 @@ +# Copyright (c) 2015, InfluxERP +# License: GNU General Public License v3. See license.txt + + +import frappe +from frappe.utils.dashboard import cache_source + + +@frappe.whitelist() +@cache_source +def get( + chart_name=None, + chart=None, + no_cache=None, + filters=None, + from_date=None, + to_date=None, + timespan=None, + time_interval=None, + heatmap_year=None, +): + if chart_name: + chart = frappe.get_doc("Dashboard Chart", chart_name) + else: + chart = frappe._dict(frappe.parse_json(chart)) + + filters = frappe.parse_json(filters) + + data = frappe.db.get_list("Medical Department", fields=["name"]) + if not filters: + filters = {} + + status = ["Open", "Scheduled", "Closed", "Cancelled"] + for department in data: + filters["department"] = department.name + department["total_appointments"] = frappe.db.count("Patient Appointment", filters=filters) + + for entry in status: + filters["status"] = entry + department[frappe.scrub(entry)] = frappe.db.count("Patient Appointment", filters=filters) + filters.pop("status") + + sorted_department_map = sorted(data, key=lambda i: i["total_appointments"], reverse=True) + + if len(sorted_department_map) > 10: + sorted_department_map = sorted_department_map[:10] + + labels = [] + open_appointments = [] + scheduled = [] + closed = [] + cancelled = [] + + for department in sorted_department_map: + labels.append(department.name) + open_appointments.append(department.open) + scheduled.append(department.scheduled) + closed.append(department.closed) + cancelled.append(department.cancelled) + + return { + "labels": labels, + "datasets": [ + {"name": "Open", "values": open_appointments}, + {"name": "Scheduled", "values": scheduled}, + {"name": "Closed", "values": closed}, + {"name": "Cancelled", "values": cancelled}, + ], + "type": "bar", + } diff --git a/healthcare/healthcare/desk_page/healthcare/healthcare.json b/healthcare/healthcare/desk_page/healthcare/healthcare.json new file mode 100644 index 0000000..af601f3 --- /dev/null +++ b/healthcare/healthcare/desk_page/healthcare/healthcare.json @@ -0,0 +1,122 @@ +{ + "cards": [ + { + "hidden": 0, + "label": "Masters", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient\",\n\t\t\"label\": \"Patient\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Practitioner\",\n\t\t\"label\":\"Healthcare Practitioner\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Practitioner Schedule\",\n\t\t\"label\": \"Practitioner Schedule\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Department\",\n\t\t\"label\": \"Medical Department\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit Type\",\n\t\t\"label\": \"Healthcare Service Unit Type\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit\",\n\t\t\"label\": \"Healthcare Service Unit\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code Standard\",\n\t\t\"label\": \"Medical Code Standard\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code\",\n\t\t\"label\": \"Medical Code\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Consultation Setup", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Appointment Type\",\n\t\t\"label\": \"Appointment Type\"\n },\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure Template\",\n\t\t\"label\": \"Clinical Procedure Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Dosage\",\n\t\t\"label\": \"Prescription Dosage\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Duration\",\n\t\t\"label\": \"Prescription Duration\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Antibiotic\",\n\t\t\"label\": \"Antibiotic\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Consultation", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Appointment\",\n\t\t\"label\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure\",\n\t\t\"label\": \"Clinical Procedure\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Encounter\",\n\t\t\"label\": \"Patient Encounter\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Vital Signs\",\n\t\t\"label\": \"Vital Signs\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Complaint\",\n\t\t\"label\": \"Complaint\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Diagnosis\",\n\t\t\"label\": \"Diagnosis\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Fee Validity\",\n\t\t\"label\": \"Fee Validity\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Settings", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Settings\",\n\t\t\"label\": \"Healthcare Settings\",\n\t\t\"onboard\": 1\n\t}\n]" + }, + { + "hidden": 0, + "label": "Laboratory Setup", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Template\",\n\t\t\"label\": \"Lab Test Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Sample\",\n\t\t\"label\": \"Lab Test Sample\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test UOM\",\n\t\t\"label\": \"Lab Test UOM\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sensitivity\",\n\t\t\"label\": \"Sensitivity\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Laboratory", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Inpatient", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Order\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Entry\",\n\t\t\"label\": \"Inpatient Medication Entry\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Rehabilitation and Physiotherapy", + "links": "[\n {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exercise Type\",\n\t\t\"label\": \"Exercise Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Type\",\n\t\t\"label\": \"Therapy Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Plan\",\n\t\t\"label\": \"Therapy Plan\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Session\",\n\t\t\"label\": \"Therapy Session\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment Template\",\n\t\t\"label\": \"Patient Assessment Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment\",\n\t\t\"label\": \"Patient Assessment\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Records and History", + "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Reports", + "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Inpatient Medication Orders\",\n\t\t\"doctype\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Orders\"\n\t}\n]" + } + ], + "category": "Domains", + "charts": [ + { + "chart_name": "Patient Appointments", + "label": "Patient Appointments" + } + ], + "charts_label": "", + "creation": "2020-03-02 17:23:17.919682", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Desk Page", + "extends_another_page": 0, + "hide_custom": 0, + "idx": 0, + "is_standard": 1, + "label": "Healthcare", + "modified": "2020-11-26 22:09:09.164584", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare", + "onboarding": "Healthcare", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "restrict_to_domain": "Healthcare", + "shortcuts": [ + { + "color": "#ffe8cd", + "format": "{} Open", + "label": "Patient Appointment", + "link_to": "Patient Appointment", + "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}", + "type": "DocType" + }, + { + "color": "#ffe8cd", + "format": "{} Active", + "label": "Patient", + "link_to": "Patient", + "stats_filter": "{\n \"status\": \"Active\"\n}", + "type": "DocType" + }, + { + "color": "#cef6d1", + "format": "{} Vacant", + "label": "Healthcare Service Unit", + "link_to": "Healthcare Service Unit", + "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}", + "type": "DocType" + }, + { + "label": "Healthcare Practitioner", + "link_to": "Healthcare Practitioner", + "type": "DocType" + }, + { + "label": "Patient History", + "link_to": "patient_history", + "type": "Page" + }, + { + "label": "Dashboard", + "link_to": "Healthcare", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/__init__.py b/healthcare/healthcare/doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/antibiotic/__init__.py b/healthcare/healthcare/doctype/antibiotic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/antibiotic/antibiotic.js b/healthcare/healthcare/doctype/antibiotic/antibiotic.js new file mode 100644 index 0000000..8ac7946 --- /dev/null +++ b/healthcare/healthcare/doctype/antibiotic/antibiotic.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Antibiotic', { +}); diff --git a/healthcare/healthcare/doctype/antibiotic/antibiotic.json b/healthcare/healthcare/doctype/antibiotic/antibiotic.json new file mode 100644 index 0000000..41a3e31 --- /dev/null +++ b/healthcare/healthcare/doctype/antibiotic/antibiotic.json @@ -0,0 +1,151 @@ +{ + "allow_copy": 1, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:antibiotic_name", + "beta": 1, + "creation": "2016-02-23 11:11:30.749731", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "antibiotic_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Antibiotic Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "abbr", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Abbr", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-10-01 17:58:23.136498", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Antibiotic", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "antibiotic_name", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "antibiotic_name", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/antibiotic/antibiotic.py b/healthcare/healthcare/doctype/antibiotic/antibiotic.py new file mode 100644 index 0000000..45ab853 --- /dev/null +++ b/healthcare/healthcare/doctype/antibiotic/antibiotic.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class Antibiotic(Document): + pass diff --git a/healthcare/healthcare/doctype/antibiotic/test_antibiotic.py b/healthcare/healthcare/doctype/antibiotic/test_antibiotic.py new file mode 100644 index 0000000..5f71716 --- /dev/null +++ b/healthcare/healthcare/doctype/antibiotic/test_antibiotic.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestAntibiotic(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/appointment_type/__init__.py b/healthcare/healthcare/doctype/appointment_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/appointment_type/appointment_type.js b/healthcare/healthcare/doctype/appointment_type/appointment_type.js new file mode 100644 index 0000000..99b7cb2 --- /dev/null +++ b/healthcare/healthcare/doctype/appointment_type/appointment_type.js @@ -0,0 +1,83 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Appointment Type', { + refresh: function(frm) { + frm.set_query('price_list', function() { + return { + filters: {'selling': 1} + }; + }); + + frm.set_query('medical_department', 'items', function(doc) { + let item_list = doc.items.map(({medical_department}) => medical_department); + return { + filters: [ + ['Medical Department', 'name', 'not in', item_list] + ] + }; + }); + + frm.set_query('op_consulting_charge_item', 'items', function() { + return { + filters: { + is_stock_item: 0 + } + }; + }); + + frm.set_query('inpatient_visit_charge_item', 'items', function() { + return { + filters: { + is_stock_item: 0 + } + }; + }); + } +}); + +frappe.ui.form.on('Appointment Type Service Item', { + op_consulting_charge_item: function(frm, cdt, cdn) { + let d = locals[cdt][cdn]; + if (frm.doc.price_list && d.op_consulting_charge_item) { + frappe.call({ + 'method': 'frappe.client.get_value', + args: { + 'doctype': 'Item Price', + 'filters': { + 'item_code': d.op_consulting_charge_item, + 'price_list': frm.doc.price_list + }, + 'fieldname': ['price_list_rate'] + }, + callback: function(data) { + if (data.message.price_list_rate) { + frappe.model.set_value(cdt, cdn, 'op_consulting_charge', data.message.price_list_rate); + } + } + }); + } + }, + + inpatient_visit_charge_item: function(frm, cdt, cdn) { + let d = locals[cdt][cdn]; + if (frm.doc.price_list && d.inpatient_visit_charge_item) { + frappe.call({ + 'method': 'frappe.client.get_value', + args: { + 'doctype': 'Item Price', + 'filters': { + 'item_code': d.inpatient_visit_charge_item, + 'price_list': frm.doc.price_list + }, + 'fieldname': ['price_list_rate'] + }, + callback: function (data) { + if (data.message.price_list_rate) { + frappe.model.set_value(cdt, cdn, 'inpatient_visit_charge', data.message.price_list_rate); + } + } + }); + } + } +}); diff --git a/healthcare/healthcare/doctype/appointment_type/appointment_type.json b/healthcare/healthcare/doctype/appointment_type/appointment_type.json new file mode 100644 index 0000000..3872318 --- /dev/null +++ b/healthcare/healthcare/doctype/appointment_type/appointment_type.json @@ -0,0 +1,114 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:appointment_type", + "beta": 1, + "creation": "2016-07-22 11:52:34.953019", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "appointment_type", + "ip", + "default_duration", + "color", + "billing_section", + "price_list", + "items" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "appointment_type", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Type", + "reqd": 1, + "translatable": 1, + "unique": 1 + }, + { + "bold": 1, + "default": "0", + "fieldname": "ip", + "fieldtype": "Check", + "label": "Is Inpatient", + "print_hide": 1, + "report_hide": 1 + }, + { + "allow_in_quick_entry": 1, + "bold": 1, + "fieldname": "default_duration", + "fieldtype": "Int", + "in_filter": 1, + "in_list_view": 1, + "label": "Default Duration (In Minutes)" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "color", + "fieldtype": "Color", + "in_list_view": 1, + "label": "Color", + "no_copy": 1, + "report_hide": 1 + }, + { + "fieldname": "billing_section", + "fieldtype": "Section Break", + "label": "Billing" + }, + { + "fieldname": "price_list", + "fieldtype": "Link", + "label": "Price List", + "options": "Price List" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Appointment Type Service Items", + "options": "Appointment Type Service Item" + } + ], + "links": [], + "modified": "2021-01-22 09:41:05.010524", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Appointment Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "search_fields": "appointment_type", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/appointment_type/appointment_type.py b/healthcare/healthcare/doctype/appointment_type/appointment_type.py new file mode 100644 index 0000000..ca73847 --- /dev/null +++ b/healthcare/healthcare/doctype/appointment_type/appointment_type.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document + + +class AppointmentType(Document): + def validate(self): + if self.items and self.price_list: + for item in self.items: + existing_op_item_price = frappe.db.exists( + "Item Price", {"item_code": item.op_consulting_charge_item, "price_list": self.price_list} + ) + + if not existing_op_item_price and item.op_consulting_charge_item and item.op_consulting_charge: + make_item_price(self.price_list, item.op_consulting_charge_item, item.op_consulting_charge) + + existing_ip_item_price = frappe.db.exists( + "Item Price", {"item_code": item.inpatient_visit_charge_item, "price_list": self.price_list} + ) + + if ( + not existing_ip_item_price + and item.inpatient_visit_charge_item + and item.inpatient_visit_charge + ): + make_item_price( + self.price_list, item.inpatient_visit_charge_item, item.inpatient_visit_charge + ) + + +@frappe.whitelist() +def get_service_item_based_on_department(appointment_type, department): + item_list = frappe.db.get_value( + "Appointment Type Service Item", + filters={"medical_department": department, "parent": appointment_type}, + fieldname=[ + "op_consulting_charge_item", + "inpatient_visit_charge_item", + "op_consulting_charge", + "inpatient_visit_charge", + ], + as_dict=1, + ) + + # if department wise items are not set up + # use the generic items + if not item_list: + item_list = frappe.db.get_value( + "Appointment Type Service Item", + filters={"parent": appointment_type}, + fieldname=[ + "op_consulting_charge_item", + "inpatient_visit_charge_item", + "op_consulting_charge", + "inpatient_visit_charge", + ], + as_dict=1, + ) + + return item_list + + +def make_item_price(price_list, item, item_price): + frappe.get_doc( + { + "doctype": "Item Price", + "price_list": price_list, + "item_code": item, + "price_list_rate": item_price, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) diff --git a/healthcare/healthcare/doctype/appointment_type/appointment_type_dashboard.py b/healthcare/healthcare/doctype/appointment_type/appointment_type_dashboard.py new file mode 100644 index 0000000..04bc04a --- /dev/null +++ b/healthcare/healthcare/doctype/appointment_type/appointment_type_dashboard.py @@ -0,0 +1,10 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "appointment_type", + "transactions": [ + {"label": _("Patient Appointments"), "items": ["Patient Appointment"]}, + ], + } diff --git a/healthcare/healthcare/doctype/appointment_type/test_appointment_type.py b/healthcare/healthcare/doctype/appointment_type/test_appointment_type.py new file mode 100644 index 0000000..2cabf51 --- /dev/null +++ b/healthcare/healthcare/doctype/appointment_type/test_appointment_type.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Appointment Type') + + +class TestAppointmentType(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/appointment_type_service_item/__init__.py b/healthcare/healthcare/doctype/appointment_type_service_item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json b/healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json new file mode 100644 index 0000000..ccae129 --- /dev/null +++ b/healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json @@ -0,0 +1,67 @@ +{ + "actions": [], + "creation": "2021-01-22 09:34:53.373105", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "medical_department", + "op_consulting_charge_item", + "op_consulting_charge", + "column_break_4", + "inpatient_visit_charge_item", + "inpatient_visit_charge" + ], + "fields": [ + { + "fieldname": "medical_department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "op_consulting_charge_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Out Patient Consulting Charge Item", + "options": "Item" + }, + { + "fieldname": "op_consulting_charge", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Out Patient Consulting Charge" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "inpatient_visit_charge_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Inpatient Visit Charge Item", + "options": "Item" + }, + { + "fieldname": "inpatient_visit_charge", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Inpatient Visit Charge" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-17 06:05:02.240812", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Appointment Type Service Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py b/healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py new file mode 100644 index 0000000..73f384c --- /dev/null +++ b/healthcare/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class AppointmentTypeServiceItem(Document): + pass diff --git a/healthcare/healthcare/doctype/body_part/__init__.py b/healthcare/healthcare/doctype/body_part/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/body_part/body_part.js b/healthcare/healthcare/doctype/body_part/body_part.js new file mode 100644 index 0000000..443d60b --- /dev/null +++ b/healthcare/healthcare/doctype/body_part/body_part.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Body Part', { + // refresh: function(frm) { + + // } +}); diff --git a/healthcare/healthcare/doctype/body_part/body_part.json b/healthcare/healthcare/doctype/body_part/body_part.json new file mode 100644 index 0000000..6e3d1d4 --- /dev/null +++ b/healthcare/healthcare/doctype/body_part/body_part.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "autoname": "field:body_part", + "creation": "2020-04-10 12:21:55.036402", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "body_part" + ], + "fields": [ + { + "fieldname": "body_part", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Body Part", + "reqd": 1, + "unique": 1 + } + ], + "links": [], + "modified": "2020-04-10 12:26:44.087985", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Body Part", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/body_part/body_part.py b/healthcare/healthcare/doctype/body_part/body_part.py new file mode 100644 index 0000000..39191b7 --- /dev/null +++ b/healthcare/healthcare/doctype/body_part/body_part.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class BodyPart(Document): + pass diff --git a/healthcare/healthcare/doctype/body_part/test_body_part.py b/healthcare/healthcare/doctype/body_part/test_body_part.py new file mode 100644 index 0000000..6e0273d --- /dev/null +++ b/healthcare/healthcare/doctype/body_part/test_body_part.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBodyPart(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/body_part_link/__init__.py b/healthcare/healthcare/doctype/body_part_link/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/body_part_link/body_part_link.json b/healthcare/healthcare/doctype/body_part_link/body_part_link.json new file mode 100644 index 0000000..400b7c6 --- /dev/null +++ b/healthcare/healthcare/doctype/body_part_link/body_part_link.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-04-10 12:23:15.259816", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "body_part" + ], + "fields": [ + { + "fieldname": "body_part", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Body Part", + "options": "Body Part", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-10 12:25:23.101749", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Body Part Link", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/body_part_link/body_part_link.py b/healthcare/healthcare/doctype/body_part_link/body_part_link.py new file mode 100644 index 0000000..712bee1 --- /dev/null +++ b/healthcare/healthcare/doctype/body_part_link/body_part_link.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class BodyPartLink(Document): + pass diff --git a/healthcare/healthcare/doctype/clinical_procedure/__init__.py b/healthcare/healthcare/doctype/clinical_procedure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.js b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.js new file mode 100644 index 0000000..bdefbc0 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -0,0 +1,377 @@ +// Copyright (c) 2017, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Clinical Procedure', { + setup: function(frm) { + frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { + let item = locals[cdt][cdn]; + if (!item.item_code) { + frappe.throw(__('Please enter Item Code to get Batch Number')); + } else { + let filters = {'item_code': item.item_code}; + + if (frm.doc.status == 'In Progress') { + filters['posting_date'] = frm.doc.start_date || frappe.datetime.nowdate(); + if (frm.doc.warehouse) filters['warehouse'] = frm.doc.warehouse; + } + + return { + query : 'erpnext.controllers.queries.get_batch_no', + filters: filters + }; + } + }); + }, + + refresh: function(frm) { + frm.set_query('patient', function () { + return { + filters: {'status': ['!=', 'Disabled']} + }; + }); + + frm.set_query('appointment', function () { + return { + filters: { + 'procedure_template': ['not in', null], + 'status': ['in', 'Open, Scheduled'] + } + }; + }); + + frm.set_query('service_unit', function() { + return { + filters: { + 'is_group': false, + 'allow_appointments': true, + 'company': frm.doc.company + } + }; + }); + + frm.set_query('practitioner', function() { + return { + filters: { + 'department': frm.doc.medical_department + } + }; + }); + + if (frm.doc.consume_stock) { + frm.set_indicator_formatter('item_code', + function(doc) { return (doc.qty<=doc.actual_qty) ? 'green' : 'orange' ; }); + } + + if (frm.doc.docstatus == 1) { + if (frm.doc.status == 'In Progress') { + let btn_label = ''; + let msg = ''; + if (frm.doc.consume_stock) { + btn_label = __('Complete and Consume'); + msg = __('Complete {0} and Consume Stock?', [frm.doc.name]); + } else { + btn_label = 'Complete'; + msg = __('Complete {0}?', [frm.doc.name]); + } + + frm.add_custom_button(__(btn_label), function () { + frappe.confirm( + msg, + function() { + frappe.call({ + method: 'complete_procedure', + doc: frm.doc, + freeze: true, + callback: function(r) { + if (r.message) { + frappe.show_alert({ + message: __('Stock Entry {0} created', ['' + r.message + '']), + indicator: 'green' + }); + } + frm.reload_doc(); + } + }); + } + ); + }).addClass("btn-primary"); + + } else if (frm.doc.status == 'Pending') { + frm.add_custom_button(__('Start'), function() { + frappe.call({ + doc: frm.doc, + method: 'start_procedure', + callback: function(r) { + if (!r.exc) { + if (r.message == 'insufficient stock') { + let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?', [frm.doc.warehouse.bold()]); + frappe.confirm( + msg, + function() { + frappe.call({ + doc: frm.doc, + method: 'make_material_receipt', + freeze: true, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + let doclist = frappe.model.sync(r.message); + frappe.set_route('Form', doclist[0].doctype, doclist[0].name); + } + } + }); + } + ); + } else { + frm.reload_doc(); + } + } + } + }); + }).addClass("btn-primary"); + } + } + }, + + onload: function(frm) { + if (frm.is_new()) { + frm.add_fetch('procedure_template', 'medical_department', 'medical_department'); + frm.set_value('start_time', null); + } + }, + + patient: function(frm) { + if (frm.doc.patient) { + frappe.call({ + 'method': 'healthcare.healthcare.doctype.patient.patient.get_patient_detail', + args: { + patient: frm.doc.patient + }, + callback: function (data) { + let age = ''; + if (data.message.dob) { + age = calculate_age(data.message.dob); + } else if (data.message.age) { + age = data.message.age; + if (data.message.age_as_on) { + age = __('{0} as on {1}', [age, data.message.age_as_on]); + } + } + frm.set_value('patient_name', data.message.patient_name); + frm.set_value('patient_age', age); + frm.set_value('patient_sex', data.message.sex); + } + }); + } else { + frm.set_value('patient_name', ''); + frm.set_value('patient_age', ''); + frm.set_value('patient_sex', ''); + } + }, + + appointment: function(frm) { + if (frm.doc.appointment) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Patient Appointment', + name: frm.doc.appointment + }, + callback: function(data) { + let values = { + 'patient':data.message.patient, + 'procedure_template': data.message.procedure_template, + 'medical_department': data.message.department, + 'practitioner': data.message.practitioner, + 'start_date': data.message.appointment_date, + 'start_time': data.message.appointment_time, + 'notes': data.message.notes, + 'service_unit': data.message.service_unit, + 'company': data.message.company + }; + frm.set_value(values); + } + }); + } else { + let values = { + 'patient': '', + 'patient_name': '', + 'patient_sex': '', + 'patient_age': '', + 'medical_department': '', + 'procedure_template': '', + 'start_date': '', + 'start_time': '', + 'notes': '', + 'service_unit': '', + 'inpatient_record': '' + }; + frm.set_value(values); + } + }, + + procedure_template: function(frm) { + if (frm.doc.procedure_template) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Clinical Procedure Template', + name: frm.doc.procedure_template + }, + callback: function (data) { + frm.set_value('medical_department', data.message.medical_department); + frm.set_value('consume_stock', data.message.consume_stock); + frm.events.set_warehouse(frm); + frm.events.set_procedure_consumables(frm); + } + }); + } + }, + + service_unit: function(frm) { + if (frm.doc.service_unit) { + frappe.call({ + method: 'frappe.client.get_value', + args:{ + fieldname: 'warehouse', + doctype: 'Healthcare Service Unit', + filters:{name: frm.doc.service_unit}, + }, + callback: function(data) { + if (data.message) { + frm.set_value('warehouse', data.message.warehouse); + } + } + }); + } + }, + + practitioner: function(frm) { + if (frm.doc.practitioner) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Healthcare Practitioner', + name: frm.doc.practitioner + }, + callback: function (data) { + frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', data.message.practitioner_name); + } + }); + } else { + frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', ''); + } + }, + + set_warehouse: function(frm) { + if (!frm.doc.warehouse) { + frappe.call({ + method: 'frappe.client.get_value', + args: { + doctype: 'Stock Settings', + fieldname: 'default_warehouse' + }, + callback: function (data) { + frm.set_value('warehouse', data.message.default_warehouse); + } + }); + } + }, + + set_procedure_consumables: function(frm) { + frappe.call({ + method: 'healthcare.healthcare.doctype.clinical_procedure.clinical_procedure.get_procedure_consumables', + args: { + procedure_template: frm.doc.procedure_template + }, + callback: function(data) { + if (data.message) { + frm.doc.items = []; + $.each(data.message, function(i, v) { + let item = frm.add_child('items'); + item.item_code = v.item_code; + item.item_name = v.item_name; + item.uom = v.uom; + item.stock_uom = v.stock_uom; + item.qty = flt(v.qty); + item.transfer_qty = v.transfer_qty; + item.conversion_factor = v.conversion_factor; + item.invoice_separately_as_consumables = v.invoice_separately_as_consumables; + item.batch_no = v.batch_no; + }); + refresh_field('items'); + } + } + }); + } + +}); + +frappe.ui.form.on('Clinical Procedure Item', { + qty: function(frm, cdt, cdn) { + let d = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, 'transfer_qty', d.qty*d.conversion_factor); + }, + + uom: function(doc, cdt, cdn) { + let d = locals[cdt][cdn]; + if (d.uom && d.item_code) { + return frappe.call({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_uom_details', + args: { + item_code: d.item_code, + uom: d.uom, + qty: d.qty + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, r.message); + } + } + }); + } + }, + + item_code: function(frm, cdt, cdn) { + let d = locals[cdt][cdn]; + let args = null; + if (d.item_code) { + args = { + 'doctype' : 'Clinical Procedure', + 'item_code' : d.item_code, + 'company' : frm.doc.company, + 'warehouse': frm.doc.warehouse + }; + return frappe.call({ + method: 'healthcare.healthcare.doctype.clinical_procedure_template.clinical_procedure_template.get_item_details', + args: {args: args}, + callback: function(r) { + if (r.message) { + let d = locals[cdt][cdn]; + $.each(r.message, function(k, v) { + d[k] = v; + }); + refresh_field('items'); + } + } + }); + } + } +}); + +let calculate_age = function(birth) { + let ageMS = Date.parse(Date()) - Date.parse(birth); + let age = new Date(); + age.setTime(ageMS); + let years = age.getFullYear() - 1970; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; +}; + +// List Stock items +cur_frm.set_query('item_code', 'items', function() { + return { + filters: { + is_stock_item:1 + } + }; +}); diff --git a/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.json b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.json new file mode 100644 index 0000000..48a9c28 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -0,0 +1,351 @@ +{ + "actions": [], + "autoname": "naming_series:", + "beta": 1, + "creation": "2017-04-07 12:52:43.542429", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "title", + "appointment", + "procedure_template", + "medical_code", + "column_break_30", + "company", + "invoiced", + "section_break_6", + "patient", + "patient_name", + "patient_sex", + "patient_age", + "inpatient_record", + "notes", + "column_break_7", + "status", + "practitioner", + "practitioner_name", + "medical_department", + "service_unit", + "start_date", + "start_time", + "sample", + "consumables_section", + "consume_stock", + "warehouse", + "items", + "section_break_24", + "invoice_separately_as_consumables", + "consumption_invoiced", + "consumable_total_amount", + "column_break_27", + "consumption_details", + "sb_refs", + "column_break_34", + "prescription", + "amended_from" + ], + "fields": [ + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-CPR-.YYYY.-" + }, + { + "fieldname": "appointment", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Appointment", + "options": "Patient Appointment", + "set_only_once": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "label": "Age", + "read_only": 1 + }, + { + "fieldname": "patient_sex", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1, + "set_only_once": 1 + }, + { + "fieldname": "prescription", + "fieldtype": "Link", + "hidden": 1, + "label": "Procedure Prescription", + "options": "Procedure Prescription", + "read_only": 1 + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "procedure_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Procedure Template", + "options": "Clinical Procedure Template", + "reqd": 1 + }, + { + "fieldname": "service_unit", + "fieldtype": "Link", + "label": "Service Unit", + "options": "Healthcare Service Unit", + "set_only_once": 1 + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "mandatory_depends_on": "eval: doc.consume_stock == 1", + "options": "Warehouse" + }, + { + "default": "Today", + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date" + }, + { + "fieldname": "start_time", + "fieldtype": "Time", + "label": "Start Time" + }, + { + "fieldname": "sample", + "fieldtype": "Link", + "label": "Sample", + "options": "Sample Collection" + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "notes", + "fieldtype": "Small Text", + "label": "Notes", + "set_only_once": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "default": "0", + "fieldname": "consume_stock", + "fieldtype": "Check", + "label": "Consume Stock" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Consumables", + "options": "Clinical Procedure Item" + }, + { + "default": "0", + "fieldname": "invoice_separately_as_consumables", + "fieldtype": "Check", + "hidden": 1, + "label": "Invoice Consumables Separately", + "read_only": 1 + }, + { + "depends_on": "invoice_separately_as_consumables", + "fieldname": "consumable_total_amount", + "fieldtype": "Currency", + "label": "Consumable Total Amount", + "read_only": 1 + }, + { + "depends_on": "invoice_separately_as_consumables", + "fieldname": "consumption_details", + "fieldtype": "Small Text", + "label": "Consumption Details" + }, + { + "default": "0", + "depends_on": "invoice_separately_as_consumables", + "fieldname": "consumption_invoiced", + "fieldtype": "Check", + "hidden": 1, + "label": "Consumption Invoiced", + "read_only": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Draft\nSubmitted\nCancelled\nIn Progress\nCompleted\nPending", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Clinical Procedure", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "consume_stock", + "fieldname": "consumables_section", + "fieldtype": "Section Break", + "label": "Consumables" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_24", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fetch_from": "procedure_template.medical_code", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code", + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [ + { + "link_doctype": "Nursing Task", + "link_fieldname": "reference_name" + } + ], + "modified": "2022-01-17 15:13:55.565922", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Clinical Procedure", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.py b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.py new file mode 100644 index 0000000..63bd6cd --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, ESS LLP and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import flt, nowdate, nowtime, now_datetime + +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import get_account +from healthcare.healthcare.doctype.lab_test.lab_test import create_sample_doc +from erpnext.stock.get_item_details import get_item_details +from erpnext.stock.stock_ledger import get_previous_sle +from healthcare.healthcare.doctype.nursing_task.nursing_task import NursingTask +from healthcare.healthcare.utils import validate_nursing_tasks + + +class ClinicalProcedure(Document): + def validate(self): + self.set_status() + self.set_title() + if self.consume_stock: + self.set_actual_qty() + + if self.items: + self.invoice_separately_as_consumables = False + for item in self.items: + if item.invoice_separately_as_consumables: + self.invoice_separately_as_consumables = True + + def before_insert(self): + if self.consume_stock: + self.set_actual_qty() + + def after_insert(self): + if self.prescription: + frappe.db.set_value("Procedure Prescription", self.prescription, "procedure_created", 1) + if self.appointment: + frappe.db.set_value("Patient Appointment", self.appointment, "status", "Closed") + + if self.procedure_template: + template = frappe.get_doc("Clinical Procedure Template", self.procedure_template) + if template.sample: + patient = frappe.get_doc("Patient", self.patient) + sample_collection = create_sample_doc(template, patient, None, self.company) + frappe.db.set_value("Clinical Procedure", self.name, "sample", sample_collection.name) + + self.reload() + + def on_submit(self): + self.create_nursing_tasks(post_event=False) + + def create_nursing_tasks(self, post_event=True): + if post_event: + template = frappe.db.get_value( + "Clinical Procedure Template", self.procedure_template, "post_op_nursing_checklist_template" + ) + start_time = now_datetime() + + else: + template = frappe.db.get_value( + "Clinical Procedure Template", self.procedure_template, "pre_op_nursing_checklist_template" + ) + # pre op tasks to be created on Clinical Procedure submit, use scheduled date + start_time = frappe.utils.get_datetime(f"{self.start_date} {self.start_time}") + + if template: + NursingTask.create_nursing_tasks_from_template( + template, self, start_time=start_time, post_event=post_event + ) + + def set_status(self): + if self.docstatus == 0: + self.status = "Draft" + elif self.docstatus == 1: + if self.status not in ["In Progress", "Completed"]: + self.status = "Pending" + elif self.docstatus == 2: + self.status = "Cancelled" + + def set_title(self): + self.title = _("{0} - {1}").format(self.patient_name or self.patient, self.procedure_template)[ + :100 + ] + + @frappe.whitelist() + def complete_procedure(self): + if self.consume_stock and self.items: + stock_entry = make_stock_entry(self) + + if self.items: + consumable_total_amount = 0 + consumption_details = False + customer = frappe.db.get_value("Patient", self.patient, "customer") + if customer: + for item in self.items: + if item.invoice_separately_as_consumables: + price_list, price_list_currency = frappe.db.get_values( + "Price List", {"selling": 1}, ["name", "currency"] + )[0] + args = { + "doctype": "Sales Invoice", + "item_code": item.item_code, + "company": self.company, + "warehouse": self.warehouse, + "customer": customer, + "selling_price_list": price_list, + "price_list_currency": price_list_currency, + "plc_conversion_rate": 1.0, + "conversion_rate": 1.0, + } + item_details = get_item_details(args) + item_price = item_details.price_list_rate * item.qty + item_consumption_details = ( + item_details.item_name + " " + str(item.qty) + " " + item.uom + " " + str(item_price) + ) + consumable_total_amount += item_price + if not consumption_details: + consumption_details = _("Clinical Procedure ({0}):").format(self.name) + consumption_details += "\n\t" + item_consumption_details + + if consumable_total_amount > 0: + frappe.db.set_value( + "Clinical Procedure", self.name, "consumable_total_amount", consumable_total_amount + ) + frappe.db.set_value( + "Clinical Procedure", self.name, "consumption_details", consumption_details + ) + else: + frappe.throw( + _("Please set Customer in Patient {0}").format(frappe.bold(self.patient)), + title=_("Customer Not Found"), + ) + + self.db_set("status", "Completed") + + # post op nursing tasks + if self.procedure_template: + self.create_nursing_tasks() + + if self.consume_stock and self.items: + return stock_entry + + @frappe.whitelist() + def start_procedure(self): + allow_start = self.set_actual_qty() + + if allow_start: + validate_nursing_tasks(self) + + self.db_set("status", "In Progress") + return "success" + + return "insufficient stock" + + def set_actual_qty(self): + allow_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") + + allow_start = True + for d in self.get("items"): + d.actual_qty = get_stock_qty(d.item_code, self.warehouse) + # validate qty + if not allow_negative_stock and d.actual_qty < d.qty: + allow_start = False + break + + return allow_start + + @frappe.whitelist() + def make_material_receipt(self, submit=False): + stock_entry = frappe.new_doc("Stock Entry") + + stock_entry.stock_entry_type = "Material Receipt" + stock_entry.to_warehouse = self.warehouse + stock_entry.company = self.company + expense_account = get_account(None, "expense_account", "Healthcare Settings", self.company) + for item in self.items: + if item.qty > item.actual_qty: + se_child = stock_entry.append("items") + se_child.item_code = item.item_code + se_child.item_name = item.item_name + se_child.uom = item.uom + se_child.stock_uom = item.stock_uom + se_child.qty = flt(item.qty - item.actual_qty) + se_child.t_warehouse = self.warehouse + # in stock uom + se_child.transfer_qty = flt(item.transfer_qty) + se_child.conversion_factor = flt(item.conversion_factor) + cost_center = frappe.get_cached_value("Company", self.company, "cost_center") + se_child.cost_center = cost_center + se_child.expense_account = expense_account + if submit: + stock_entry.submit() + return stock_entry + return stock_entry.as_dict() + + +def get_stock_qty(item_code, warehouse): + return ( + get_previous_sle( + { + "item_code": item_code, + "warehouse": warehouse, + "posting_date": nowdate(), + "posting_time": nowtime(), + } + ).get("qty_after_transaction") + or 0 + ) + + +@frappe.whitelist() +def get_procedure_consumables(procedure_template): + return get_items("Clinical Procedure Item", procedure_template, "Clinical Procedure Template") + + +@frappe.whitelist() +def set_stock_items(doc, stock_detail_parent, parenttype): + items = get_items("Clinical Procedure Item", stock_detail_parent, parenttype) + + for item in items: + se_child = doc.append("items") + se_child.item_code = item.item_code + se_child.item_name = item.item_name + se_child.uom = item.uom + se_child.stock_uom = item.stock_uom + se_child.qty = flt(item.qty) + # in stock uom + se_child.transfer_qty = flt(item.transfer_qty) + se_child.conversion_factor = flt(item.conversion_factor) + if item.batch_no: + se_child.batch_no = item.batch_no + if parenttype == "Clinical Procedure Template": + se_child.invoice_separately_as_consumables = item.invoice_separately_as_consumables + + return doc + + +def get_items(table, parent, parenttype): + items = frappe.db.get_all( + table, filters={"parent": parent, "parenttype": parenttype}, fields=["*"] + ) + + return items + + +@frappe.whitelist() +def make_stock_entry(doc): + stock_entry = frappe.new_doc("Stock Entry") + stock_entry = set_stock_items(stock_entry, doc.name, "Clinical Procedure") + stock_entry.stock_entry_type = "Material Issue" + stock_entry.from_warehouse = doc.warehouse + stock_entry.company = doc.company + expense_account = get_account(None, "expense_account", "Healthcare Settings", doc.company) + + for item_line in stock_entry.items: + cost_center = frappe.get_cached_value("Company", doc.company, "cost_center") + item_line.cost_center = cost_center + item_line.expense_account = expense_account + + stock_entry.save(ignore_permissions=True) + stock_entry.submit() + return stock_entry.name + + +@frappe.whitelist() +def make_procedure(source_name, target_doc=None): + def set_missing_values(source, target): + consume_stock = frappe.db.get_value( + "Clinical Procedure Template", source.procedure_template, "consume_stock" + ) + if consume_stock: + target.consume_stock = 1 + warehouse = None + if source.service_unit: + warehouse = frappe.db.get_value("Healthcare Service Unit", source.service_unit, "warehouse") + if not warehouse: + warehouse = frappe.db.get_value("Stock Settings", None, "default_warehouse") + if warehouse: + target.warehouse = warehouse + + set_stock_items(target, source.procedure_template, "Clinical Procedure Template") + + doc = get_mapped_doc( + "Patient Appointment", + source_name, + { + "Patient Appointment": { + "doctype": "Clinical Procedure", + "field_map": [ + ["appointment", "name"], + ["patient", "patient"], + ["patient_age", "patient_age"], + ["patient_sex", "patient_sex"], + ["procedure_template", "procedure_template"], + ["prescription", "procedure_prescription"], + ["practitioner", "practitioner"], + ["medical_department", "department"], + ["start_date", "appointment_date"], + ["start_time", "appointment_time"], + ["notes", "notes"], + ["service_unit", "service_unit"], + ["company", "company"], + ["invoiced", "invoiced"], + ], + } + }, + target_doc, + set_missing_values, + ) + + return doc diff --git a/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure_list.js b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure_list.js new file mode 100644 index 0000000..c8601f9 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure/clinical_procedure_list.js @@ -0,0 +1,11 @@ +frappe.listview_settings['Clinical Procedure'] = { + get_indicator: function(doc) { + var colors = { + 'Completed': 'green', + 'In Progress': 'orange', + 'Pending': 'orange', + 'Cancelled': 'grey' + }; + return [__(doc.status), colors[doc.status], 'status,=,' + doc.status]; + } +}; diff --git a/healthcare/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/healthcare/healthcare/doctype/clinical_procedure/test_clinical_procedure.py new file mode 100644 index 0000000..6733041 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe + +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_clinical_procedure_template, + create_healthcare_docs, +) + +test_dependencies = ["Item"] + + +class TestClinicalProcedure(FrappeTestCase): + def test_procedure_template_item(self): + patient, practitioner = create_healthcare_docs() + procedure_template = create_clinical_procedure_template() + self.assertTrue(frappe.db.exists("Item", procedure_template.item)) + + procedure_template.disabled = 1 + procedure_template.save() + self.assertEqual(frappe.db.get_value("Item", procedure_template.item, "disabled"), 1) + + def test_consumables(self): + patient, practitioner = create_healthcare_docs() + procedure_template = create_clinical_procedure_template() + procedure_template.allow_stock_consumption = 1 + consumable = create_consumable() + procedure_template.append( + "items", + { + "item_code": consumable.item_code, + "qty": 1, + "uom": consumable.stock_uom, + "stock_uom": consumable.stock_uom, + }, + ) + procedure_template.save() + procedure = create_procedure(procedure_template, patient, practitioner) + result = procedure.start_procedure() + if result == "insufficient stock": + procedure.make_material_receipt(submit=True) + result = procedure.start_procedure() + self.assertEqual(procedure.status, "In Progress") + result = procedure.complete_procedure() + # check consumption + self.assertTrue(frappe.db.exists("Stock Entry", result)) + + +def create_consumable(): + if frappe.db.exists("Item", "Syringe"): + return frappe.get_doc("Item", "Syringe") + consumable = frappe.new_doc("Item") + consumable.item_code = "Syringe" + consumable.item_group = "_Test Item Group" + consumable.stock_uom = "Nos" + consumable.valuation_rate = 5.00 + consumable.save() + return consumable + + +def create_procedure(procedure_template, patient, practitioner): + procedure = frappe.new_doc("Clinical Procedure") + procedure.procedure_template = procedure_template.name + procedure.patient = patient + procedure.practitioner = practitioner + procedure.consume_stock = procedure_template.allow_stock_consumption + procedure.items = procedure_template.items + procedure.company = "_Test Company" + procedure.warehouse = "_Test Warehouse - _TC" + procedure.submit() + return procedure diff --git a/healthcare/healthcare/doctype/clinical_procedure_item/__init__.py b/healthcare/healthcare/doctype/clinical_procedure_item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json b/healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json new file mode 100644 index 0000000..a7dde0b --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json @@ -0,0 +1,123 @@ +{ + "actions": [], + "beta": 1, + "creation": "2017-10-05 16:15:10.876952", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "qty", + "barcode", + "uom", + "invoice_separately_as_consumables", + "column_break_5", + "batch_no", + "conversion_factor", + "stock_uom", + "transfer_qty", + "actual_qty" + ], + "fields": [ + { + "bold": 1, + "columns": 3, + "fieldname": "item_code", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_global_search": 1, + "in_list_view": 1, + "label": "Item", + "options": "Item", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "barcode", + "fieldtype": "Data", + "label": "Barcode" + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name", + "read_only": 1 + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "reqd": 1 + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "in_list_view": 1, + "label": "UOM", + "options": "UOM", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "invoice_separately_as_consumables", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Invoice Separately as Consumables" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch", + "options": "Batch" + }, + { + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "read_only": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "transfer_qty", + "fieldtype": "Float", + "label": "Transfer Qty", + "read_only": 1 + }, + { + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": "Actual Qty (at source/target)", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "search_index": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-01 15:34:54.226722", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Clinical Procedure Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py b/healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py new file mode 100644 index 0000000..4ce3f80 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, earthians and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ClinicalProcedureItem(Document): + pass diff --git a/healthcare/healthcare/doctype/clinical_procedure_template/__init__.py b/healthcare/healthcare/doctype/clinical_procedure_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js new file mode 100644 index 0000000..2997bd0 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js @@ -0,0 +1,220 @@ +// Copyright (c) 2017, earthians and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Clinical Procedure Template', { + template: function (frm) { + if (!frm.doc.item_code) + frm.set_value('item_code', frm.doc.template); + if (!frm.doc.description) + frm.set_value('description', frm.doc.template); + mark_change_in_item(frm); + }, + + rate: function (frm) { + mark_change_in_item(frm); + }, + + is_billable: function (frm) { + mark_change_in_item(frm); + }, + + item_group: function (frm) { + mark_change_in_item(frm); + }, + + description: function (frm) { + mark_change_in_item(frm); + }, + + medical_department: function (frm) { + mark_change_in_item(frm); + }, + + medical_code: function (frm) { + frm.set_query("medical_code", function () { + return { + filters: { + medical_code_standard: frm.doc.medical_code_standard + } + }; + }); + }, + + refresh: function (frm) { + frm.fields_dict['items'].grid.set_column_disp('barcode', false); + frm.fields_dict['items'].grid.set_column_disp('batch_no', false); + + if (!frm.doc.__islocal) { + cur_frm.add_custom_button(__('Change Item Code'), function () { + change_template_code(frm.doc); + }); + } + + frm.set_query('item', function() { + return { + filters: { + 'disabled': false, + 'is_stock_item': false + } + }; + }); + }, + + link_existing_item: function (frm) { + if (frm.doc.link_existing_item) { + frm.set_value('item_code', ''); + } else { + frm.set_value('item', ''); + } + }, + + item: function (frm) { + if (frm.doc.item) { + frappe.db.get_value('Item', frm.doc.item, ['item_group', 'description']) + .then(r => { + frm.set_value({ + 'item_group': r.message.item_group, + 'description': r.message.description, + 'item_code': frm.doc.item + }); + }) + } + } +}); + +let mark_change_in_item = function (frm) { + if (!frm.doc.__islocal || frm.doc.link_existing_item) { + frm.doc.change_in_item = 1; + } +}; + +let change_template_code = function (doc) { + let d = new frappe.ui.Dialog({ + title: __('Change Item Code'), + fields: [ + { + 'fieldtype': 'Data', + 'label': 'Item Code', + 'fieldname': 'item_code', + reqd: 1 + } + ], + primary_action: function () { + let values = d.get_values(); + + if (values) { + frappe.call({ + 'method': 'healthcare.healthcare.doctype.clinical_procedure_template.clinical_procedure_template.change_item_code_from_template', + 'args': { item_code: values.item_code, doc: doc }, + callback: function () { + cur_frm.reload_doc(); + frappe.show_alert({ + message: 'Item Code renamed successfully', + indicator: 'green' + }); + } + }); + } + d.hide(); + }, + primary_action_label: __('Change Item Code') + }); + d.show(); + + d.set_values({ + 'item_code': doc.item_code + }); +}; + +frappe.ui.form.on('Clinical Procedure Item', { + qty: function (frm, cdt, cdn) { + let d = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, 'transfer_qty', d.qty * d.conversion_factor); + }, + + uom: function (doc, cdt, cdn) { + let d = locals[cdt][cdn]; + if (d.uom && d.item_code) { + return frappe.call({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_uom_details', + args: { + item_code: d.item_code, + uom: d.uom, + qty: d.qty + }, + callback: function (r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, r.message); + } + } + }); + } + }, + + item_code: function (frm, cdt, cdn) { + let d = locals[cdt][cdn]; + if (d.item_code) { + let args = { + 'item_code': d.item_code, + 'transfer_qty': d.transfer_qty, + 'quantity': d.qty + }; + return frappe.call({ + method: 'healthcare.healthcare.doctype.clinical_procedure_template.clinical_procedure_template.get_item_details', + args: { args: args }, + callback: function (r) { + if (r.message) { + let d = locals[cdt][cdn]; + $.each(r.message, function (k, v) { + d[k] = v; + }); + refresh_field('items'); + } + } + }); + } + } +}); + +// List Stock items +cur_frm.set_query('item_code', 'items', function () { + return { + filters: { + is_stock_item: 1 + } + }; +}); + +frappe.tour['Clinical Procedure Template'] = [ + { + fieldname: 'template', + title: __('Template Name'), + description: __('Enter a name for the Clinical Procedure Template') + }, + { + fieldname: 'item_code', + title: __('Item Code'), + description: __('Set the Item Code which will be used for billing the Clinical Procedure.') + }, + { + fieldname: 'item_group', + title: __('Item Group'), + description: __('Select an Item Group for the Clinical Procedure Item.') + }, + { + fieldname: 'is_billable', + title: __('Clinical Procedure Rate'), + description: __('Check this if the Clinical Procedure is billable and also set the rate.') + }, + { + fieldname: 'consume_stock', + title: __('Allow Stock Consumption'), + description: __('Check this if the Clinical Procedure utilises consumables. Click ') + "here" + __(' to know more') + + }, + { + fieldname: 'medical_department', + title: __('Medical Department'), + description: __('You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.') + } +]; diff --git a/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json new file mode 100644 index 0000000..02c87d7 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json @@ -0,0 +1,286 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:template", + "beta": 1, + "creation": "2017-10-05 14:59:55.438359", + "description": "Procedure Template", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "template", + "link_existing_item", + "item", + "item_code", + "item_group", + "description", + "column_break_5", + "disabled", + "is_billable", + "rate", + "medical_department", + "pre_op_nursing_checklist_template", + "post_op_nursing_checklist_template", + "medical_coding_section", + "medical_code_standard", + "medical_code", + "consumables", + "consume_stock", + "items", + "sample_collection", + "sample", + "sample_uom", + "sample_qty", + "column_break_21", + "sample_details", + "change_in_item" + ], + "fields": [ + { + "fieldname": "template", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Template Name", + "reqd": 1, + "unique": 1 + }, + { + "depends_on": "eval:!doc.link_existing_item || !doc.__islocal", + "fieldname": "item_code", + "fieldtype": "Data", + "label": "Item Code", + "mandatory_depends_on": "eval:!doc.link_existing_item", + "read_only_depends_on": "eval: !doc.__islocal" + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item Group", + "options": "Item Group", + "read_only_depends_on": "eval:doc.link_existing_item", + "reqd": 1 + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:!doc.link_existing_item || !doc.__islocal;", + "fieldname": "is_billable", + "fieldtype": "Check", + "label": "Is Billable" + }, + { + "depends_on": "eval:doc.is_billable && (!doc.link_existing_item || !doc.__islocal)", + "fieldname": "rate", + "fieldtype": "Float", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Rate", + "mandatory_depends_on": "eval:doc.is_billable && !doc.link_existing_item" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Description", + "no_copy": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "consume_stock", + "fieldtype": "Check", + "label": "Allow Stock Consumption", + "search_index": 1 + }, + { + "fieldname": "consumables", + "fieldtype": "Section Break", + "label": "Consumables" + }, + { + "depends_on": "eval:doc.consume_stock == 1", + "fieldname": "items", + "fieldtype": "Table", + "ignore_user_permissions": 1, + "label": "Items", + "options": "Clinical Procedure Item" + }, + { + "collapsible": 1, + "fieldname": "sample_collection", + "fieldtype": "Section Break", + "label": "Sample Collection" + }, + { + "fieldname": "sample", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Sample", + "options": "Lab Test Sample" + }, + { + "fetch_from": "sample.sample_uom", + "fieldname": "sample_uom", + "fieldtype": "Data", + "label": "Sample UOM", + "read_only": 1 + }, + { + "fieldname": "sample_qty", + "fieldtype": "Float", + "label": "Quantity" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "fieldname": "sample_details", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Collection Details" + }, + { + "default": "0", + "fieldname": "change_in_item", + "fieldtype": "Check", + "hidden": 1, + "label": "Change In Item", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "depends_on": "eval: !doc.__islocal || doc.link_existing_item", + "fieldname": "item", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item", + "mandatory_depends_on": "eval:doc.link_existing_item", + "no_copy": 1, + "options": "Item", + "read_only_depends_on": "eval: !doc.__islocal" + }, + { + "collapsible": 1, + "fieldname": "medical_coding_section", + "fieldtype": "Section Break", + "label": "Medical Coding" + }, + { + "fieldname": "medical_code_standard", + "fieldtype": "Link", + "label": "Medical Code Standard", + "options": "Medical Code Standard" + }, + { + "depends_on": "medical_code_standard", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code" + }, + { + "fieldname": "pre_op_nursing_checklist_template", + "fieldtype": "Link", + "label": "Pre-op Nursing Checklist Template", + "options": "Nursing Checklist Template" + }, + { + "fieldname": "post_op_nursing_checklist_template", + "fieldtype": "Link", + "label": "Post-op Nursing Checklist Template", + "options": "Nursing Checklist Template" + }, + { + "default": "0", + "depends_on": "eval: doc.__islocal", + "fieldname": "link_existing_item", + "fieldtype": "Check", + "label": "Link existing Item" + } + ], + "links": [], + "modified": "2022-06-22 06:42:56.910453", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Clinical Procedure Template", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "template", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "template", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py new file mode 100644 index 0000000..41f1954 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, earthians and contributors +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc +from frappe.utils import today + + +class ClinicalProcedureTemplate(Document): + def before_insert(self): + if self.link_existing_item and self.item: + price_list = frappe.db.get_all( + "Item Price", {"item_code": self.item}, ["price_list_rate"], order_by="valid_from desc" + ) + if price_list: + self.rate = price_list[0].get("price_list_rate") + + def validate(self): + self.enable_disable_item() + + def after_insert(self): + if not self.link_existing_item: + create_item_from_template(self) + + def on_update(self): + if self.change_in_item: + update_item_and_item_price(self) + + def enable_disable_item(self): + if self.is_billable: + if self.disabled: + frappe.db.set_value("Item", self.item, "disabled", 1) + else: + frappe.db.set_value("Item", self.item, "disabled", 0) + + +@frappe.whitelist() +def get_item_details(args=None): + if not isinstance(args, dict): + args = json.loads(args) + + item = frappe.db.get_all( + "Item", filters={"disabled": 0, "name": args.get("item_code")}, fields=["stock_uom", "item_name"] + ) + + if not item: + frappe.throw(_("Item {0} is not active").format(args.get("item_code"))) + + item = item[0] + ret = { + "uom": item.stock_uom, + "stock_uom": item.stock_uom, + "item_name": item.item_name, + "qty": 1, + "transfer_qty": 0, + "conversion_factor": 1, + } + return ret + + +def create_item_from_template(doc): + disabled = doc.disabled + if doc.is_billable and not doc.disabled: + disabled = 0 + + uom = frappe.db.exists("UOM", "Unit") or frappe.db.get_single_value("Stock Settings", "stock_uom") + item = frappe.get_doc( + { + "doctype": "Item", + "item_code": doc.template, + "item_name": doc.template, + "item_group": doc.item_group, + "description": doc.description, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "disabled": disabled, + "stock_uom": uom, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + make_item_price(item.name, doc.rate) + doc.db_set("item", item.name) + + +def make_item_price(item, item_price): + price_list_name = frappe.db.get_value( + "Selling Settings", None, "selling_price_list" + ) or frappe.db.get_value("Price List", {"selling": 1}) + frappe.get_doc( + { + "doctype": "Item Price", + "price_list": price_list_name, + "item_code": item, + "price_list_rate": item_price, + "valid_from": today(), + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + +@frappe.whitelist() +def change_item_code_from_template(item_code, doc): + doc = frappe._dict(json.loads(doc)) + + if frappe.db.exists("Item", {"item_code": item_code}): + frappe.throw(_("Item with Item Code {0} already exists").format(item_code)) + else: + rename_doc("Item", doc.item_code, item_code, ignore_permissions=True) + frappe.db.set_value("Clinical Procedure Template", doc.name, "item_code", item_code) + return + + +def update_item_and_item_price(doc): + if doc.is_billable and doc.item: + item_name = doc.lab_test_name if doc.get("doctype") == "Lab Test Template" else doc.template + rate = doc.lab_test_rate if doc.get("doctype") == "Lab Test Template" else doc.rate + item_group = doc.lab_test_group if doc.get("doctype") == "Lab Test Template" else doc.item_group + + item_doc = frappe.get_doc("Item", {"item_code": doc.item}) + item_doc.item_name = item_name + item_doc.item_group = item_group + item_doc.description = ( + doc.description if doc.get("doctype") == "Clinical Procedure Template" else "" + ) + item_doc.disabled = 0 + item_doc.save(ignore_permissions=True) + if rate: + if not frappe.db.exists("Item Price", {"item_code": doc.item, "valid_from": today()}): + make_item_price(doc.item, rate) + else: + item_price = frappe.get_doc("Item Price", {"item_code": doc.item}) + item_price.item_name = item_name + item_price.valid_from = today() + item_price.price_list_rate = rate + item_price.save() + + elif not doc.is_billable and doc.item and not doc.link_existing_item: + frappe.db.set_value("Item", doc.item, "disabled", 1) + + doc.db_set("change_in_item", 0) diff --git a/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py new file mode 100644 index 0000000..bc16756 --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py @@ -0,0 +1,8 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "procedure_template", + "transactions": [{"label": _("Consultations"), "items": ["Clinical Procedure"]}], + } diff --git a/healthcare/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py b/healthcare/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py new file mode 100644 index 0000000..c6b742a --- /dev/null +++ b/healthcare/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, earthians and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestClinicalProcedureTemplate(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/codification_table/__init__.py b/healthcare/healthcare/doctype/codification_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/codification_table/codification_table.json b/healthcare/healthcare/doctype/codification_table/codification_table.json new file mode 100644 index 0000000..9a917b4 --- /dev/null +++ b/healthcare/healthcare/doctype/codification_table/codification_table.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2017-06-22 13:09:23.159579", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "medical_code", + "code", + "description" + ], + "fields": [ + { + "fieldname": "medical_code", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Medical Code", + "options": "Medical Code", + "reqd": 1 + }, + { + "fetch_from": "medical_code.code", + "fieldname": "code", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Code", + "read_only": 1 + }, + { + "fetch_from": "medical_code.description", + "fieldname": "description", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Description", + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-02-26 13:17:49.016293", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Codification Table", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/codification_table/codification_table.py b/healthcare/healthcare/doctype/codification_table/codification_table.py new file mode 100644 index 0000000..069904b --- /dev/null +++ b/healthcare/healthcare/doctype/codification_table/codification_table.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class CodificationTable(Document): + pass diff --git a/healthcare/healthcare/doctype/complaint/__init__.py b/healthcare/healthcare/doctype/complaint/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/complaint/complaint.js b/healthcare/healthcare/doctype/complaint/complaint.js new file mode 100644 index 0000000..a670218 --- /dev/null +++ b/healthcare/healthcare/doctype/complaint/complaint.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Complaint', { +}); diff --git a/healthcare/healthcare/doctype/complaint/complaint.json b/healthcare/healthcare/doctype/complaint/complaint.json new file mode 100644 index 0000000..f600838 --- /dev/null +++ b/healthcare/healthcare/doctype/complaint/complaint.json @@ -0,0 +1,116 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:complaints", + "beta": 1, + "creation": "2017-02-15 12:25:28.045267", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "complaints", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Complaints", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-05 11:18:42.017864", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Complaint", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "complaints", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "complaints", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/complaint/complaint.py b/healthcare/healthcare/doctype/complaint/complaint.py new file mode 100644 index 0000000..33eef46 --- /dev/null +++ b/healthcare/healthcare/doctype/complaint/complaint.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class Complaint(Document): + pass diff --git a/healthcare/healthcare/doctype/complaint/test_complaint.py b/healthcare/healthcare/doctype/complaint/test_complaint.py new file mode 100644 index 0000000..401ecc5 --- /dev/null +++ b/healthcare/healthcare/doctype/complaint/test_complaint.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestComplaint(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/descriptive_test_result/__init__.py b/healthcare/healthcare/doctype/descriptive_test_result/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.json b/healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.json new file mode 100644 index 0000000..fcd3828 --- /dev/null +++ b/healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.json @@ -0,0 +1,74 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 15:12:36.202380", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lab_test_particulars", + "result_value", + "allow_blank", + "template", + "require_result_value" + ], + "fields": [ + { + "fieldname": "lab_test_particulars", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Particulars", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value == 1", + "fieldname": "result_value", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Value" + }, + { + "fieldname": "template", + "fieldtype": "Link", + "hidden": 1, + "label": "Template", + "options": "Lab Test Template", + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "require_result_value", + "fieldtype": "Check", + "hidden": 1, + "label": "Require Result Value", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "default": "1", + "fieldname": "allow_blank", + "fieldtype": "Check", + "label": "Allow Blank", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-23 12:33:47.693065", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Descriptive Test Result", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.py b/healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.py new file mode 100644 index 0000000..6db1082 --- /dev/null +++ b/healthcare/healthcare/doctype/descriptive_test_result/descriptive_test_result.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class DescriptiveTestResult(Document): + pass diff --git a/healthcare/healthcare/doctype/descriptive_test_template/__init__.py b/healthcare/healthcare/doctype/descriptive_test_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.json b/healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.json new file mode 100644 index 0000000..9ee8f4f --- /dev/null +++ b/healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 16:12:12.394200", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "particulars", + "allow_blank" + ], + "fields": [ + { + "fieldname": "particulars", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Result Component" + }, + { + "default": "0", + "fieldname": "allow_blank", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Blank" + } + ], + "istable": 1, + "links": [], + "modified": "2020-06-24 14:03:51.728863", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Descriptive Test Template", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.py b/healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.py new file mode 100644 index 0000000..91a8dba --- /dev/null +++ b/healthcare/healthcare/doctype/descriptive_test_template/descriptive_test_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class DescriptiveTestTemplate(Document): + pass diff --git a/healthcare/healthcare/doctype/diagnosis/__init__.py b/healthcare/healthcare/doctype/diagnosis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/diagnosis/diagnosis.js b/healthcare/healthcare/doctype/diagnosis/diagnosis.js new file mode 100644 index 0000000..fb2557f --- /dev/null +++ b/healthcare/healthcare/doctype/diagnosis/diagnosis.js @@ -0,0 +1,5 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Diagnosis', { +}); diff --git a/healthcare/healthcare/doctype/diagnosis/diagnosis.json b/healthcare/healthcare/doctype/diagnosis/diagnosis.json new file mode 100644 index 0000000..936c2c5 --- /dev/null +++ b/healthcare/healthcare/doctype/diagnosis/diagnosis.json @@ -0,0 +1,116 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:diagnosis", + "beta": 1, + "creation": "2017-02-15 12:23:59.341108", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "diagnosis", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Diagnosis", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-05 11:25:46.107435", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Diagnosis", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "diagnosis", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "diagnosis", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/diagnosis/diagnosis.py b/healthcare/healthcare/doctype/diagnosis/diagnosis.py new file mode 100644 index 0000000..f4f4ac8 --- /dev/null +++ b/healthcare/healthcare/doctype/diagnosis/diagnosis.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class Diagnosis(Document): + pass diff --git a/healthcare/healthcare/doctype/diagnosis/test_diagnosis.py b/healthcare/healthcare/doctype/diagnosis/test_diagnosis.py new file mode 100644 index 0000000..fd8be28 --- /dev/null +++ b/healthcare/healthcare/doctype/diagnosis/test_diagnosis.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Diagnosis') + + +class TestDiagnosis(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/dosage_form/__init__.py b/healthcare/healthcare/doctype/dosage_form/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/dosage_form/dosage_form.js b/healthcare/healthcare/doctype/dosage_form/dosage_form.js new file mode 100644 index 0000000..60e9696 --- /dev/null +++ b/healthcare/healthcare/doctype/dosage_form/dosage_form.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Dosage Form', { +}); diff --git a/healthcare/healthcare/doctype/dosage_form/dosage_form.json b/healthcare/healthcare/doctype/dosage_form/dosage_form.json new file mode 100644 index 0000000..350aaed --- /dev/null +++ b/healthcare/healthcare/doctype/dosage_form/dosage_form.json @@ -0,0 +1,114 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:dosage_form", + "beta": 1, + "creation": "2017-04-08 12:04:33.987972", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "dosage_form", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Dosage Form", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-05 11:24:57.888091", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Dosage Form", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/dosage_form/dosage_form.py b/healthcare/healthcare/doctype/dosage_form/dosage_form.py new file mode 100644 index 0000000..a1cd1e3 --- /dev/null +++ b/healthcare/healthcare/doctype/dosage_form/dosage_form.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, ESS LLP and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class DosageForm(Document): + pass diff --git a/healthcare/healthcare/doctype/dosage_form/test_dosage_form.py b/healthcare/healthcare/doctype/dosage_form/test_dosage_form.py new file mode 100644 index 0000000..97296e6 --- /dev/null +++ b/healthcare/healthcare/doctype/dosage_form/test_dosage_form.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestDosageForm(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/dosage_strength/__init__.py b/healthcare/healthcare/doctype/dosage_strength/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/dosage_strength/dosage_strength.json b/healthcare/healthcare/doctype/dosage_strength/dosage_strength.json new file mode 100644 index 0000000..da4f1a7 --- /dev/null +++ b/healthcare/healthcare/doctype/dosage_strength/dosage_strength.json @@ -0,0 +1,102 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-02-14 15:40:14.385707", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "strength", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Strength", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "strength_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-08-31 14:11:59.874645", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Dosage Strength", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/dosage_strength/dosage_strength.py b/healthcare/healthcare/doctype/dosage_strength/dosage_strength.py new file mode 100644 index 0000000..429a667 --- /dev/null +++ b/healthcare/healthcare/doctype/dosage_strength/dosage_strength.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class DosageStrength(Document): + pass diff --git a/healthcare/healthcare/doctype/drug_prescription/__init__.py b/healthcare/healthcare/doctype/drug_prescription/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/drug_prescription/drug_prescription.json b/healthcare/healthcare/doctype/drug_prescription/drug_prescription.json new file mode 100644 index 0000000..a65c566 --- /dev/null +++ b/healthcare/healthcare/doctype/drug_prescription/drug_prescription.json @@ -0,0 +1,122 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-09-16 16:41:45.533374", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "drug_code", + "drug_name", + "dosage", + "period", + "dosage_form", + "column_break_7", + "comment", + "usage_interval", + "interval", + "interval_uom", + "update_schedule" + ], + "fields": [ + { + "fieldname": "drug_code", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Drug", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "drug_code.item_name", + "fieldname": "drug_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Drug Name / Description" + }, + { + "fieldname": "dosage", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Dosage", + "options": "Prescription Dosage", + "reqd": 1 + }, + { + "fieldname": "period", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Period", + "options": "Prescription Duration", + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "dosage_form", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Dosage Form", + "options": "Dosage Form", + "reqd": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "comment", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Comment" + }, + { + "depends_on": "usage_interval", + "fieldname": "interval", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Interval" + }, + { + "default": "1", + "depends_on": "usage_interval", + "fieldname": "update_schedule", + "fieldtype": "Check", + "hidden": 1, + "label": "Update Schedule", + "print_hide": 1, + "report_hide": 1 + }, + { + "depends_on": "use_interval", + "fieldname": "interval_uom", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Interval UOM", + "options": "\nHour\nDay" + }, + { + "default": "0", + "fieldname": "usage_interval", + "fieldtype": "Check", + "hidden": 1, + "label": "Dosage by Time Interval" + } + ], + "istable": 1, + "links": [], + "modified": "2021-06-11 11:53:06.343704", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Drug Prescription", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/drug_prescription/drug_prescription.py b/healthcare/healthcare/doctype/drug_prescription/drug_prescription.py new file mode 100644 index 0000000..1a4aa13 --- /dev/null +++ b/healthcare/healthcare/doctype/drug_prescription/drug_prescription.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document + + +class DrugPrescription(Document): + def get_quantity(self): + quantity = 0 + dosage = None + period = None + + if self.dosage: + dosage = frappe.get_doc("Prescription Dosage", self.dosage) + for item in dosage.dosage_strength: + quantity += item.strength + if self.period and self.interval: + period = frappe.get_doc("Prescription Duration", self.period) + if self.interval < period.get_days(): + quantity = quantity * (period.get_days() / self.interval) + + elif self.interval and self.interval_uom and self.period: + period = frappe.get_doc("Prescription Duration", self.period) + interval_in = self.interval_uom + if interval_in == "Day" and self.interval < period.get_days(): + quantity = period.get_days() / self.interval + elif interval_in == "Hour" and self.interval < period.get_hours(): + quantity = period.get_hours() / self.interval + if quantity > 0: + return quantity + else: + return 1 diff --git a/healthcare/healthcare/doctype/exercise/__init__.py b/healthcare/healthcare/doctype/exercise/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/exercise/exercise.json b/healthcare/healthcare/doctype/exercise/exercise.json new file mode 100644 index 0000000..683cc6d --- /dev/null +++ b/healthcare/healthcare/doctype/exercise/exercise.json @@ -0,0 +1,62 @@ +{ + "actions": [], + "creation": "2020-03-11 09:25:00.968572", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "exercise_type", + "difficulty_level", + "counts_target", + "counts_completed", + "assistance_level" + ], + "fields": [ + { + "fieldname": "exercise_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Exercise Type", + "options": "Exercise Type", + "reqd": 1 + }, + { + "fetch_from": "exercise_type.difficulty_level", + "fieldname": "difficulty_level", + "fieldtype": "Link", + "label": "Difficulty Level", + "options": "Exercise Difficulty Level" + }, + { + "fieldname": "counts_target", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Counts Target" + }, + { + "depends_on": "eval:doc.parenttype==\"Therapy\";", + "fieldname": "counts_completed", + "fieldtype": "Int", + "label": "Counts Completed", + "no_copy": 1 + }, + { + "fieldname": "assistance_level", + "fieldtype": "Select", + "label": "Assistance Level", + "options": "\nPassive\nActive Assist\nActive" + } + ], + "istable": 1, + "links": [], + "modified": "2020-11-04 18:20:25.583491", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/exercise/exercise.py b/healthcare/healthcare/doctype/exercise/exercise.py new file mode 100644 index 0000000..f7a5064 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise/exercise.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class Exercise(Document): + pass diff --git a/healthcare/healthcare/doctype/exercise_difficulty_level/__init__.py b/healthcare/healthcare/doctype/exercise_difficulty_level/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js b/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js new file mode 100644 index 0000000..e35fc62 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Exercise Difficulty Level', { + // refresh: function(frm) { + + // } +}); diff --git a/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json b/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json new file mode 100644 index 0000000..a6aed75 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "autoname": "field:difficulty_level", + "creation": "2020-03-29 21:12:55.835941", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "difficulty_level" + ], + "fields": [ + { + "fieldname": "difficulty_level", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Difficulty Level", + "reqd": 1, + "unique": 1 + } + ], + "links": [], + "modified": "2020-03-31 23:14:33.554066", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise Difficulty Level", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py b/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py new file mode 100644 index 0000000..f2ccea7 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class ExerciseDifficultyLevel(Document): + pass diff --git a/healthcare/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py b/healthcare/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py new file mode 100644 index 0000000..3035fe4 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestExerciseDifficultyLevel(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/exercise_type/__init__.py b/healthcare/healthcare/doctype/exercise_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/exercise_type/exercise_type.js b/healthcare/healthcare/doctype/exercise_type/exercise_type.js new file mode 100644 index 0000000..5bb8ea3 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_type/exercise_type.js @@ -0,0 +1,186 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Exercise Type', { + refresh: function(frm) { + let wrapper = frm.fields_dict.steps_html.wrapper; + + frm.ExerciseEditor = new erpnext.ExerciseEditor(frm, wrapper); + } +}); + +erpnext.ExerciseEditor = class ExerciseEditor { + constructor(frm, wrapper) { + this.wrapper = wrapper; + this.frm = frm; + this.make(frm, wrapper); + } + + make(frm, wrapper) { + $(this.wrapper).empty(); + + this.exercise_toolbar = $('

\ + ').appendTo(this.wrapper); + + this.exercise_cards = $('

').appendTo(this.wrapper); + + this.row = $('
').appendTo(this.wrapper); + + let me = this; + + this.exercise_toolbar.find(".btn-add") + .html(__('Add')) + .on("click", function() { + me.show_add_card_dialog(frm); + }); + + if (frm.doc.steps_table && frm.doc.steps_table.length > 0) { + this.make_cards(frm); + this.make_buttons(frm); + } + } + + make_cards(frm) { + var me = this; + $(me.exercise_cards).empty(); + + $.each(frm.doc.steps_table, function(i, step) { + $(repl(` +
+
+
+ ... +

%(title)s

+

%(description)s

+
+ +
+
`, {image_src: step.image, title: step.title, description: step.description, col_id: "col-"+i, card_id: "card-"+i, id: i})).appendTo(me.row); + }); + } + + make_buttons(frm) { + let me = this; + $('.btn-edit').on('click', function() { + let id = $(this).attr('data-id'); + me.show_edit_card_dialog(frm, id); + }); + + $('.btn-del').on('click', function() { + let id = $(this).attr('data-id'); + $('#card-'+id).addClass("zoom-out"); + + setTimeout(() => { + // not using grid_rows[id].remove because + // grid_rows is not defined when the table is hidden + frm.doc.steps_table.pop(id); + frm.refresh_field('steps_table'); + $('#col-'+id).remove(); + frm.dirty(); + }, 300); + }); + } + + show_add_card_dialog(frm) { + let me = this; + let d = new frappe.ui.Dialog({ + title: __('Add Exercise Step'), + fields: [ + { + "label": "Title", + "fieldname": "title", + "fieldtype": "Data", + "reqd": 1 + }, + { + "label": "Attach Image", + "fieldname": "image", + "fieldtype": "Attach Image" + }, + { + "label": "Step Description", + "fieldname": "step_description", + "fieldtype": "Long Text" + } + ], + primary_action: function() { + let data = d.get_values(); + let i = 0; + if (frm.doc.steps_table) { + i = frm.doc.steps_table.length; + } + $(repl(` +
+
+
+ ... +

%(title)s

+

%(description)s

+
+ +
+
`, {image_src: data.image, title: data.title, description: data.step_description, col_id: "col-"+i, card_id: "card-"+i, id: i})).appendTo(me.row); + let step = frappe.model.add_child(frm.doc, 'Exercise Type Step', 'steps_table'); + step.title = data.title; + step.image = data.image; + step.description = data.step_description; + me.make_buttons(frm); + frm.refresh_field('steps_table'); + d.hide(); + }, + primary_action_label: __('Add') + }); + d.show(); + } + + show_edit_card_dialog(frm, id) { + let new_dialog = new frappe.ui.Dialog({ + title: __("Edit Exercise Step"), + fields: [ + { + "label": "Title", + "fieldname": "title", + "fieldtype": "Data", + "reqd": 1 + }, + { + "label": "Attach Image", + "fieldname": "image", + "fieldtype": "Attach Image" + }, + { + "label": "Step Description", + "fieldname": "step_description", + "fieldtype": "Long Text" + } + ], + primary_action: () => { + let data = new_dialog.get_values(); + $('#card-'+id).find('.card-title').html(data.title); + $('#card-'+id).find('img').attr('src', data.image); + $('#card-'+id).find('.card-text').html(data.step_description); + + frm.doc.steps_table[id].title = data.title; + frm.doc.steps_table[id].image = data.image; + frm.doc.steps_table[id].description = data.step_description; + refresh_field('steps_table'); + frm.dirty(); + new_dialog.hide(); + }, + primary_action_label: __("Edit"), + }); + + new_dialog.set_values({ + title: frm.doc.steps_table[id].title, + image: frm.doc.steps_table[id].image, + step_description: frm.doc.steps_table[id].description + }); + new_dialog.show(); + } +}; diff --git a/healthcare/healthcare/doctype/exercise_type/exercise_type.json b/healthcare/healthcare/doctype/exercise_type/exercise_type.json new file mode 100644 index 0000000..0db9c6e --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_type/exercise_type.json @@ -0,0 +1,144 @@ +{ + "actions": [], + "creation": "2020-03-29 21:37:03.366344", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "exercise_name", + "body_parts", + "column_break_3", + "difficulty_level", + "section_break_5", + "description", + "section_break_7", + "exercise_steps", + "column_break_9", + "exercise_video", + "section_break_11", + "steps_html", + "section_break_13", + "steps_table" + ], + "fields": [ + { + "fieldname": "exercise_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Exercise Name", + "reqd": 1 + }, + { + "fieldname": "difficulty_level", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Difficulty Level", + "options": "Exercise Difficulty Level" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Long Text", + "label": "Description" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "exercise_steps", + "fieldtype": "Attach", + "label": "Exercise Instructions" + }, + { + "fieldname": "exercise_video", + "fieldtype": "Link", + "label": "Exercise Video", + "options": "Video" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "steps_html", + "fieldtype": "HTML", + "label": "Steps" + }, + { + "fieldname": "steps_table", + "fieldtype": "Table", + "hidden": 1, + "label": "Steps Table", + "options": "Exercise Type Step" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "label": "Exercise Steps" + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break" + }, + { + "fieldname": "body_parts", + "fieldtype": "Table MultiSelect", + "label": "Body Parts", + "options": "Body Part Link" + } + ], + "links": [], + "modified": "2020-04-21 13:05:36.555060", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/exercise_type/exercise_type.py b/healthcare/healthcare/doctype/exercise_type/exercise_type.py new file mode 100644 index 0000000..27dbfe7 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_type/exercise_type.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class ExerciseType(Document): + def autoname(self): + if self.difficulty_level: + self.name = " - ".join(filter(None, [self.exercise_name, self.difficulty_level])) + else: + self.name = self.exercise_name diff --git a/healthcare/healthcare/doctype/exercise_type/test_exercise_type.py b/healthcare/healthcare/doctype/exercise_type/test_exercise_type.py new file mode 100644 index 0000000..c6d0014 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_type/test_exercise_type.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestExerciseType(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/exercise_type_step/__init__.py b/healthcare/healthcare/doctype/exercise_type_step/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.json b/healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.json new file mode 100644 index 0000000..b37ff00 --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.json @@ -0,0 +1,44 @@ +{ + "actions": [], + "creation": "2020-03-31 23:01:18.761967", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "image", + "description" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "label": "Image" + }, + { + "fieldname": "description", + "fieldtype": "Long Text", + "in_list_view": 1, + "label": "Description" + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-02 20:39:34.258512", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise Type Step", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.py b/healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.py new file mode 100644 index 0000000..551407d --- /dev/null +++ b/healthcare/healthcare/doctype/exercise_type_step/exercise_type_step.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class ExerciseTypeStep(Document): + pass diff --git a/healthcare/healthcare/doctype/fee_validity/__init__.py b/healthcare/healthcare/doctype/fee_validity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/fee_validity/fee_validity.js b/healthcare/healthcare/doctype/fee_validity/fee_validity.js new file mode 100644 index 0000000..7ea2213 --- /dev/null +++ b/healthcare/healthcare/doctype/fee_validity/fee_validity.js @@ -0,0 +1,5 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Fee Validity', { +}); diff --git a/healthcare/healthcare/doctype/fee_validity/fee_validity.json b/healthcare/healthcare/doctype/fee_validity/fee_validity.json new file mode 100644 index 0000000..d76b42e --- /dev/null +++ b/healthcare/healthcare/doctype/fee_validity/fee_validity.json @@ -0,0 +1,134 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "beta": 1, + "creation": "2017-01-05 10:56:29.564806", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "practitioner", + "patient", + "column_break_3", + "status", + "section_break_5", + "section_break_3", + "max_visits", + "visited", + "ref_appointments", + "column_break_6", + "start_date", + "valid_till" + ], + "fields": [ + { + "fieldname": "practitioner", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "visited", + "fieldtype": "Int", + "label": "Visits Completed", + "read_only": 1 + }, + { + "fieldname": "valid_till", + "fieldtype": "Date", + "label": "Valid Till", + "read_only": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break", + "label": "Validity", + "read_only": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "max_visits", + "fieldtype": "Int", + "label": "Max number of visit", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Completed\nPending", + "read_only": 1 + }, + { + "fetch_from": "ref_appointment.appointment_date", + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date", + "read_only": 1 + }, + { + "fieldname": "ref_appointments", + "fieldtype": "Table MultiSelect", + "label": "Reference Appointments", + "options": "Fee Validity Reference", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_5", + "fieldtype": "Section Break" + } + ], + "in_create": 1, + "links": [], + "modified": "2021-08-26 10:51:05.609349", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Fee Validity", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "search_fields": "practitioner, patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "practitioner" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/fee_validity/fee_validity.py b/healthcare/healthcare/doctype/fee_validity/fee_validity.py new file mode 100644 index 0000000..1fb0df7 --- /dev/null +++ b/healthcare/healthcare/doctype/fee_validity/fee_validity.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import datetime + +import frappe +from frappe.model.document import Document +from frappe.utils import getdate + + +class FeeValidity(Document): + def validate(self): + self.update_status() + + def update_status(self): + if self.visited >= self.max_visits: + self.status = "Completed" + else: + self.status = "Pending" + + +def create_fee_validity(appointment): + if not check_is_new_patient(appointment): + return + + fee_validity = frappe.new_doc("Fee Validity") + fee_validity.practitioner = appointment.practitioner + fee_validity.patient = appointment.patient + fee_validity.max_visits = frappe.db.get_single_value("Healthcare Settings", "max_visits") or 1 + valid_days = frappe.db.get_single_value("Healthcare Settings", "valid_days") or 1 + fee_validity.visited = 0 + fee_validity.start_date = getdate(appointment.appointment_date) + fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta( + days=int(valid_days) + ) + fee_validity.save(ignore_permissions=True) + return fee_validity + + +def check_is_new_patient(appointment): + validity_exists = frappe.db.exists( + "Fee Validity", {"practitioner": appointment.practitioner, "patient": appointment.patient} + ) + if validity_exists: + return False + + appointment_exists = frappe.db.get_all( + "Patient Appointment", + { + "name": ("!=", appointment.name), + "status": ("!=", "Cancelled"), + "patient": appointment.patient, + "practitioner": appointment.practitioner, + }, + ) + if len(appointment_exists) and appointment_exists[0]: + return False + return True diff --git a/healthcare/healthcare/doctype/fee_validity/test_fee_validity.py b/healthcare/healthcare/doctype/fee_validity/test_fee_validity.py new file mode 100644 index 0000000..ec23ba9 --- /dev/null +++ b/healthcare/healthcare/doctype/fee_validity/test_fee_validity.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import add_days, nowdate + +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_appointment, + create_healthcare_docs, + create_healthcare_service_items, +) + +test_dependencies = ["Company"] + + +class TestFeeValidity(FrappeTestCase): + def setUp(self): + frappe.db.sql("""delete from `tabPatient Appointment`""") + frappe.db.sql("""delete from `tabFee Validity`""") + frappe.db.sql("""delete from `tabPatient`""") + make_pos_profile() + + def test_fee_validity(self): + item = create_healthcare_service_items() + healthcare_settings = frappe.get_single("Healthcare Settings") + healthcare_settings.enable_free_follow_ups = 1 + healthcare_settings.max_visits = 1 + healthcare_settings.valid_days = 7 + healthcare_settings.automate_appointment_invoicing = 1 + healthcare_settings.op_consulting_charge_item = item + healthcare_settings.save(ignore_permissions=True) + patient, practitioner = create_healthcare_docs() + + # For first appointment, invoice is generated. First appointment not considered in fee validity + appointment = create_appointment(patient, practitioner, nowdate()) + invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") + self.assertEqual(invoiced, 1) + + # appointment should not be invoiced as it is within fee validity + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4)) + invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") + self.assertEqual(invoiced, 0) + + # appointment should be invoiced as it is within fee validity but the max_visits are exceeded + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 5), invoice=1) + invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") + self.assertEqual(invoiced, 1) + + # appointment should be invoiced as it is not within fee validity and the max_visits are exceeded + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 10), invoice=1) + invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") + self.assertEqual(invoiced, 1) diff --git a/healthcare/healthcare/doctype/fee_validity_reference/__init__.py b/healthcare/healthcare/doctype/fee_validity_reference/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.json b/healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.json new file mode 100644 index 0000000..40f128e --- /dev/null +++ b/healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-03-13 16:08:42.859996", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "appointment" + ], + "fields": [ + { + "fieldname": "appointment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient Appointment", + "options": "Patient Appointment", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-15 00:27:02.076470", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Fee Validity Reference", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.py b/healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.py new file mode 100644 index 0000000..e051583 --- /dev/null +++ b/healthcare/healthcare/doctype/fee_validity_reference/fee_validity_reference.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class FeeValidityReference(Document): + pass diff --git a/healthcare/healthcare/doctype/healthcare.py b/healthcare/healthcare/doctype/healthcare.py new file mode 100644 index 0000000..795d633 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare.py @@ -0,0 +1,2 @@ +def get_data(): + return [] diff --git a/healthcare/healthcare/doctype/healthcare_activity/__init__.py b/healthcare/healthcare/doctype/healthcare_activity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.js b/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.js new file mode 100644 index 0000000..236d34e --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.js @@ -0,0 +1,26 @@ +// Copyright (c) 2021, healthcare and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Healthcare Activity', { + onload: function(frm) { + + frm.set_query('role', function() { + return { + filters: { + 'disabled': false, + 'desk_access': true + } + }; + }); + + frm.set_query('task_doctype', function() { + return { + filters: { + 'istable': false, + 'issingle': false + } + }; + }); + + } +}); diff --git a/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.json b/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.json new file mode 100644 index 0000000..be57b51 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.json @@ -0,0 +1,88 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:activity", + "creation": "2021-10-28 06:33:43.654812", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "activity", + "description", + "column_break_4", + "activity_duration", + "role", + "task_doctype" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "activity", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Activity", + "reqd": 1, + "unique": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "role", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Role", + "options": "Role" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "task_doctype", + "fieldtype": "Link", + "label": "Task DocType", + "options": "DocType" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "activity_duration", + "fieldtype": "Duration", + "label": "Activity Duration" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2022-07-25 19:29:15.860690", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Activity", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.py b/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.py new file mode 100644 index 0000000..f768ac8 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_activity/healthcare_activity.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, healthcare and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class HealthcareActivity(Document): + pass diff --git a/healthcare/healthcare/doctype/healthcare_activity/test_healthcare_activity.py b/healthcare/healthcare/doctype/healthcare_activity/test_healthcare_activity.py new file mode 100644 index 0000000..2841b02 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_activity/test_healthcare_activity.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, healthcare and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestHealthcareActivity(unittest.TestCase): + pass diff --git a/healthcare/healthcare/doctype/healthcare_practitioner/__init__.py b/healthcare/healthcare/doctype/healthcare_practitioner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js new file mode 100644 index 0000000..44c3998 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js @@ -0,0 +1,144 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Healthcare Practitioner', { + setup: function(frm) { + frm.set_query('account', 'accounts', function(doc, cdt, cdn) { + let d = locals[cdt][cdn]; + return { + filters: { + 'root_type': 'Income', + 'company': d.company, + 'is_group': 0 + } + }; + }); + }, + refresh: function(frm) { + frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Healthcare Practitioner'}; + + if (!frm.is_new()) { + frappe.contacts.render_address_and_contact(frm); + } else { + frappe.contacts.clear_address_and_contact(frm); + } + + frm.set_query('service_unit', 'practitioner_schedules', function(){ + return { + filters: { + 'is_group': false, + 'allow_appointments': true + } + }; + }); + + set_query_service_item(frm, 'inpatient_visit_charge_item'); + set_query_service_item(frm, 'op_consulting_charge_item'); + } +}); + +let set_query_service_item = function(frm, service_item_field) { + frm.set_query(service_item_field, function() { + return { + filters: { + 'is_sales_item': 1, + 'is_stock_item': 0 + } + }; + }); +}; + +frappe.ui.form.on('Healthcare Practitioner', 'user_id',function(frm) { + if (frm.doc.user_id) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'User', + name: frm.doc.user_id + }, + callback: function (data) { + + frappe.model.get_value('Employee', {'user_id': frm.doc.user_id}, 'name', + function(data) { + if (data) { + if (!frm.doc.employee || frm.doc.employee != data.name) + frappe.model.set_value(frm.doctype, frm.docname, 'employee', data.name); + } else { + frappe.model.set_value(frm.doctype, frm.docname, 'employee', ''); + } + } + ); + + if (!frm.doc.first_name || frm.doc.first_name != data.message.first_name) + frappe.model.set_value(frm.doctype,frm.docname, 'first_name', data.message.first_name); + if (!frm.doc.middle_name || frm.doc.middle_name != data.message.middle_name) + frappe.model.set_value(frm.doctype,frm.docname, 'middle_name', data.message.middle_name); + if (!frm.doc.last_name || frm.doc.last_name != data.message.last_name) + frappe.model.set_value(frm.doctype,frm.docname, 'last_name', data.message.last_name); + if (!frm.doc.mobile_phone || frm.doc.mobile_phone != data.message.mobile_no) + frappe.model.set_value(frm.doctype,frm.docname, 'mobile_phone', data.message.mobile_no); + } + }); + } +}); + +frappe.ui.form.on('Healthcare Practitioner', 'employee', function(frm) { + if (frm.doc.employee){ + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Employee', + name: frm.doc.employee + }, + callback: function (data) { + if (!frm.doc.user_id || frm.doc.user_id != data.message.user_id) + frm.set_value('user_id', data.message.user_id); + if (!frm.doc.designation || frm.doc.designation != data.message.designation) + frappe.model.set_value(frm.doctype,frm.docname, 'designation', data.message.designation); + if (!frm.doc.first_name || !frm.doc.user_id){ + frappe.model.set_value(frm.doctype,frm.docname, 'first_name', data.message.first_name); + frappe.model.set_value(frm.doctype,frm.docname, 'middle_name', ''); + frappe.model.set_value(frm.doctype,frm.docname, 'last_name', data.message.last_name); + } + if (!frm.doc.mobile_phone || !frm.doc.user_id) + frappe.model.set_value(frm.doctype,frm.docname, 'mobile_phone', data.message.cell_number); + if (!frm.doc.address || frm.doc.address != data.message.current_address) + frappe.model.set_value(frm.doctype,frm.docname, 'address', data.message.current_address); + } + }); + } +}); + +frappe.tour['Healthcare Practitioner'] = [ + { + fieldname: 'employee', + title: __('Employee'), + description: __('If you want to track Payroll and other HRMS operations for a Practitoner, create an Employee and link it here.') + }, + { + fieldname: 'practitioner_schedules', + title: __('Practitioner Schedules'), + description: __('Set the Practitioner Schedule you just created. This will be used while booking appointments.') + }, + { + fieldname: 'op_consulting_charge_item', + title: __('Out Patient Consulting Charge Item'), + description: __('Create a service item for Out Patient Consulting.') + }, + { + fieldname: 'inpatient_visit_charge_item', + title: __('Inpatient Visit Charge Item'), + description: __('If this Healthcare Practitioner works for the In-Patient Department, create a service item for Inpatient Visits.') + }, + { + fieldname: 'op_consulting_charge', + title: __('Out Patient Consulting Charge'), + description: __('Set the Out Patient Consulting Charge for this Practitioner.') + + }, + { + fieldname: 'inpatient_visit_charge', + title: __('Inpatient Visit Charge'), + description: __('If this Healthcare Practitioner also works for the In-Patient Department, set the inpatient visit charge for this Practitioner.') + } +]; diff --git a/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json new file mode 100644 index 0000000..6482b1a --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json @@ -0,0 +1,339 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2016-02-23 11:20:53.565119", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "basic_details_section", + "naming_series", + "first_name", + "middle_name", + "last_name", + "practitioner_name", + "gender", + "image", + "column_break_7", + "status", + "mobile_phone", + "residence_phone", + "office_phone", + "employee_and_user_details_section", + "employee", + "department", + "designation", + "column_break_17", + "user_id", + "hospital", + "appointments", + "practitioner_schedules", + "charges", + "op_consulting_charge_item", + "op_consulting_charge", + "column_break_18", + "inpatient_visit_charge_item", + "inpatient_visit_charge", + "account_details", + "default_currency", + "accounts", + "address_and_contacts_section", + "address_html", + "column_break_19", + "contact_html" + ], + "fields": [ + { + "fieldname": "first_name", + "fieldtype": "Data", + "label": "First Name", + "no_copy": 1, + "reqd": 1 + }, + { + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name (Optional)", + "no_copy": 1 + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name", + "no_copy": 1 + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee" + }, + { + "fieldname": "user_id", + "fieldtype": "Link", + "label": "User", + "options": "User", + "search_index": 1 + }, + { + "fetch_from": "employee", + "fieldname": "designation", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fieldname": "department", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "hospital", + "fieldtype": "Data", + "label": "Hospital" + }, + { + "fieldname": "mobile_phone", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Mobile" + }, + { + "fieldname": "residence_phone", + "fieldtype": "Data", + "label": "Phone (R)" + }, + { + "fieldname": "office_phone", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Phone (Office)" + }, + { + "collapsible": 1, + "fieldname": "appointments", + "fieldtype": "Section Break", + "label": "Appointments" + }, + { + "fieldname": "practitioner_schedules", + "fieldtype": "Table", + "label": "Practitioner Schedules", + "options": "Practitioner Service Unit Schedule" + }, + { + "collapsible": 1, + "fieldname": "charges", + "fieldtype": "Section Break", + "label": "Charges" + }, + { + "fieldname": "op_consulting_charge_item", + "fieldtype": "Link", + "label": "Out Patient Consulting Charge Item", + "options": "Item" + }, + { + "fieldname": "op_consulting_charge", + "fieldtype": "Currency", + "label": "Out Patient Consulting Charge", + "mandatory_depends_on": "op_consulting_charge_item", + "options": "Currency" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "inpatient_visit_charge_item", + "fieldtype": "Link", + "label": "Inpatient Visit Charge Item", + "options": "Item" + }, + { + "fieldname": "inpatient_visit_charge", + "fieldtype": "Currency", + "label": "Inpatient Visit Charge", + "mandatory_depends_on": "inpatient_visit_charge_item" + }, + { + "depends_on": "eval: !doc.__islocal", + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: !doc.__islocal", + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "account_details", + "fieldtype": "Section Break", + "label": "Account Details" + }, + { + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Income Account", + "options": "Party Account" + }, + { + "fieldname": "default_currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Default Currency", + "no_copy": 1, + "options": "Currency", + "print_hide": 1, + "report_hide": 1 + }, + { + "bold": 1, + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Full Name", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "HLC-PRAC-.YYYY.-", + "report_hide": 1, + "set_only_once": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender" + }, + { + "fieldname": "employee_and_user_details_section", + "fieldtype": "Section Break", + "label": "Employee and User Details" + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "default": "Active", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "\nActive\nDisabled", + "reqd": 1 + }, + { + "fieldname": "basic_details_section", + "fieldtype": "Section Break", + "label": "Basic Details" + }, + { + "collapsible": 1, + "depends_on": "eval: !doc.__islocal", + "fieldname": "address_and_contacts_section", + "fieldtype": "Section Break", + "label": "Address and Contacts" + } + ], + "image_field": "image", + "links": [], + "modified": "2021-10-20 08:30:46.786977", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Practitioner", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "select": 1, + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "select": 1, + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "select": 1, + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "search_fields": "practitioner_name, mobile_phone, office_phone", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "practitioner_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py new file mode 100644 index 0000000..6bd17b7 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.contacts.address_and_contact import ( + delete_contact_and_address, + load_address_and_contact, +) +from frappe.model.document import Document +from frappe.model.naming import append_number_if_name_exists + +from erpnext.accounts.party import validate_party_accounts + + +class HealthcarePractitioner(Document): + def onload(self): + load_address_and_contact(self) + + def autoname(self): + # concat first and last name + self.name = self.practitioner_name + + if frappe.db.exists("Healthcare Practitioner", self.name): + self.name = append_number_if_name_exists("Contact", self.name) + + def validate(self): + self.set_full_name() + validate_party_accounts(self) + if self.inpatient_visit_charge_item: + validate_service_item( + self.inpatient_visit_charge_item, + "Configure a service Item for Inpatient Consulting Charge Item", + ) + if self.op_consulting_charge_item: + validate_service_item( + self.op_consulting_charge_item, + "Configure a service Item for Out Patient Consulting Charge Item", + ) + + if self.user_id: + self.validate_user_id() + else: + existing_user_id = frappe.db.get_value("Healthcare Practitioner", self.name, "user_id") + if existing_user_id: + frappe.permissions.remove_user_permission( + "Healthcare Practitioner", self.name, existing_user_id + ) + + def on_update(self): + if self.user_id: + frappe.permissions.add_user_permission("Healthcare Practitioner", self.name, self.user_id) + + def set_full_name(self): + if self.last_name: + self.practitioner_name = " ".join(filter(None, [self.first_name, self.last_name])) + else: + self.practitioner_name = self.first_name + + def validate_user_id(self): + if not frappe.db.exists("User", self.user_id): + frappe.throw(_("User {0} does not exist").format(self.user_id)) + elif not frappe.db.exists("User", self.user_id, "enabled"): + frappe.throw(_("User {0} is disabled").format(self.user_id)) + + # check duplicate + practitioner = frappe.db.exists( + "Healthcare Practitioner", {"user_id": self.user_id, "name": ("!=", self.name)} + ) + if practitioner: + frappe.throw( + _("User {0} is already assigned to Healthcare Practitioner {1}").format( + self.user_id, practitioner + ) + ) + + def on_trash(self): + delete_contact_and_address("Healthcare Practitioner", self.name) + + +def validate_service_item(item, msg): + if frappe.db.get_value("Item", item, "is_stock_item"): + frappe.throw(_(msg)) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None): + + active_filter = {"status": "Active"} + + filters = {**active_filter, **filters} if filters else active_filter + + fields = ["name", "practitioner_name", "mobile_phone"] + + text_in = {"name": ("like", "%%%s%%" % txt), "practitioner_name": ("like", "%%%s%%" % txt)} + + return frappe.get_all( + "Healthcare Practitioner", + fields=fields, + filters=filters, + or_filters=text_in, + start=start, + page_length=page_len, + order_by="name, practitioner_name", + as_list=1, + ) diff --git a/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py new file mode 100644 index 0000000..ba6b86b --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py @@ -0,0 +1,16 @@ +from frappe import _ + + +def get_data(): + return { + "heatmap": True, + "heatmap_message": _("This is based on transactions against this Healthcare Practitioner."), + "fieldname": "practitioner", + "transactions": [ + { + "label": _("Appointments and Patient Encounters"), + "items": ["Patient Appointment", "Patient Encounter", "Fee Validity"], + }, + {"label": _("Consultation"), "items": ["Clinical Procedure", "Lab Test"]}, + ], + } diff --git a/healthcare/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py b/healthcare/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py new file mode 100644 index 0000000..449dc5d --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestHealthcarePractitioner(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/healthcare_schedule_time_slot/__init__.py b/healthcare/healthcare/doctype/healthcare_schedule_time_slot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json b/healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json new file mode 100644 index 0000000..cf54e82 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json @@ -0,0 +1,136 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-05-03 17:27:07.466088", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "day", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Day", + "length": 0, + "no_copy": 0, + "options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "From Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "To Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2020-09-18 17:26:09.703215", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Schedule Time Slot", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py b/healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py new file mode 100644 index 0000000..536bcec --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class HealthcareScheduleTimeSlot(Document): + pass diff --git a/healthcare/healthcare/doctype/healthcare_service_unit/__init__.py b/healthcare/healthcare/doctype/healthcare_service_unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js new file mode 100644 index 0000000..67d3b21 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js @@ -0,0 +1,73 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Healthcare Service Unit', { + onload: function(frm) { + frm.list_route = 'Tree/Healthcare Service Unit'; + + // get query select healthcare service unit + frm.fields_dict['parent_healthcare_service_unit'].get_query = function(doc) { + return { + filters: [ + ['Healthcare Service Unit', 'is_group', '=', 1], + ['Healthcare Service Unit', 'name', '!=', doc.healthcare_service_unit_name] + ] + }; + }; + }, + refresh: function(frm) { + frm.trigger('set_root_readonly'); + frm.set_df_property('service_unit_type', 'reqd', 1); + frm.add_custom_button(__('Healthcare Service Unit Tree'), function() { + frappe.set_route('Tree', 'Healthcare Service Unit'); + }); + + frm.set_query('warehouse', function() { + return { + filters: { + 'company': frm.doc.company + } + }; + }); + }, + set_root_readonly: function(frm) { + // read-only for root healthcare service unit + frm.set_intro(''); + if (!frm.doc.parent_healthcare_service_unit) { + frm.set_read_only(); + frm.set_intro(__('This is a root healthcare service unit and cannot be edited.'), true); + } + }, + allow_appointments: function(frm) { + if (!frm.doc.allow_appointments) { + frm.set_value('overlap_appointments', false); + } + }, + is_group: function(frm) { + if (frm.doc.is_group == 1) { + frm.set_value('allow_appointments', false); + frm.set_df_property('service_unit_type', 'reqd', 0); + } + else { + frm.set_df_property('service_unit_type', 'reqd', 1); + } + }, + overlap_appointments: function(frm) { + if (frm.doc.overlap_appointments == 0) { + frm.set_value('service_unit_capacity', ''); + } + } +}); + + +frappe.ui.form.on('Healthcare Service Unit', 'parent_healthcare_service_unit', function(frm) { + if (!frm.doc.parent_healthcare_service_unit) { + frm.set_intro(__('This is a root healthcare service unit and cannot be edited.'), true); + frm.set_read_only(); + frm.disable_save(); + } else { + frm.set_intro(''); + frm.perm = frappe.perm.get_perm(frm.doc.doctype); + frm.enable_save(); + } +}); diff --git a/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json new file mode 100644 index 0000000..520d1d3 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json @@ -0,0 +1,257 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "beta": 1, + "creation": "2016-09-21 13:48:14.731437", + "description": "Healthcare Service Unit", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "healthcare_service_unit_name", + "is_group", + "service_unit_type", + "allow_appointments", + "overlap_appointments", + "service_unit_capacity", + "inpatient_occupancy", + "occupancy_status", + "column_break_9", + "company", + "warehouse", + "tree_details_section", + "parent_healthcare_service_unit", + "lft", + "rgt", + "old_parent" + ], + "fields": [ + { + "fieldname": "healthcare_service_unit_name", + "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, + "in_global_search": 1, + "in_list_view": 1, + "label": "Service Unit", + "reqd": 1 + }, + { + "bold": 1, + "fieldname": "parent_healthcare_service_unit", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Parent Service Unit", + "options": "Healthcare Service Unit" + }, + { + "bold": 1, + "default": "0", + "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1", + "fieldname": "is_group", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Is Group" + }, + { + "bold": 1, + "depends_on": "eval:doc.is_group != 1", + "fieldname": "service_unit_type", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "Service Unit Type", + "options": "Healthcare Service Unit Type" + }, + { + "default": "0", + "depends_on": "eval:doc.is_group != 1 && doc.inpatient_occupancy != 1", + "fetch_from": "service_unit_type.allow_appointments", + "fieldname": "allow_appointments", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "in_list_view": 1, + "label": "Allow Appointments", + "no_copy": 1, + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.is_group != 1 && doc.allow_appointments == 1 && doc.inpatient_occupany != 1", + "fetch_from": "service_unit_type.overlap_appointments", + "fieldname": "overlap_appointments", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Allow Overlap", + "no_copy": 1, + "read_only": 1 + }, + { + "bold": 1, + "default": "0", + "depends_on": "eval:doc.allow_appointments != 1 && doc.is_group != 1", + "fetch_from": "service_unit_type.inpatient_occupancy", + "fieldname": "inpatient_occupancy", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "in_list_view": 1, + "label": "Inpatient Occupancy", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "depends_on": "eval:doc.inpatient_occupancy == 1", + "fieldname": "occupancy_status", + "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, + "label": "Occupancy Status", + "no_copy": 1, + "options": "Vacant\nOccupied", + "read_only": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 + }, + { + "bold": 1, + "depends_on": "eval:doc.is_group != 1", + "fieldname": "warehouse", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "Warehouse", + "no_copy": 1, + "options": "Warehouse" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "ignore_user_permissions": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "lft", + "fieldtype": "Int", + "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, + "label": "lft", + "no_copy": 1, + "print_hide": 1, + "search_index": 1 + }, + { + "fieldname": "rgt", + "fieldtype": "Int", + "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, + "label": "rgt", + "no_copy": 1, + "print_hide": 1, + "search_index": 1 + }, + { + "fieldname": "old_parent", + "fieldtype": "Link", + "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, + "ignore_user_permissions": 1, + "label": "Old Parent", + "no_copy": 1, + "options": "Healthcare Service Unit", + "print_hide": 1, + "report_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "tree_details_section", + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, + "label": "Tree Details" + }, + { + "allow_in_quick_entry": 1, + "depends_on": "eval:doc.overlap_appointments == 1", + "fieldname": "service_unit_capacity", + "fieldtype": "Int", + "label": "Service Unit Capacity", + "mandatory_depends_on": "eval:doc.overlap_appointments == 1", + "non_negative": 1 + } + ], + "is_tree": 1, + "links": [], + "modified": "2022-07-26 02:18:38.363485", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Service Unit", + "nsm_parent_field": "parent_healthcare_service_unit", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "search_fields": "healthcare_service_unit_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "healthcare_service_unit_name", + "track_changes": 1 +} diff --git a/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py new file mode 100644 index 0000000..5c502f9 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.utils import cint, cstr +from frappe.utils.nestedset import NestedSet + + +class HealthcareServiceUnit(NestedSet): + nsm_parent_field = "parent_healthcare_service_unit" + + def validate(self): + self.set_service_unit_properties() + + def autoname(self): + if self.company: + suffix = " - " + frappe.get_cached_value("Company", self.company, "abbr") + if not self.healthcare_service_unit_name.endswith(suffix): + self.name = self.healthcare_service_unit_name + suffix + else: + self.name = self.healthcare_service_unit_name + + def on_update(self): + super(HealthcareServiceUnit, self).on_update() + self.validate_one_root() + + def set_service_unit_properties(self): + if cint(self.is_group): + self.allow_appointments = False + self.overlap_appointments = False + self.inpatient_occupancy = False + self.service_unit_capacity = 0 + self.occupancy_status = "" + self.service_unit_type = "" + elif self.service_unit_type != "": + service_unit_type = frappe.get_doc("Healthcare Service Unit Type", self.service_unit_type) + self.allow_appointments = service_unit_type.allow_appointments + self.inpatient_occupancy = service_unit_type.inpatient_occupancy + + if self.inpatient_occupancy and self.occupancy_status != "": + self.occupancy_status = "Vacant" + + if service_unit_type.overlap_appointments: + self.overlap_appointments = True + else: + self.overlap_appointments = False + self.service_unit_capacity = 0 + + if self.overlap_appointments: + if not self.service_unit_capacity: + frappe.throw( + _("Please set a valid Service Unit Capacity to enable Overlapping Appointments"), + title=_("Mandatory"), + ) + + +@frappe.whitelist() +def add_multiple_service_units(parent, data): + """ + parent - parent service unit under which the service units are to be created + data (dict) - company, healthcare_service_unit_name, count, service_unit_type, warehouse, service_unit_capacity + """ + if not parent or not data: + return + + data = json.loads(data) + company = ( + data.get("company") + or frappe.defaults.get_defaults().get("company") + or frappe.db.get_single_value("Global Defaults", "default_company") + ) + + if not data.get("healthcare_service_unit_name") or not company: + frappe.throw( + _("Service Unit Name and Company are mandatory to create Healthcare Service Units"), + title=_("Missing Required Fields"), + ) + + count = cint(data.get("count") or 0) + if count <= 0: + frappe.throw( + _("Number of Service Units to be created should at least be 1"), + title=_("Invalid Number of Service Units"), + ) + + capacity = cint(data.get("service_unit_capacity") or 1) + + service_unit = { + "doctype": "Healthcare Service Unit", + "parent_healthcare_service_unit": parent, + "service_unit_type": data.get("service_unit_type") or None, + "service_unit_capacity": capacity if capacity > 0 else 1, + "warehouse": data.get("warehouse") or None, + "company": company, + } + + service_unit_name = "{}".format(data.get("healthcare_service_unit_name").strip(" -")) + + last_suffix = frappe.db.sql( + """SELECT + IFNULL(MAX(CAST(SUBSTRING(name FROM %(start)s FOR 4) AS UNSIGNED)), 0) + FROM `tabHealthcare Service Unit` + WHERE name like %(prefix)s AND company=%(company)s""", + { + "start": len(service_unit_name) + 2, + "prefix": "{}-%".format(service_unit_name), + "company": company, + }, + as_list=1, + )[0][0] + start_suffix = cint(last_suffix) + 1 + + failed_list = [] + for i in range(start_suffix, count + start_suffix): + # name to be in the form WARD-#### + service_unit["healthcare_service_unit_name"] = "{}-{}".format( + service_unit_name, cstr("%0*d" % (4, i)) + ) + service_unit_doc = frappe.get_doc(service_unit) + try: + service_unit_doc.insert() + except Exception: + failed_list.append(service_unit["healthcare_service_unit_name"]) + + return failed_list + + +def on_doctype_update(): + frappe.db.add_unique( + "Healthcare Service Unit", + ["healthcare_service_unit_name", "company"], + constraint_name="unique_service_unit_company", + ) diff --git a/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_dashboard.py b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_dashboard.py new file mode 100644 index 0000000..2a99232 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_dashboard.py @@ -0,0 +1,6 @@ +def get_data(): + return { + "fieldname": "service_unit", + "internal_links": {"Inpatient Occupancy": ["inpatient_occupancies"]}, + "transactions": [{"items": ["Inpatient Record"]}], + } diff --git a/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js new file mode 100644 index 0000000..570fce2 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js @@ -0,0 +1,185 @@ +frappe.provide("frappe.treeview_settings"); + +frappe.treeview_settings['Healthcare Service Unit'] = { + breadcrumbs: 'Healthcare Service Unit', + title: __('Service Unit Tree'), + get_tree_root: false, + get_tree_nodes: 'healthcare.healthcare.utils.get_children', + filters: [{ + fieldname: 'company', + fieldtype: 'Select', + options: erpnext.utils.get_tree_options('company'), + label: __('Company'), + default: erpnext.utils.get_tree_default('company') + }], + fields: [ + { + fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('New Service Unit Name'), + reqd: true + }, + { + fieldtype: 'Check', fieldname: 'is_group', label: __('Is Group'), + description: __("Child nodes can be only created under 'Group' type nodes") + }, + { + fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), + options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), + depends_on: 'eval:!doc.is_group', default: '', + onchange: () => { + if (cur_dialog) { + if (cur_dialog.fields_dict.service_unit_type.value) { + frappe.db.get_value('Healthcare Service Unit Type', + cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') + .then(r => { + if (r.message.overlap_appointments) { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + }); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + } + } + }, + { + fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), + description: __('Sets the number of concurrent appointments allowed'), reqd: false, + depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true + }, + { + fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', + description: __('Optional, if you want to manage stock separately for this Service Unit'), + depends_on: 'eval:!doc.is_group' + }, + { + fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, + default: () => { + return cur_page.page.page.fields_dict.company.value; + } + } + ], + ignore_fields: ['parent_healthcare_service_unit'], + onrender: function (node) { + if (node.data.occupied_of_available !== undefined) { + $("" + + ' ' + node.data.occupied_of_available + + '').insertBefore(node.$ul); + } + if (node.data && node.data.inpatient_occupancy !== undefined) { + if (node.data.inpatient_occupancy == 1) { + if (node.data.occupancy_status == 'Occupied') { + $("" + + ' ' + node.data.occupancy_status + + '').insertBefore(node.$ul); + } + if (node.data.occupancy_status == 'Vacant') { + $("" + + ' ' + node.data.occupancy_status + + '').insertBefore(node.$ul); + } + } + } + }, + post_render: function (treeview) { + frappe.treeview_settings['Healthcare Service Unit'].treeview = {}; + $.extend(frappe.treeview_settings['Healthcare Service Unit'].treeview, treeview); + }, + toolbar: [ + { + label: __('Add Multiple'), + condition: function (node) { + return node.expandable; + }, + click: function (node) { + const dialog = new frappe.ui.Dialog({ + title: __('Add Multiple Service Units'), + fields: [ + { + fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('Service Unit Name'), + reqd: true, description: __("Will be serially suffixed to maintain uniquness. Example: 'Ward' will be named as 'Ward-####'"), + }, + { + fieldtype: 'Int', fieldname: 'count', label: __('Number of Service Units'), + reqd: true + }, + { + fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), + options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), + depends_on: 'eval:!doc.is_group', default: '', reqd: true, + onchange: () => { + if (cur_dialog) { + if (cur_dialog.fields_dict.service_unit_type.value) { + frappe.db.get_value('Healthcare Service Unit Type', + cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') + .then(r => { + if (r.message.overlap_appointments) { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + }); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + } + } + }, + { + fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), + description: __('Sets the number of concurrent appointments allowed'), reqd: false, + depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true + }, + { + fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', + description: __('Optional, if you want to manage stock separately for this Service Unit'), + }, + { + fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, + default: () => { + return cur_page.page.page.fields_dict.company.get_value(); + } + } + ], + primary_action: () => { + dialog.hide(); + let vals = dialog.get_values(); + if (!vals) return; + + return frappe.call({ + method: 'healthcare.healthcare.doctype.healthcare_service_unit.healthcare_service_unit.add_multiple_service_units', + args: { + parent: node.data.value, + data: vals + }, + callback: function (r) { + if (!r.exc && r.message) { + frappe.treeview_settings['Healthcare Service Unit'].treeview.tree.load_children(node, true); + + frappe.show_alert({ + message: __('{0} Service Units created', [vals.count - r.message.length]), + indicator: 'green' + }); + } else { + frappe.msgprint(__('Could not create Service Units')); + } + }, + freeze: true, + freeze_message: __('Creating {0} Service Units', [vals.count]) + }); + }, + primary_action_label: __('Create') + }); + dialog.show(); + } + } + ], + extend_toolbar: true +}; diff --git a/healthcare/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py b/healthcare/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py new file mode 100644 index 0000000..f036ec3 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# See license.txt +import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestHealthcareServiceUnit(FrappeTestCase): + def test_create_company_should_create_root_service_unit(self): + company = frappe.get_doc( + { + "doctype": "Company", + "company_name": "Test Hospital", + "country": "India", + "default_currency": "INR", + } + ) + try: + company = company.insert() + except frappe.exceptions.DuplicateEntryError: + pass + filters = {"company": company.name, "parent_healthcare_service_unit": None} + root_service_unit = frappe.db.exists("Healthcare Service Unit", filters) + self.assertTrue(root_service_unit) diff --git a/healthcare/healthcare/doctype/healthcare_service_unit_type/__init__.py b/healthcare/healthcare/doctype/healthcare_service_unit_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js new file mode 100644 index 0000000..394924f --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js @@ -0,0 +1,86 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Healthcare Service Unit Type', { + refresh: function(frm) { + frm.set_df_property('item_code', 'read_only', frm.doc.__islocal ? 0 : 1); + if (!frm.doc.__islocal && frm.doc.is_billable) { + frm.add_custom_button(__('Change Item Code'), function() { + change_item_code(cur_frm, frm.doc); + }); + } + }, + + service_unit_type: function(frm) { + set_item_details(frm); + + if (!frm.doc.__islocal) { + frm.doc.change_in_item = 1; + } + }, + + is_billable: function(frm) { + set_item_details(frm); + }, + + rate: function(frm) { + if (!frm.doc.__islocal) { + frm.doc.change_in_item = 1; + } + }, + item_group: function(frm) { + if (!frm.doc.__islocal) { + frm.doc.change_in_item = 1; + } + }, + description: function(frm) { + if (!frm.doc.__islocal) { + frm.doc.change_in_item = 1; + } + } +}); + +let set_item_details = function(frm) { + if (frm.doc.service_unit_type && frm.doc.is_billable) { + if (!frm.doc.item_code) + frm.set_value('item_code', frm.doc.service_unit_type); + if (!frm.doc.description) + frm.set_value('description', frm.doc.service_unit_type); + if (!frm.doc.item_group) + frm.set_value('item_group', 'Services'); + } +}; + +let change_item_code = function(frm, doc) { + let d = new frappe.ui.Dialog({ + title: __('Change Item Code'), + fields: [ + { + 'fieldtype': 'Data', + 'label': 'Item Code', + 'fieldname': 'item_code', + 'default': doc.item_code, + reqd: 1, + } + ], + primary_action: function() { + let values = d.get_values(); + if (values) { + frappe.call({ + "method": "healthcare.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.change_item_code", + "args": { item: doc.item, item_code: values['item_code'], doc_name: doc.name }, + callback: function() { + frm.reload_doc(); + } + }); + } + d.hide(); + }, + primary_action_label: __("Change Template Code") + }); + + d.show(); + d.set_values({ + 'Item Code': frm.doc.item_code + }); +}; diff --git a/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json new file mode 100644 index 0000000..9c81c65 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json @@ -0,0 +1,196 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:service_unit_type", + "creation": "2018-07-11 16:47:51.414675", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "disabled", + "service_unit_type", + "allow_appointments", + "overlap_appointments", + "inpatient_occupancy", + "is_billable", + "item_details", + "item", + "item_code", + "item_group", + "uom", + "no_of_hours", + "column_break_11", + "rate", + "description", + "change_in_item" + ], + "fields": [ + { + "fieldname": "service_unit_type", + "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, + "in_list_view": 1, + "label": "Service Unit Type", + "no_copy": 1, + "reqd": 1, + "unique": 1 + }, + { + "bold": 1, + "default": "0", + "depends_on": "eval:doc.inpatient_occupancy != 1", + "fieldname": "allow_appointments", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Allow Appointments" + }, + { + "bold": 1, + "default": "0", + "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1", + "fieldname": "overlap_appointments", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Allow Overlap" + }, + { + "bold": 1, + "default": "0", + "depends_on": "eval:doc.allow_appointments != 1", + "fieldname": "inpatient_occupancy", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Inpatient Occupancy" + }, + { + "bold": 1, + "default": "0", + "depends_on": "eval:doc.inpatient_occupancy == 1 && doc.allow_appointments != 1", + "fieldname": "is_billable", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Is Billable" + }, + { + "depends_on": "is_billable", + "fieldname": "item_details", + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, + "label": "Item Details" + }, + { + "fieldname": "item", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "Item", + "no_copy": 1, + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, + "label": "Item Code", + "mandatory_depends_on": "eval: doc.is_billable == 1", + "no_copy": 1 + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "Item Group", + "mandatory_depends_on": "eval: doc.is_billable == 1", + "options": "Item Group" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "UOM", + "mandatory_depends_on": "eval: doc.is_billable == 1", + "options": "UOM" + }, + { + "fieldname": "no_of_hours", + "fieldtype": "Int", + "hide_days": 1, + "hide_seconds": 1, + "label": "UOM Conversion in Hours", + "mandatory_depends_on": "eval: doc.is_billable == 1" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "hide_days": 1, + "hide_seconds": 1, + "label": "Rate / UOM" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Disabled", + "no_copy": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "hide_days": 1, + "hide_seconds": 1, + "label": "Description" + }, + { + "default": "0", + "fieldname": "change_in_item", + "fieldtype": "Check", + "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, + "label": "Change in Item" + } + ], + "links": [], + "modified": "2021-08-19 17:52:30.266667", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Service Unit Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "service_unit_type" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py new file mode 100644 index 0000000..85e291b --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc + + +class HealthcareServiceUnitType(Document): + def validate(self): + if self.allow_appointments and self.inpatient_occupancy: + frappe.msgprint( + _("Healthcare Service Unit Type cannot have both {0} and {1}").format( + frappe.bold("Allow Appointments"), frappe.bold("Inpatient Occupancy") + ), + raise_exception=1, + title=_("Validation Error"), + indicator="red", + ) + elif not self.allow_appointments and not self.inpatient_occupancy: + frappe.msgprint( + _("Healthcare Service Unit Type must allow atleast one among {0} and {1}").format( + frappe.bold("Allow Appointments"), frappe.bold("Inpatient Occupancy") + ), + raise_exception=1, + title=_("Validation Error"), + indicator="red", + ) + + if not self.allow_appointments: + self.overlap_appointments = 0 + + if self.is_billable: + if self.disabled: + frappe.db.set_value("Item", self.item, "disabled", 1) + else: + frappe.db.set_value("Item", self.item, "disabled", 0) + + def after_insert(self): + if self.inpatient_occupancy and self.is_billable: + create_item(self) + + def on_trash(self): + if self.item: + try: + item = self.item + self.db_set("item", "") + frappe.delete_doc("Item", item) + except Exception: + frappe.throw(_("Not permitted. Please disable the Service Unit Type")) + + def on_update(self): + if self.change_in_item and self.is_billable and self.item: + update_item(self) + + item_price = item_price_exists(self) + + if not item_price: + price_list_name = frappe.db.get_value("Price List", {"selling": 1}) + if self.rate: + make_item_price(self.item_code, price_list_name, self.rate) + else: + make_item_price(self.item_code, price_list_name, 0.0) + else: + frappe.db.set_value("Item Price", item_price, "price_list_rate", self.rate) + + frappe.db.set_value(self.doctype, self.name, "change_in_item", 0) + elif not self.is_billable and self.item: + frappe.db.set_value("Item", self.item, "disabled", 1) + self.reload() + + +def item_price_exists(doc): + item_price = frappe.db.exists({"doctype": "Item Price", "item_code": doc.item_code}) + if len(item_price): + return item_price[0][0] + return False + + +def create_item(doc): + # insert item + item = frappe.get_doc( + { + "doctype": "Item", + "item_code": doc.item_code, + "item_name": doc.service_unit_type, + "item_group": doc.item_group, + "description": doc.description or doc.item_code, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "disabled": 0, + "stock_uom": doc.uom, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + # insert item price + # get item price list to insert item price + price_list_name = frappe.db.get_value("Price List", {"selling": 1}) + if doc.rate: + make_item_price(item.name, price_list_name, doc.rate) + item.standard_rate = doc.rate + else: + make_item_price(item.name, price_list_name, 0.0) + item.standard_rate = 0.0 + + item.save(ignore_permissions=True) + + # Set item in the doc + doc.db_set("item", item.name) + + +def make_item_price(item, price_list_name, item_price): + frappe.get_doc( + { + "doctype": "Item Price", + "price_list": price_list_name, + "item_code": item, + "price_list_rate": item_price, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + +def update_item(doc): + item = frappe.get_doc("Item", doc.item) + if item: + item.update( + { + "item_name": doc.service_unit_type, + "item_group": doc.item_group, + "disabled": 0, + "standard_rate": doc.rate, + "description": doc.description, + } + ) + item.db_update() + + +@frappe.whitelist() +def change_item_code(item, item_code, doc_name): + if frappe.db.exists({"doctype": "Item", "item_code": item_code}): + frappe.throw(_("Item with Item Code {0} already exists").format(item_code)) + else: + rename_doc("Item", item, item_code, ignore_permissions=True) + frappe.db.set_value("Healthcare Service Unit Type", doc_name, "item_code", item_code) diff --git a/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py new file mode 100644 index 0000000..c14efb7 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py @@ -0,0 +1,10 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "service_unit_type", + "transactions": [ + {"label": _("Healthcare Service Units"), "items": ["Healthcare Service Unit"]}, + ], + } diff --git a/healthcare/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py b/healthcare/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py new file mode 100644 index 0000000..43dd2d6 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe + + +class TestHealthcareServiceUnitType(FrappeTestCase): + def test_item_creation(self): + unit_type = get_unit_type() + self.assertTrue(frappe.db.exists("Item", unit_type.item)) + + # check item disabled + unit_type.disabled = 1 + unit_type.save() + self.assertEqual(frappe.db.get_value("Item", unit_type.item, "disabled"), 1) + + +def get_unit_type(): + if frappe.db.exists("Healthcare Service Unit Type", "Inpatient Rooms"): + return frappe.get_doc("Healthcare Service Unit Type", "Inpatient Rooms") + + unit_type = frappe.new_doc("Healthcare Service Unit Type") + unit_type.service_unit_type = "Inpatient Rooms" + unit_type.inpatient_occupancy = 1 + unit_type.is_billable = 1 + unit_type.item_code = "Inpatient Rooms" + unit_type.item_group = "Services" + unit_type.uom = "Hour" + unit_type.no_of_hours = 1 + unit_type.rate = 4000 + unit_type.save() + return unit_type diff --git a/healthcare/healthcare/doctype/healthcare_settings/__init__.py b/healthcare/healthcare/doctype/healthcare_settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.js b/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.js new file mode 100644 index 0000000..39dd904 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.js @@ -0,0 +1,80 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Healthcare Settings', { + setup: function(frm) { + frm.set_query('account', 'receivable_account', function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + 'account_type': 'Receivable', + 'company': d.company, + 'is_group': 0 + } + }; + }); + frm.set_query('account', 'income_account', function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: { + 'root_type': 'Income', + 'company': d.company, + 'is_group': 0 + } + }; + }); + set_query_service_item(frm, 'inpatient_visit_charge_item'); + set_query_service_item(frm, 'op_consulting_charge_item'); + set_query_service_item(frm, 'clinical_procedure_consumable_item'); + } +}); + +var set_query_service_item = function(frm, service_item_field) { + frm.set_query(service_item_field, function() { + return { + filters: { + 'is_sales_item': 1, + 'is_stock_item': 0 + } + }; + }); +}; + +frappe.tour['Healthcare Settings'] = [ + { + fieldname: 'link_customer_to_patient', + title: __('Link Customer to Patient'), + description: __('If checked, a customer will be created for every Patient. Patient Invoices will be created against this Customer. You can also select existing Customer while creating a Patient. This field is checked by default.') + }, + { + fieldname: 'collect_registration_fee', + title: __('Collect Registration Fee'), + description: __('If your Healthcare facility bills registrations of Patients, you can check this and set the Registration Fee in the field below. Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.') + }, + { + fieldname: 'automate_appointment_invoicing', + title: __('Automate Appointment Invoicing'), + description: __('Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.') + }, + { + fieldname: 'validate_nursing_checklists', + title: __('validate_nursing_checklists'), + description: __('Validates all mandatory tasks in nursing checklist to be Completed before a Patient transactional event. For example, if any of the tasks as part of the Discharge Checklist is not in status Completed, system will alert the user while trying to Discharge the Patient from inpatient facility') + }, + { + fieldname: 'inpatient_visit_charge_item', + title: __('Healthcare Service Items'), + description: __('You can create a service item for Inpatient Visit Charge and set it here. Similarly, you can set up other Healthcare Service Items for billing in this section. Click ') + "here" + __(' to know more') + }, + { + fieldname: 'income_account', + title: __('Set up default Accounts for the Healthcare Facility'), + description: __('If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.') + + }, + { + fieldname: 'send_registration_msg', + title: __('Out Patient SMS alerts'), + description: __('If you want to send SMS alert on Patient Registration, you can enable this option. Similary, you can set up Out Patient SMS alerts for other functionalities in this section. Click ') + "here" + __(' to know more') + } +]; diff --git a/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.json b/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.json new file mode 100644 index 0000000..985ff3c --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.json @@ -0,0 +1,359 @@ +{ + "actions": [], + "beta": 1, + "creation": "2017-05-09 11:26:22.337760", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sb_op_settings", + "patient_name_by", + "link_customer_to_patient", + "default_medical_code_standard", + "column_break_9", + "collect_registration_fee", + "registration_fee", + "automate_appointment_invoicing", + "enable_free_follow_ups", + "validate_nursing_checklists", + "max_visits", + "valid_days", + "inpatient_settings_section", + "allow_discharge_despite_unbilled_services", + "do_not_bill_inpatient_encounters", + "healthcare_service_items", + "inpatient_visit_charge_item", + "op_consulting_charge_item", + "column_break_13", + "clinical_procedure_consumable_item", + "sb_in_ac", + "income_account", + "receivable_account", + "out_patient_sms_alerts", + "send_registration_msg", + "registration_msg", + "send_appointment_confirmation", + "appointment_confirmation_msg", + "avoid_confirmation", + "column_break_16", + "send_appointment_reminder", + "appointment_reminder_msg", + "remind_before", + "sb_lab_settings", + "create_lab_test_on_si_submit", + "create_sample_collection_for_lab_test", + "column_break_34", + "lab_test_approval_required", + "employee_name_and_designation_in_print", + "custom_signature_in_print", + "laboratory_sms_alerts", + "sms_printed", + "column_break_28", + "sms_emailed" + ], + "fields": [ + { + "fieldname": "sb_op_settings", + "fieldtype": "Section Break", + "label": "Out Patient Settings" + }, + { + "fieldname": "default_medical_code_standard", + "fieldtype": "Link", + "label": "Default Medical Code Standard", + "options": "Medical Code Standard" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.", + "fieldname": "collect_registration_fee", + "fieldtype": "Check", + "label": "Collect Fee for Patient Registration" + }, + { + "depends_on": "collect_registration_fee", + "fieldname": "registration_fee", + "fieldtype": "Currency", + "label": "Registration Fee", + "mandatory_depends_on": "eval:doc.collect_registration_fee == 1", + "options": "Currency" + }, + { + "depends_on": "eval:doc.enable_free_follow_ups == 1", + "description": "Time period (Valid number of days) for free consultations", + "fieldname": "valid_days", + "fieldtype": "Int", + "label": "Valid Number of Days", + "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1" + }, + { + "collapsible": 1, + "description": "You can configure default Items for billing consultation charges, procedure consumption items and inpatient visits", + "fieldname": "healthcare_service_items", + "fieldtype": "Section Break", + "label": "Default Healthcare Service Items" + }, + { + "fieldname": "inpatient_visit_charge_item", + "fieldtype": "Link", + "label": "Inpatient Visit Charge Item", + "options": "Item" + }, + { + "fieldname": "op_consulting_charge_item", + "fieldtype": "Link", + "label": "Out Patient Consulting Charge Item", + "options": "Item" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "clinical_procedure_consumable_item", + "fieldtype": "Link", + "label": "Clinical Procedure Consumable Item", + "options": "Item" + }, + { + "collapsible": 1, + "fieldname": "out_patient_sms_alerts", + "fieldtype": "Section Break", + "label": "Out Patient SMS Alerts" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "fieldname": "sb_in_ac", + "fieldtype": "Section Break", + "label": "Default Accounts" + }, + { + "description": "Default income accounts to be used if not set in Healthcare Practitioner to book Appointment charges.", + "fieldname": "income_account", + "fieldtype": "Table", + "label": "Income Account", + "options": "Party Account" + }, + { + "description": "Default receivable accounts to be used to book Appointment charges.", + "fieldname": "receivable_account", + "fieldtype": "Table", + "label": "Receivable Account", + "options": "Party Account" + }, + { + "collapsible": 1, + "fieldname": "sb_lab_settings", + "fieldtype": "Section Break", + "label": "Laboratory Settings" + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" + }, + { + "default": "1", + "description": "Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.", + "fieldname": "employee_name_and_designation_in_print", + "fieldtype": "Check", + "label": "Employee name and designation in print" + }, + { + "depends_on": "eval:doc.employee_name_and_designation_in_print == '0'\n", + "fieldname": "custom_signature_in_print", + "fieldtype": "Small Text", + "label": "Custom Signature in Print" + }, + { + "collapsible": 1, + "fieldname": "laboratory_sms_alerts", + "fieldtype": "Section Break", + "label": "Laboratory SMS Alerts" + }, + { + "default": "Hello {{doc.patient}}, Your {{doc.lab_test_name}} result is ready with {{doc.company }}. \nThank You, Good day!", + "fieldname": "sms_printed", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Result Printed Message" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "default": "Hello {{doc.patient}}, Your {{doc.lab_test_name}} result has been emailed to {{doc.email}}. \n{{doc.company }}. \nThank You, Good day!", + "fieldname": "sms_emailed", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Result Emailed Message" + }, + { + "default": "0", + "description": "Checking this will restrict printing and emailing of Lab Test documents unless they have the status as Approved.", + "fieldname": "lab_test_approval_required", + "fieldtype": "Check", + "label": "Do not print or email Lab Tests without Approval" + }, + { + "default": "1", + "description": "If checked, a customer will be created, mapped to Patient.\nPatient Invoices will be created against this Customer. You can also select existing Customer while creating Patient.", + "fieldname": "link_customer_to_patient", + "fieldtype": "Check", + "label": "Link Customer to Patient" + }, + { + "default": "0", + "description": "Checking this will create Lab Test(s) specified in the Sales Invoice on submission.", + "fieldname": "create_lab_test_on_si_submit", + "fieldtype": "Check", + "label": "Create Lab Test(s) on Sales Invoice Submission" + }, + { + "default": "0", + "description": "Checking this will create a Sample Collection document every time you create a Lab Test", + "fieldname": "create_sample_collection_for_lab_test", + "fieldtype": "Check", + "label": "Create Sample Collection document for Lab Test" + }, + { + "fieldname": "patient_name_by", + "fieldtype": "Select", + "label": "Patient Name By", + "options": "Patient Name\nNaming Series" + }, + { + "default": "0", + "description": "Manage Appointment Invoice submit and cancel automatically for Patient Encounter", + "fieldname": "automate_appointment_invoicing", + "fieldtype": "Check", + "label": "Automate Appointment Invoicing" + }, + { + "default": "0", + "fieldname": "send_registration_msg", + "fieldtype": "Check", + "label": "Patient Registration" + }, + { + "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.name}} . Please note this ID for future reference. \nThank You!", + "depends_on": "send_registration_msg", + "fieldname": "registration_msg", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Registration Message" + }, + { + "default": "0", + "fieldname": "send_appointment_confirmation", + "fieldtype": "Check", + "label": "Appointment Confirmation" + }, + { + "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} on {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!", + "depends_on": "send_appointment_confirmation", + "fieldname": "appointment_confirmation_msg", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Confirmation Message" + }, + { + "default": "0", + "depends_on": "send_appointment_confirmation", + "description": "Do not confirm if appointment is created for the same day", + "fieldname": "avoid_confirmation", + "fieldtype": "Check", + "label": "Avoid Confirmation" + }, + { + "default": "0", + "fieldname": "send_appointment_reminder", + "fieldtype": "Check", + "label": "Appointment Reminder" + }, + { + "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!\n", + "depends_on": "send_appointment_reminder", + "fieldname": "appointment_reminder_msg", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Reminder Message" + }, + { + "depends_on": "send_appointment_reminder", + "fieldname": "remind_before", + "fieldtype": "Time", + "label": "Remind Before" + }, + { + "depends_on": "eval:doc.enable_free_follow_ups == 1", + "description": "The number of free follow ups (Patient Encounters in valid days) allowed", + "fieldname": "max_visits", + "fieldtype": "Int", + "label": "Number of Patient Encounters in Valid Days", + "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1" + }, + { + "default": "0", + "fieldname": "enable_free_follow_ups", + "fieldtype": "Check", + "label": "Enable Free Follow-ups" + }, + { + "fieldname": "inpatient_settings_section", + "fieldtype": "Section Break", + "label": "Inpatient Settings" + }, + { + "default": "0", + "fieldname": "allow_discharge_despite_unbilled_services", + "fieldtype": "Check", + "label": "Allow Discharge Despite Unbilled Healthcare Services" + }, + { + "default": "0", + "fieldname": "do_not_bill_inpatient_encounters", + "fieldtype": "Check", + "label": "Do Not Bill Patient Encounters for Inpatients" + }, + { + "default": "0", + "description": "Validates all mandatory tasks in nursing checklist to be Completed before a Patient transactional event.", + "fieldname": "validate_nursing_checklists", + "fieldtype": "Check", + "label": "Validate Nursing Checklists" + } + ], + "issingle": 1, + "links": [], + "modified": "2022-01-18 11:35:17.261514", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.py b/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.py new file mode 100644 index 0000000..659438f --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_settings/healthcare_settings.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.core.doctype.sms_settings.sms_settings import send_sms +from frappe.model.document import Document + + +class HealthcareSettings(Document): + def validate(self): + for key in [ + "collect_registration_fee", + "link_customer_to_patient", + "patient_name_by", + "lab_test_approval_required", + "create_sample_collection_for_lab_test", + "default_medical_code_standard", + ]: + frappe.db.set_default(key, self.get(key, "")) + + if self.collect_registration_fee: + if self.registration_fee <= 0: + frappe.throw(_("Registration Fee cannot be negative or zero")) + + if self.inpatient_visit_charge_item: + validate_service_item(self.inpatient_visit_charge_item) + if self.op_consulting_charge_item: + validate_service_item(self.op_consulting_charge_item) + if self.clinical_procedure_consumable_item: + validate_service_item(self.clinical_procedure_consumable_item) + + +def validate_service_item(item): + if frappe.db.get_value("Item", item, "is_stock_item"): + frappe.throw(_("Configure a service Item for {0}").format(item)) + + +@frappe.whitelist() +def get_sms_text(doc): + sms_text = {} + doc = frappe.get_doc("Lab Test", doc) + context = {"doc": doc, "alert": doc, "comments": None} + + emailed = frappe.db.get_value("Healthcare Settings", None, "sms_emailed") + sms_text["emailed"] = frappe.render_template(emailed, context) + + printed = frappe.db.get_value("Healthcare Settings", None, "sms_printed") + sms_text["printed"] = frappe.render_template(printed, context) + + return sms_text + + +def send_registration_sms(doc): + if frappe.db.get_single_value("Healthcare Settings", "send_registration_msg"): + if doc.mobile: + context = {"doc": doc, "alert": doc, "comments": None} + if doc.get("_comments"): + context["comments"] = json.loads(doc.get("_comments")) + messages = frappe.db.get_single_value("Healthcare Settings", "registration_msg") + messages = frappe.render_template(messages, context) + number = [doc.mobile] + send_sms(number, messages) + else: + frappe.msgprint(doc.name + " has no mobile number to send registration SMS", alert=True) + + +def get_receivable_account(company): + receivable_account = get_account(None, "receivable_account", "Healthcare Settings", company) + if receivable_account: + return receivable_account + + return frappe.get_cached_value("Company", company, "default_receivable_account") + + +def get_income_account(practitioner, company): + # check income account in Healthcare Practitioner + if practitioner: + income_account = get_account("Healthcare Practitioner", None, practitioner, company) + if income_account: + return income_account + + # else check income account in Healthcare Settings + income_account = get_account(None, "income_account", "Healthcare Settings", company) + if income_account: + return income_account + + # else return default income account of company + return frappe.get_cached_value("Company", company, "default_income_account") + + +def get_account(parent_type, parent_field, parent, company): + if parent_type: + return frappe.db.get_value( + "Party Account", {"parenttype": parent_type, "parent": parent, "company": company}, "account" + ) + + if parent_field: + return frappe.db.get_value( + "Party Account", {"parentfield": parent_field, "parent": parent, "company": company}, "account" + ) diff --git a/healthcare/healthcare/doctype/healthcare_settings/test_healthcare_settings.py b/healthcare/healthcare/doctype/healthcare_settings/test_healthcare_settings.py new file mode 100644 index 0000000..3075278 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_settings/test_healthcare_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestHealthcareSettings(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry/__init__.py b/healthcare/healthcare/doctype/inpatient_medication_entry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js new file mode 100644 index 0000000..7ec70fe --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js @@ -0,0 +1,74 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Inpatient Medication Entry', { + refresh: function(frm) { + // Ignore cancellation of doctype on cancel all + frm.ignore_doctypes_on_cancel_all = ['Stock Entry']; + frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide(); + + frm.set_query('item_code', () => { + return { + filters: { + is_stock_item: 1 + } + }; + }); + + frm.set_query('drug_code', 'medication_orders', () => { + return { + filters: { + is_stock_item: 1 + } + }; + }); + + frm.set_query('warehouse', () => { + return { + filters: { + company: frm.doc.company + } + }; + }); + + if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock) + return; + + frm.add_custom_button(__('Make Stock Entry'), function() { + frappe.call({ + method: 'healthcare.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry', + args: { docname: frm.doc.name }, + freeze: true, + callback: function(r) { + if (r.message) { + var doclist = frappe.model.sync(r.message); + frappe.set_route('Form', doclist[0].doctype, doclist[0].name); + } else { + frappe.msgprint({ + title: __('No Drug Shortage'), + message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'), + indicator: 'green' + }); + } + } + }); + }); + }, + + patient: function(frm) { + if (frm.doc.patient) + frm.set_value('service_unit', ''); + }, + + get_medication_orders: function(frm) { + frappe.call({ + method: 'get_medication_orders', + doc: frm.doc, + freeze: true, + freeze_message: __('Fetching Pending Medication Orders'), + callback: function() { + refresh_field('medication_orders'); + } + }); + } +}); diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json new file mode 100644 index 0000000..b1a6ee4 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json @@ -0,0 +1,204 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-09-25 14:13:20.111906", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "company", + "column_break_3", + "posting_date", + "status", + "filters_section", + "item_code", + "assigned_to_practitioner", + "patient", + "practitioner", + "service_unit", + "column_break_11", + "from_date", + "to_date", + "from_time", + "to_time", + "select_medication_orders_section", + "get_medication_orders", + "medication_orders", + "section_break_18", + "update_stock", + "warehouse", + "amended_from" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "HLC-IME-.YYYY.-" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled", + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval: doc.__islocal", + "fieldname": "filters_section", + "fieldtype": "Section Break", + "label": "Filters" + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item Code (Drug)", + "options": "Item" + }, + { + "depends_on": "update_stock", + "description": "Warehouse from where medication stock should be consumed", + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Medication Warehouse", + "mandatory_depends_on": "update_stock", + "options": "Warehouse" + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "label": "Patient", + "options": "Patient" + }, + { + "depends_on": "eval:!doc.patient", + "fieldname": "service_unit", + "fieldtype": "Link", + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Inpatient Medication Entry", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "select_medication_orders_section", + "fieldtype": "Section Break", + "label": "Medication Orders" + }, + { + "fieldname": "medication_orders", + "fieldtype": "Table", + "label": "Inpatient Medication Orders", + "options": "Inpatient Medication Entry Detail", + "reqd": 1 + }, + { + "depends_on": "eval:doc.docstatus!==1", + "fieldname": "get_medication_orders", + "fieldtype": "Button", + "label": "Get Pending Medication Orders", + "print_hide": 1 + }, + { + "fieldname": "assigned_to_practitioner", + "fieldtype": "Link", + "label": "Assigned To", + "options": "User" + }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break", + "label": "Stock Details" + }, + { + "default": "1", + "fieldname": "update_stock", + "fieldtype": "Check", + "label": "Update Stock" + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "label": "From Time" + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "label": "To Time" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-01-11 12:37:46.749659", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Medication Entry", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py new file mode 100644 index 0000000..248f42d --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt, get_link_to_form + +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import get_account +from erpnext.stock.utils import get_latest_stock_qty + + +class InpatientMedicationEntry(Document): + def validate(self): + self.validate_medication_orders() + + @frappe.whitelist() + def get_medication_orders(self): + # pull inpatient medication orders based on selected filters + orders = get_pending_medication_orders(self) + + if orders: + self.add_mo_to_table(orders) + return self + else: + self.set("medication_orders", []) + frappe.msgprint(_("No pending medication orders found for selected criteria")) + + def add_mo_to_table(self, orders): + # Add medication orders in the child table + self.set("medication_orders", []) + + for data in orders: + self.append( + "medication_orders", + { + "patient": data.patient, + "patient_name": data.patient_name, + "inpatient_record": data.inpatient_record, + "service_unit": data.service_unit, + "datetime": "%s %s" % (data.date, data.time or "00:00:00"), + "drug_code": data.drug, + "drug_name": data.drug_name, + "dosage": data.dosage, + "dosage_form": data.dosage_form, + "against_imo": data.parent, + "against_imoe": data.name, + }, + ) + + def on_submit(self): + self.validate_medication_orders() + success_msg = "" + if self.update_stock: + stock_entry = self.process_stock() + success_msg += _("Stock Entry {0} created and ").format( + frappe.bold(get_link_to_form("Stock Entry", stock_entry)) + ) + + self.update_medication_orders() + success_msg += _("Inpatient Medication Orders updated successfully") + frappe.msgprint(success_msg, title=_("Success"), indicator="green") + + def validate_medication_orders(self): + for entry in self.medication_orders: + docstatus, is_completed = frappe.db.get_value( + "Inpatient Medication Order Entry", entry.against_imoe, ["docstatus", "is_completed"] + ) + + if docstatus == 2: + frappe.throw( + _( + "Row {0}: Cannot create Inpatient Medication Entry against cancelled Inpatient Medication Order {1}" + ).format(entry.idx, get_link_to_form(entry.against_imo)) + ) + + if is_completed: + frappe.throw( + _("Row {0}: This Medication Order is already marked as completed").format(entry.idx) + ) + + def on_cancel(self): + self.cancel_stock_entries() + self.update_medication_orders(on_cancel=True) + + def process_stock(self): + allow_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") + if not allow_negative_stock: + self.check_stock_qty() + + return self.make_stock_entry() + + def update_medication_orders(self, on_cancel=False): + orders, order_entry_map = self.get_order_entry_map() + # mark completion status + is_completed = 1 + if on_cancel: + is_completed = 0 + + frappe.db.sql( + """ + UPDATE `tabInpatient Medication Order Entry` + SET is_completed = %(is_completed)s + WHERE name IN %(orders)s + """, + {"orders": orders, "is_completed": is_completed}, + ) + + # update status and completed orders count + for order, count in order_entry_map.items(): + medication_order = frappe.get_doc("Inpatient Medication Order", order) + completed_orders = flt(count) + current_value = frappe.db.get_value("Inpatient Medication Order", order, "completed_orders") + + if on_cancel: + completed_orders = flt(current_value) - flt(count) + else: + completed_orders = flt(current_value) + flt(count) + + medication_order.db_set("completed_orders", completed_orders) + medication_order.set_status() + + def get_order_entry_map(self): + # for marking order completion status + orders = [] + # orders mapped + order_entry_map = dict() + + for entry in self.medication_orders: + orders.append(entry.against_imoe) + parent = entry.against_imo + if not order_entry_map.get(parent): + order_entry_map[parent] = 0 + + order_entry_map[parent] += 1 + + return orders, order_entry_map + + def check_stock_qty(self): + drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse) + + if drug_shortage: + message = _("Quantity not available for the following items in warehouse {0}. ").format( + frappe.bold(self.warehouse) + ) + message += _( + "Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed." + ) + + formatted_item_rows = "" + + for drug, shortage_qty in drug_shortage.items(): + item_link = get_link_to_form("Item", drug) + formatted_item_rows += """ + {0} + {1} + """.format( + item_link, frappe.bold(shortage_qty) + ) + + message += """ + + + + + + {2} +
{0}{1}
+ """.format( + _("Drug Code"), _("Shortage Qty"), formatted_item_rows + ) + + frappe.throw(message, title=_("Insufficient Stock"), is_minimizable=True, wide=True) + + def make_stock_entry(self): + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.purpose = "Material Issue" + stock_entry.set_stock_entry_type() + stock_entry.from_warehouse = self.warehouse + stock_entry.company = self.company + stock_entry.inpatient_medication_entry = self.name + cost_center = frappe.get_cached_value("Company", self.company, "cost_center") + expense_account = get_account(None, "expense_account", "Healthcare Settings", self.company) + + for entry in self.medication_orders: + se_child = stock_entry.append("items") + se_child.item_code = entry.drug_code + se_child.item_name = entry.drug_name + se_child.uom = frappe.db.get_value("Item", entry.drug_code, "stock_uom") + se_child.stock_uom = se_child.uom + se_child.qty = flt(entry.dosage) + # in stock uom + se_child.conversion_factor = 1 + se_child.cost_center = cost_center + se_child.expense_account = expense_account + # references + se_child.patient = entry.patient + se_child.inpatient_medication_entry_child = entry.name + + stock_entry.submit() + return stock_entry.name + + def cancel_stock_entries(self): + stock_entries = frappe.get_all("Stock Entry", {"inpatient_medication_entry": self.name}) + for entry in stock_entries: + doc = frappe.get_doc("Stock Entry", entry.name) + doc.cancel() + + +def get_pending_medication_orders(entry): + filters, values = get_filters(entry) + to_remove = [] + + data = frappe.db.sql( + """ + SELECT + ip.inpatient_record, ip.patient, ip.patient_name, + entry.name, entry.parent, entry.drug, entry.drug_name, + entry.dosage, entry.dosage_form, entry.date, entry.time, entry.instructions + FROM + `tabInpatient Medication Order` ip + INNER JOIN + `tabInpatient Medication Order Entry` entry + ON + ip.name = entry.parent + WHERE + ip.docstatus = 1 and + ip.company = %(company)s and + entry.is_completed = 0 + {0} + ORDER BY + entry.date, entry.time + """.format( + filters + ), + values, + as_dict=1, + ) + + for doc in data: + inpatient_record = doc.inpatient_record + if inpatient_record: + doc["service_unit"] = get_current_healthcare_service_unit(inpatient_record) + + if entry.service_unit and doc.service_unit != entry.service_unit: + to_remove.append(doc) + + for doc in to_remove: + data.remove(doc) + + return data + + +def get_filters(entry): + filters = "" + values = dict(company=entry.company) + if entry.from_date: + filters += " and entry.date >= %(from_date)s" + values["from_date"] = entry.from_date + + if entry.to_date: + filters += " and entry.date <= %(to_date)s" + values["to_date"] = entry.to_date + + if entry.from_time: + filters += " and entry.time >= %(from_time)s" + values["from_time"] = entry.from_time + + if entry.to_time: + filters += " and entry.time <= %(to_time)s" + values["to_time"] = entry.to_time + + if entry.patient: + filters += " and ip.patient = %(patient)s" + values["patient"] = entry.patient + + if entry.practitioner: + filters += " and ip.practitioner = %(practitioner)s" + values["practitioner"] = entry.practitioner + + if entry.item_code: + filters += " and entry.drug = %(item_code)s" + values["item_code"] = entry.item_code + + if entry.assigned_to_practitioner: + filters += " and ip._assign LIKE %(assigned_to)s" + values["assigned_to"] = "%" + entry.assigned_to_practitioner + "%" + + return filters, values + + +def get_current_healthcare_service_unit(inpatient_record): + ip_record = frappe.get_doc("Inpatient Record", inpatient_record) + if ip_record.status in ["Admitted", "Discharge Scheduled"] and ip_record.inpatient_occupancies: + return ip_record.inpatient_occupancies[-1].service_unit + return + + +def get_drug_shortage_map(medication_orders, warehouse): + """ + Returns a dict like { drug_code: shortage_qty } + """ + drug_requirement = dict() + for d in medication_orders: + if not drug_requirement.get(d.drug_code): + drug_requirement[d.drug_code] = 0 + drug_requirement[d.drug_code] += flt(d.dosage) + + drug_shortage = dict() + for drug, required_qty in drug_requirement.items(): + available_qty = get_latest_stock_qty(drug, warehouse) + if flt(required_qty) > flt(available_qty): + drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty)) + + return drug_shortage + + +@frappe.whitelist() +def make_difference_stock_entry(docname): + doc = frappe.get_doc("Inpatient Medication Entry", docname) + drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse) + + if not drug_shortage: + return None + + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.purpose = "Material Transfer" + stock_entry.set_stock_entry_type() + stock_entry.to_warehouse = doc.warehouse + stock_entry.company = doc.company + cost_center = frappe.get_cached_value("Company", doc.company, "cost_center") + expense_account = get_account(None, "expense_account", "Healthcare Settings", doc.company) + + for drug, shortage_qty in drug_shortage.items(): + se_child = stock_entry.append("items") + se_child.item_code = drug + se_child.item_name = frappe.db.get_value("Item", drug, "stock_uom") + se_child.uom = frappe.db.get_value("Item", drug, "stock_uom") + se_child.stock_uom = se_child.uom + se_child.qty = flt(shortage_qty) + se_child.t_warehouse = doc.warehouse + # in stock uom + se_child.conversion_factor = 1 + se_child.cost_center = cost_center + se_child.expense_account = expense_account + + return stock_entry diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py new file mode 100644 index 0000000..07d634e --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py @@ -0,0 +1,9 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "against_imoe", + "internal_links": {"Inpatient Medication Order": ["medication_orders", "against_imo"]}, + "transactions": [{"label": _("Reference"), "items": ["Inpatient Medication Order"]}], + } diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py b/healthcare/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py new file mode 100644 index 0000000..617433d --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import add_days, getdate, now_datetime + +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import get_account +from healthcare.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( + get_drug_shortage_map, + make_difference_stock_entry, +) +from healthcare.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import ( + create_ipme, + create_ipmo, +) +from healthcare.healthcare.doctype.inpatient_record.inpatient_record import ( + admit_patient, + discharge_patient, + schedule_discharge, +) +from healthcare.healthcare.doctype.inpatient_record.test_inpatient_record import ( + create_inpatient, + create_patient, + get_healthcare_service_unit, + mark_invoiced_inpatient_occupancy, +) + + +class TestInpatientMedicationEntry(FrappeTestCase): + def setUp(self): + frappe.db.sql("""delete from `tabInpatient Record`""") + frappe.db.sql("""delete from `tabInpatient Medication Order`""") + frappe.db.sql("""delete from `tabInpatient Medication Entry`""") + self.patient = create_patient() + + # Admit + ip_record = create_inpatient(self.patient) + ip_record.expected_length_of_stay = 0 + ip_record.save() + ip_record.reload() + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + self.ip_record = ip_record + + def test_filters_for_fetching_pending_mo(self): + ipmo = create_ipmo(self.patient) + ipmo.submit() + ipmo.reload() + + date = add_days(getdate(), -1) + filters = frappe._dict( + from_date=date, + to_date=date, + from_time="", + to_time="", + item_code="Dextromethorphan", + patient=self.patient, + ) + + ipme = create_ipme(filters, update_stock=0) + + # 3 dosages per day + self.assertEqual(len(ipme.medication_orders), 3) + self.assertEqual(getdate(ipme.medication_orders[0].datetime), date) + + def test_ipme_with_stock_update(self): + ipmo = create_ipmo(self.patient) + ipmo.submit() + ipmo.reload() + + date = add_days(getdate(), -1) + filters = frappe._dict( + from_date=date, + to_date=date, + from_time="", + to_time="", + item_code="Dextromethorphan", + patient=self.patient, + ) + + make_stock_entry() + ipme = create_ipme(filters, update_stock=1) + ipme.submit() + ipme.reload() + + # test order completed + is_order_completed = frappe.db.get_value( + "Inpatient Medication Order Entry", ipme.medication_orders[0].against_imoe, "is_completed" + ) + self.assertEqual(is_order_completed, 1) + + # test stock entry + stock_entry = frappe.db.exists("Stock Entry", {"inpatient_medication_entry": ipme.name}) + self.assertTrue(stock_entry) + + # check references + stock_entry = frappe.get_doc("Stock Entry", stock_entry) + self.assertEqual(stock_entry.items[0].patient, self.patient) + self.assertEqual( + stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name + ) + + def test_drug_shortage_stock_entry(self): + ipmo = create_ipmo(self.patient) + ipmo.submit() + ipmo.reload() + + date = add_days(getdate(), -1) + filters = frappe._dict( + from_date=date, + to_date=date, + from_time="", + to_time="", + item_code="Dextromethorphan", + patient=self.patient, + ) + + # check drug shortage + ipme = create_ipme(filters, update_stock=1) + ipme.warehouse = "Finished Goods - _TC" + ipme.save() + drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse) + self.assertEqual(drug_shortage.get("Dextromethorphan"), 3) + + # check material transfer for drug shortage + make_stock_entry() + stock_entry = make_difference_stock_entry(ipme.name) + self.assertEqual(stock_entry.items[0].item_code, "Dextromethorphan") + self.assertEqual(stock_entry.items[0].qty, 3) + stock_entry.from_warehouse = "Stores - _TC" + stock_entry.submit() + + ipme.reload() + ipme.submit() + + def tearDown(self): + # cleanup - Discharge + schedule_discharge(frappe.as_json({"patient": self.patient})) + self.ip_record.reload() + mark_invoiced_inpatient_occupancy(self.ip_record) + + self.ip_record.reload() + discharge_patient(self.ip_record) + + for entry in frappe.get_all("Inpatient Medication Entry"): + doc = frappe.get_doc("Inpatient Medication Entry", entry.name) + doc.cancel() + + for entry in frappe.get_all("Inpatient Medication Order"): + doc = frappe.get_doc("Inpatient Medication Order", entry.name) + doc.cancel() + + +def make_stock_entry(warehouse=None): + frappe.db.set_value( + "Company", + "_Test Company", + { + "stock_adjustment_account": "Stock Adjustment - _TC", + "default_inventory_account": "Stock In Hand - _TC", + }, + ) + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.stock_entry_type = "Material Receipt" + stock_entry.company = "_Test Company" + stock_entry.to_warehouse = warehouse or "Stores - _TC" + expense_account = get_account(None, "expense_account", "Healthcare Settings", "_Test Company") + se_child = stock_entry.append("items") + se_child.item_code = "Dextromethorphan" + se_child.item_name = "Dextromethorphan" + se_child.uom = "Nos" + se_child.stock_uom = "Nos" + se_child.qty = 6 + se_child.t_warehouse = "Stores - _TC" + # in stock uom + se_child.conversion_factor = 1.0 + se_child.expense_account = expense_account + stock_entry.submit() diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry_detail/__init__.py b/healthcare/healthcare/doctype/inpatient_medication_entry_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json b/healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json new file mode 100644 index 0000000..e3d7212 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json @@ -0,0 +1,163 @@ +{ + "actions": [], + "creation": "2020-09-25 14:56:32.636569", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "patient", + "patient_name", + "inpatient_record", + "column_break_4", + "service_unit", + "datetime", + "medication_details_section", + "drug_code", + "drug_name", + "dosage", + "available_qty", + "dosage_form", + "column_break_10", + "instructions", + "references_section", + "against_imo", + "against_imoe" + ], + "fields": [ + { + "columns": 2, + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "drug_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Drug Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "drug_code.item_name", + "fieldname": "drug_name", + "fieldtype": "Data", + "label": "Drug Name", + "read_only": 1 + }, + { + "columns": 1, + "fieldname": "dosage", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Dosage", + "reqd": 1 + }, + { + "fieldname": "dosage_form", + "fieldtype": "Link", + "label": "Dosage Form", + "options": "Dosage Form" + }, + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "references_section", + "fieldtype": "Section Break", + "label": "References" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "medication_details_section", + "fieldtype": "Section Break", + "label": "Medication Details" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "columns": 3, + "fieldname": "datetime", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Datetime", + "reqd": 1 + }, + { + "fieldname": "instructions", + "fieldtype": "Small Text", + "label": "Instructions" + }, + { + "columns": 2, + "fieldname": "service_unit", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Service Unit", + "options": "Healthcare Service Unit", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "against_imo", + "fieldtype": "Link", + "label": "Against Inpatient Medication Order", + "no_copy": 1, + "options": "Inpatient Medication Order", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "against_imoe", + "fieldtype": "Data", + "label": "Against Inpatient Medication Order Entry", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "available_qty", + "fieldtype": "Float", + "hidden": 1, + "label": "Available Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-09-30 14:48:23.648223", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Medication Entry Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py b/healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py new file mode 100644 index 0000000..6dd7d2f --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class InpatientMedicationEntryDetail(Document): + pass diff --git a/healthcare/healthcare/doctype/inpatient_medication_order/__init__.py b/healthcare/healthcare/doctype/inpatient_medication_order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js new file mode 100644 index 0000000..c933755 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js @@ -0,0 +1,107 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Inpatient Medication Order', { + refresh: function(frm) { + if (frm.doc.docstatus === 1) { + frm.trigger("show_progress"); + } + + frm.events.show_medication_order_button(frm); + + frm.set_query('patient', () => { + return { + filters: { + 'inpatient_record': ['!=', ''], + 'inpatient_status': 'Admitted' + } + }; + }); + }, + + show_medication_order_button: function(frm) { + frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide(); + frm.fields_dict['medication_orders'].grid.add_custom_button(__('Add Medication Orders'), () => { + let d = new frappe.ui.Dialog({ + title: __('Add Medication Orders'), + fields: [ + { + fieldname: 'drug_code', + label: __('Drug'), + fieldtype: 'Link', + options: 'Item', + reqd: 1, + "get_query": function () { + return { + filters: {'is_stock_item': 1} + }; + } + }, + { + fieldname: 'dosage', + label: __('Dosage'), + fieldtype: 'Link', + options: 'Prescription Dosage', + reqd: 1 + }, + { + fieldname: 'period', + label: __('Period'), + fieldtype: 'Link', + options: 'Prescription Duration', + reqd: 1 + }, + { + fieldname: 'dosage_form', + label: __('Dosage Form'), + fieldtype: 'Link', + options: 'Dosage Form', + reqd: 1 + } + ], + primary_action_label: __('Add'), + primary_action: () => { + let values = d.get_values(); + if (values) { + frm.call({ + doc: frm.doc, + method: 'add_order_entries', + args: { + order: values + }, + freeze: true, + freeze_message: __('Adding Order Entries'), + callback: function() { + frm.refresh_field('medication_orders'); + } + }); + } + }, + }); + d.show(); + }); + }, + + show_progress: function(frm) { + let bars = []; + let message = ''; + + // completed sessions + let title = __('{0} medication orders completed', [frm.doc.completed_orders]); + if (frm.doc.completed_orders === 1) { + title = __('{0} medication order completed', [frm.doc.completed_orders]); + } + title += __(' out of {0}', [frm.doc.total_orders]); + + bars.push({ + 'title': title, + 'width': (frm.doc.completed_orders / frm.doc.total_orders * 100) + '%', + 'progress_class': 'progress-bar-success' + }); + if (bars[0].width == '0%') { + bars[0].width = '0.5%'; + } + message = title; + frm.dashboard.add_progress(__('Status'), bars, message); + } +}); diff --git a/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json new file mode 100644 index 0000000..e31d2e3 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json @@ -0,0 +1,196 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-09-14 18:33:56.715736", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "patient_details_section", + "naming_series", + "patient_encounter", + "patient", + "patient_name", + "patient_age", + "inpatient_record", + "column_break_6", + "company", + "status", + "practitioner", + "start_date", + "end_date", + "medication_orders_section", + "medication_orders", + "section_break_16", + "total_orders", + "column_break_18", + "completed_orders", + "amended_from" + ], + "fields": [ + { + "fieldname": "patient_details_section", + "fieldtype": "Section Break", + "label": "Patient Details" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "HLC-IMO-.YYYY.-" + }, + { + "fieldname": "patient_encounter", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient Encounter", + "options": "Patient Encounter" + }, + { + "fetch_from": "patient_encounter.patient", + "fieldname": "patient", + "fieldtype": "Link", + "label": "Patient", + "options": "Patient", + "read_only_depends_on": "patient_encounter", + "reqd": 1 + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "label": "Patient Age", + "read_only": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1, + "reqd": 1 + }, + { + "fetch_from": "patient_encounter.practitioner", + "fieldname": "practitioner", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only_depends_on": "patient_encounter" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "reqd": 1 + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "read_only": 1 + }, + { + "depends_on": "eval: doc.patient && doc.start_date", + "fieldname": "medication_orders_section", + "fieldtype": "Section Break", + "label": "Medication Orders" + }, + { + "fieldname": "medication_orders", + "fieldtype": "Table", + "label": "Medication Orders", + "options": "Inpatient Medication Order Entry" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Inpatient Medication Order", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_16", + "fieldtype": "Section Break", + "label": "Other Details" + }, + { + "fieldname": "total_orders", + "fieldtype": "Float", + "label": "Total Orders", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "completed_orders", + "fieldtype": "Float", + "label": "Completed Orders", + "no_copy": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2020-09-30 21:53:27.128591", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Medication Order", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "search_fields": "patient_encounter, patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py new file mode 100644 index 0000000..d061d39 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cstr + +from healthcare.healthcare.doctype.patient_encounter.patient_encounter import ( + get_prescription_dates, +) + + +class InpatientMedicationOrder(Document): + def validate(self): + self.validate_inpatient() + self.validate_duplicate() + self.set_total_orders() + self.set_status() + + def on_submit(self): + self.validate_inpatient() + self.set_status() + + def on_cancel(self): + self.set_status() + + def validate_inpatient(self): + if not self.inpatient_record: + frappe.throw(_("No Inpatient Record found against patient {0}").format(self.patient)) + + def validate_duplicate(self): + existing_mo = frappe.db.exists( + "Inpatient Medication Order", + { + "patient_encounter": self.patient_encounter, + "docstatus": ("!=", 2), + "name": ("!=", self.name), + }, + ) + if existing_mo: + frappe.throw( + _("An Inpatient Medication Order {0} against Patient Encounter {1} already exists.").format( + existing_mo, self.patient_encounter + ), + frappe.DuplicateEntryError, + ) + + def set_total_orders(self): + self.db_set("total_orders", len(self.medication_orders)) + + def set_status(self): + status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[cstr(self.docstatus or 0)] + + if self.docstatus == 1: + if not self.completed_orders: + status = "Pending" + elif self.completed_orders < self.total_orders: + status = "In Process" + else: + status = "Completed" + + self.db_set("status", status) + + @frappe.whitelist() + def add_order_entries(self, order): + if order.get("drug_code"): + dosage = frappe.get_doc("Prescription Dosage", order.get("dosage")) + dates = get_prescription_dates(order.get("period"), self.start_date) + for date in dates: + for dose in dosage.dosage_strength: + entry = self.append("medication_orders") + entry.drug = order.get("drug_code") + entry.drug_name = frappe.db.get_value("Item", order.get("drug_code"), "item_name") + entry.dosage = dose.strength + entry.dosage_form = order.get("dosage_form") + entry.date = date + entry.time = dose.strength_time + self.end_date = dates[-1] + return diff --git a/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js new file mode 100644 index 0000000..1c31876 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js @@ -0,0 +1,16 @@ +frappe.listview_settings['Inpatient Medication Order'] = { + add_fields: ["status"], + filters: [["status", "!=", "Cancelled"]], + get_indicator: function(doc) { + if (doc.status === "Pending") { + return [__("Pending"), "orange", "status,=,Pending"]; + + } else if (doc.status === "In Process") { + return [__("In Process"), "blue", "status,=,In Process"]; + + } else if (doc.status === "Completed") { + return [__("Completed"), "green", "status,=,Completed"]; + + } + } +}; diff --git a/healthcare/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py b/healthcare/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py new file mode 100644 index 0000000..ceb282c --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import add_days, getdate, now_datetime + +from healthcare.healthcare.doctype.inpatient_record.inpatient_record import ( + admit_patient, + discharge_patient, + schedule_discharge, +) +from healthcare.healthcare.doctype.inpatient_record.test_inpatient_record import ( + create_inpatient, + create_patient, + get_healthcare_service_unit, + mark_invoiced_inpatient_occupancy, +) + + +class TestInpatientMedicationOrder(FrappeTestCase): + def setUp(self): + frappe.db.sql("""delete from `tabInpatient Record`""") + self.patient = create_patient() + + # Admit + ip_record = create_inpatient(self.patient) + ip_record.expected_length_of_stay = 0 + ip_record.save() + ip_record.reload() + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + self.ip_record = ip_record + + def test_order_creation(self): + ipmo = create_ipmo(self.patient) + ipmo.submit() + ipmo.reload() + + # 3 dosages per day for 2 days + self.assertEqual(len(ipmo.medication_orders), 6) + self.assertEqual(ipmo.medication_orders[0].date, add_days(getdate(), -1)) + + prescription_dosage = frappe.get_doc("Prescription Dosage", "1-1-1") + for i in range(len(prescription_dosage.dosage_strength)): + self.assertEqual( + ipmo.medication_orders[i].time, prescription_dosage.dosage_strength[i].strength_time + ) + + self.assertEqual(ipmo.medication_orders[3].date, getdate()) + + def test_inpatient_validation(self): + # Discharge + schedule_discharge(frappe.as_json({"patient": self.patient})) + + self.ip_record.reload() + mark_invoiced_inpatient_occupancy(self.ip_record) + + self.ip_record.reload() + discharge_patient(self.ip_record) + + ipmo = create_ipmo(self.patient) + # inpatient validation + self.assertRaises(frappe.ValidationError, ipmo.insert) + + def test_status(self): + ipmo = create_ipmo(self.patient) + ipmo.submit() + ipmo.reload() + + self.assertEqual(ipmo.status, "Pending") + + filters = frappe._dict( + from_date=add_days(getdate(), -1), to_date=add_days(getdate(), -1), from_time="", to_time="" + ) + ipme = create_ipme(filters) + ipme.submit() + ipmo.reload() + self.assertEqual(ipmo.status, "In Process") + + filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time="", to_time="") + ipme = create_ipme(filters) + ipme.submit() + ipmo.reload() + self.assertEqual(ipmo.status, "Completed") + + def tearDown(self): + if frappe.db.get_value("Patient", self.patient, "inpatient_record"): + # cleanup - Discharge + schedule_discharge(frappe.as_json({"patient": self.patient})) + self.ip_record.reload() + mark_invoiced_inpatient_occupancy(self.ip_record) + + self.ip_record.reload() + discharge_patient(self.ip_record) + + for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]: + frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) + + +def create_dosage_form(): + if not frappe.db.exists("Dosage Form", "Tablet"): + frappe.get_doc({"doctype": "Dosage Form", "dosage_form": "Tablet"}).insert() + + +def create_drug(item=None): + if not item: + item = "Dextromethorphan" + drug = frappe.db.exists("Item", {"item_code": "Dextromethorphan"}) + if not drug: + drug = frappe.get_doc( + { + "doctype": "Item", + "item_code": "Dextromethorphan", + "item_name": "Dextromethorphan", + "item_group": "Products", + "stock_uom": "Nos", + "is_stock_item": 1, + "valuation_rate": 50, + "opening_stock": 20, + } + ).insert() + + +def get_orders(): + create_dosage_form() + create_drug() + return { + "drug_code": "Dextromethorphan", + "drug_name": "Dextromethorphan", + "dosage": "1-1-1", + "dosage_form": "Tablet", + "period": "2 Day", + } + + +def create_ipmo(patient): + orders = get_orders() + ipmo = frappe.new_doc("Inpatient Medication Order") + ipmo.patient = patient + ipmo.company = "_Test Company" + ipmo.start_date = add_days(getdate(), -1) + ipmo.add_order_entries(orders) + + return ipmo + + +def create_ipme(filters, update_stock=0): + ipme = frappe.new_doc("Inpatient Medication Entry") + ipme.company = "_Test Company" + ipme.posting_date = getdate() + ipme.update_stock = update_stock + if update_stock: + ipme.warehouse = "Stores - _TC" + for key, value in filters.items(): + ipme.set(key, value) + ipme = ipme.get_medication_orders() + + return ipme diff --git a/healthcare/healthcare/doctype/inpatient_medication_order_entry/__init__.py b/healthcare/healthcare/doctype/inpatient_medication_order_entry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json b/healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json new file mode 100644 index 0000000..72999a9 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json @@ -0,0 +1,94 @@ +{ + "actions": [], + "creation": "2020-09-14 21:51:30.259164", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "drug", + "drug_name", + "dosage", + "dosage_form", + "instructions", + "column_break_4", + "date", + "time", + "is_completed" + ], + "fields": [ + { + "fieldname": "drug", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Drug", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "drug.item_name", + "fieldname": "drug_name", + "fieldtype": "Data", + "label": "Drug Name", + "read_only": 1 + }, + { + "fieldname": "dosage", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Dosage", + "reqd": 1 + }, + { + "fieldname": "dosage_form", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Dosage Form", + "options": "Dosage Form", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1 + }, + { + "fieldname": "time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "Time", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "is_completed", + "fieldtype": "Check", + "label": "Is Order Completed", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "instructions", + "fieldtype": "Small Text", + "label": "Instructions" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-09-30 14:03:26.755925", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Medication Order Entry", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py b/healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py new file mode 100644 index 0000000..ce60dc3 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class InpatientMedicationOrderEntry(Document): + pass diff --git a/healthcare/healthcare/doctype/inpatient_occupancy/__init__.py b/healthcare/healthcare/doctype/inpatient_occupancy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json new file mode 100644 index 0000000..3fa98b6 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json @@ -0,0 +1,64 @@ +{ + "actions": [], + "creation": "2018-07-12 12:07:36.932333", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "service_unit", + "check_in", + "left", + "check_out", + "invoiced" + ], + "fields": [ + { + "fieldname": "service_unit", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit", + "reqd": 1 + }, + { + "fieldname": "check_in", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Check In" + }, + { + "default": "0", + "fieldname": "left", + "fieldtype": "Check", + "label": "Left", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "check_out", + "fieldtype": "Datetime", + "label": "Check Out" + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-03-18 15:08:54.634132", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Occupancy", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py b/healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py new file mode 100644 index 0000000..932284d --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class InpatientOccupancy(Document): + pass diff --git a/healthcare/healthcare/doctype/inpatient_record/__init__.py b/healthcare/healthcare/doctype/inpatient_record/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/inpatient_record/inpatient_record.js b/healthcare/healthcare/doctype/inpatient_record/inpatient_record.js new file mode 100644 index 0000000..e7d81ef --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_record/inpatient_record.js @@ -0,0 +1,319 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Inpatient Record', { + setup: function(frm) { + frm.get_field('drug_prescription').grid.editable_fields = [ + {fieldname: 'drug_code', columns: 2}, + {fieldname: 'drug_name', columns: 2}, + {fieldname: 'dosage', columns: 2}, + {fieldname: 'period', columns: 2} + ]; + }, + refresh: function(frm) { + frm.set_query('admission_service_unit_type', function() { + return { + filters: { + 'inpatient_occupancy': 1, + 'allow_appointments': 0 + } + }; + }); + + frm.set_query('primary_practitioner', function() { + return { + filters: { + 'department': frm.doc.medical_department + } + }; + }); + if (!frm.doc.__islocal) { + if (frm.doc.status == 'Admitted') { + frm.add_custom_button(__('Schedule Discharge'), function() { + schedule_discharge(frm); + }); + } else if (frm.doc.status == 'Admission Scheduled') { + frm.add_custom_button(__('Cancel Admission'), function() { + cancel_ip_order(frm) + }) + frm.add_custom_button(__('Admit'), function() { + admit_patient_dialog(frm); + } ); + } else if (frm.doc.status == 'Discharge Scheduled') { + frm.add_custom_button(__('Discharge'), function() { + discharge_patient(frm); + } ); + } + } + }, + btn_transfer: function(frm) { + transfer_patient_dialog(frm); + } +}); + +let discharge_patient = function(frm) { + frappe.call({ + doc: frm.doc, + method: 'discharge', + callback: function(data) { + if (!data.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('Processing Inpatient Discharge') + }); +}; + +let admit_patient_dialog = function(frm) { + let dialog = new frappe.ui.Dialog({ + title: 'Admit Patient', + width: 100, + fields: [ + {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', + options: 'Healthcare Service Unit Type', default: frm.doc.admission_service_unit_type + }, + {fieldtype: 'Link', label: 'Service Unit', fieldname: 'service_unit', + options: 'Healthcare Service Unit', reqd: 1 + }, + {fieldtype: 'Datetime', label: 'Admission Datetime', fieldname: 'check_in', + reqd: 1, default: frappe.datetime.now_datetime() + }, + {fieldtype: 'Date', label: 'Expected Discharge', fieldname: 'expected_discharge', + default: frm.doc.expected_length_of_stay ? frappe.datetime.add_days(frappe.datetime.now_datetime(), frm.doc.expected_length_of_stay) : '' + } + ], + primary_action_label: __('Admit'), + primary_action : function(){ + let service_unit = dialog.get_value('service_unit'); + let check_in = dialog.get_value('check_in'); + let expected_discharge = null; + if (dialog.get_value('expected_discharge')) { + expected_discharge = dialog.get_value('expected_discharge'); + } + if (!service_unit && !check_in) { + return; + } + frappe.call({ + doc: frm.doc, + method: 'admit', + args:{ + 'service_unit': service_unit, + 'check_in': check_in, + 'expected_discharge': expected_discharge + }, + callback: function(data) { + if (!data.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('Processing Patient Admission') + }); + frm.refresh_fields(); + dialog.hide(); + } + }); + + dialog.fields_dict['service_unit_type'].get_query = function() { + return { + filters: { + 'inpatient_occupancy': 1, + 'allow_appointments': 0 + } + }; + }; + dialog.fields_dict['service_unit'].get_query = function() { + return { + filters: { + 'is_group': 0, + 'company': frm.doc.company, + 'service_unit_type': dialog.get_value('service_unit_type'), + 'occupancy_status' : 'Vacant' + } + }; + }; + + dialog.show(); +}; + +let transfer_patient_dialog = function(frm) { + let dialog = new frappe.ui.Dialog({ + title: 'Transfer Patient', + width: 100, + fields: [ + {fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1}, + {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, + {fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1}, + {fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1, default: frappe.datetime.now_datetime()} + ], + primary_action_label: __('Transfer'), + primary_action : function() { + let service_unit = null; + let check_in = dialog.get_value('check_in'); + let leave_from = null; + if(dialog.get_value('leave_from')){ + leave_from = dialog.get_value('leave_from'); + } + if(dialog.get_value('service_unit')){ + service_unit = dialog.get_value('service_unit'); + } + if(check_in > frappe.datetime.now_datetime()){ + frappe.msgprint({ + title: __('Not Allowed'), + message: __('Check-in time cannot be greater than the current time'), + indicator: 'red' + }); + return; + } + frappe.call({ + doc: frm.doc, + method: 'transfer', + args:{ + 'service_unit': service_unit, + 'check_in': check_in, + 'leave_from': leave_from + }, + callback: function(data) { + if (!data.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('Process Transfer') + }); + frm.refresh_fields(); + dialog.hide(); + } + }); + + dialog.fields_dict['leave_from'].get_query = function(){ + return { + query : 'healthcare.healthcare.doctype.inpatient_record.inpatient_record.get_leave_from', + filters: {docname:frm.doc.name} + }; + }; + dialog.fields_dict['service_unit_type'].get_query = function(){ + return { + filters: { + 'inpatient_occupancy': 1, + 'allow_appointments': 0 + } + }; + }; + dialog.fields_dict['service_unit'].get_query = function(){ + return { + filters: { + 'is_group': 0, + 'service_unit_type': dialog.get_value('service_unit_type'), + 'occupancy_status' : 'Vacant' + } + }; + }; + + dialog.show(); + + let not_left_service_unit = null; + for (let inpatient_occupancy in frm.doc.inpatient_occupancies) { + if (frm.doc.inpatient_occupancies[inpatient_occupancy].left != 1) { + not_left_service_unit = frm.doc.inpatient_occupancies[inpatient_occupancy].service_unit; + } + } + dialog.set_values({ + 'leave_from': not_left_service_unit + }); +}; + +var schedule_discharge = function(frm) { + var dialog = new frappe.ui.Dialog ({ + title: 'Inpatient Discharge', + fields: [ + { + fieldtype: 'Link', + label: 'Discharge Practitioner', + fieldname: 'discharge_practitioner', + options: 'Healthcare Practitioner' + }, + { + fieldtype: 'Datetime', + label: 'Discharge Ordered DateTime', + fieldname: 'discharge_ordered_datetime', + default: frappe.datetime.now_datetime() + }, + { + fieldtype: 'Date', + label: 'Followup Date', + fieldname: 'followup_date' + }, + { + fieldtype: 'Column Break' + }, + { + fieldtype: 'Small Text', + label: 'Discharge Instructions', + fieldname: 'discharge_instructions' + }, + { + fieldtype: 'Section Break', + label:'Discharge Summary' + }, + { + fieldtype: 'Long Text', + label: 'Discharge Note', + fieldname: 'discharge_note' + } + ], + primary_action_label: __('Order Discharge'), + primary_action : function() { + var args = { + patient: frm.doc.patient, + discharge_practitioner: dialog.get_value('discharge_practitioner'), + discharge_ordered_datetime: dialog.get_value('discharge_ordered_datetime'), + followup_date: dialog.get_value('followup_date'), + discharge_instructions: dialog.get_value('discharge_instructions'), + discharge_note: dialog.get_value('discharge_note') + } + frappe.call ({ + method: 'healthcare.healthcare.doctype.inpatient_record.inpatient_record.schedule_discharge', + args: {args}, + callback: function(data) { + if(!data.exc){ + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: 'Scheduling Inpatient Discharge' + }); + frm.refresh_fields(); + dialog.hide(); + } + }); + + dialog.show(); + dialog.$wrapper.find('.modal-dialog').css('width', '800px'); +}; + +let cancel_ip_order = function(frm) { + frappe.prompt([ + { + fieldname: 'reason_for_cancellation', + label: __('Reason for Cancellation'), + fieldtype: 'Small Text', + reqd: 1 + } + ], + function(data) { + frappe.call({ + method: 'healthcare.healthcare.doctype.inpatient_record.inpatient_record.set_ip_order_cancelled', + async: false, + freeze: true, + args: { + inpatient_record: frm.doc.name, + reason: data.reason_for_cancellation + }, + callback: function(r) { + if (!r.exc) frm.reload_doc(); + } + }); + }, __('Reason for Cancellation'), __('Submit')); +} diff --git a/healthcare/healthcare/doctype/inpatient_record/inpatient_record.json b/healthcare/healthcare/doctype/inpatient_record/inpatient_record.json new file mode 100644 index 0000000..b8a00a1 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_record/inpatient_record.json @@ -0,0 +1,590 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2018-07-11 17:48:51.404139", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "naming_series", + "patient", + "patient_name", + "gender", + "blood_group", + "dob", + "mobile", + "email", + "phone", + "column_break_8", + "company", + "status", + "scheduled_date", + "admitted_datetime", + "expected_discharge", + "references", + "admission_encounter", + "admission_practitioner", + "medical_department", + "admission_ordered_for", + "admission_service_unit_type", + "admission_nursing_checklist_template", + "discharge_nursing_checklist_template", + "expected_length_of_stay", + "cb_admission", + "primary_practitioner", + "secondary_practitioner", + "admission_instruction", + "encounter_details_section", + "chief_complaint", + "column_break_29", + "diagnosis", + "medication_section", + "drug_prescription", + "investigations_section", + "lab_test_prescription", + "procedures_section", + "procedure_prescription", + "rehabilitation_section", + "therapy_plan", + "therapies", + "sb_inpatient_occupancy", + "inpatient_occupancies", + "btn_transfer", + "sb_discharge_details", + "discharge_ordered_date", + "discharge_practitioner", + "discharge_encounter", + "discharge_datetime", + "cb_discharge", + "discharge_instructions", + "followup_date", + "sb_discharge_note", + "discharge_note", + "cancellation_details_section", + "reason_for_cancellation" + ], + "fields": [ + { + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 1, + "label": "Series", + "options": "HLC-INP-.YYYY.-" + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1, + "set_only_once": 1 + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, + { + "fetch_from": "patient.blood_group", + "fieldname": "blood_group", + "fieldtype": "Select", + "label": "Blood Group", + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", + "read_only": 1 + }, + { + "fetch_from": "patient.dob", + "fieldname": "dob", + "fieldtype": "Date", + "label": "Date of birth", + "read_only": 1 + }, + { + "fetch_from": "patient.mobile", + "fieldname": "mobile", + "fieldtype": "Data", + "label": "Mobile", + "read_only": 1 + }, + { + "fetch_from": "patient.email", + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "options": "Email", + "read_only": 1 + }, + { + "fetch_from": "patient.phone", + "fieldname": "phone", + "fieldtype": "Data", + "label": "Phone", + "read_only": 1 + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "label": "Medical Department", + "mandatory_depends_on": "eval:!doc.admission_encounter;", + "options": "Medical Department", + "set_only_once": 1 + }, + { + "fieldname": "primary_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner (Primary)", + "mandatory_depends_on": "eval:!doc.admission_encounter;", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "secondary_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner (Secondary)", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "default": "Admission Scheduled", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged\nCancelled", + "read_only": 1 + }, + { + "default": "Today", + "fieldname": "scheduled_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Admission Schedule Date", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "admission_ordered_for", + "fieldtype": "Date", + "label": "Admission Ordered For", + "read_only": 1 + }, + { + "fieldname": "admitted_datetime", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Admitted Datetime", + "permlevel": 2 + }, + { + "fieldname": "expected_length_of_stay", + "fieldtype": "Int", + "label": "Expected Length of Stay", + "mandatory_depends_on": "eval:!doc.admission_encounter;", + "set_only_once": 1 + }, + { + "fieldname": "expected_discharge", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Expected Discharge", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "references", + "fieldtype": "Section Break", + "label": "Admission Order Details" + }, + { + "fieldname": "cb_admission", + "fieldtype": "Column Break" + }, + { + "fieldname": "admission_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, + { + "fieldname": "admission_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, + { + "fieldname": "chief_complaint", + "fieldtype": "Table MultiSelect", + "label": "Chief Complaint", + "options": "Patient Encounter Symptom", + "permlevel": 1 + }, + { + "fieldname": "admission_instruction", + "fieldtype": "Small Text", + "label": "Admission Instructions", + "set_only_once": 1 + }, + { + "fieldname": "cb_discharge", + "fieldtype": "Column Break" + }, + { + "fieldname": "discharge_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, + { + "fieldname": "discharge_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:!doc.__islocal;", + "fieldname": "medication_section", + "fieldtype": "Section Break", + "label": "Medications", + "permlevel": 1 + }, + { + "fieldname": "drug_prescription", + "fieldtype": "Table", + "options": "Drug Prescription", + "permlevel": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:!doc.__islocal;", + "fieldname": "investigations_section", + "fieldtype": "Section Break", + "label": "Investigations", + "permlevel": 1 + }, + { + "fieldname": "lab_test_prescription", + "fieldtype": "Table", + "options": "Lab Prescription", + "permlevel": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:!doc.__islocal;", + "fieldname": "procedures_section", + "fieldtype": "Section Break", + "label": "Procedures", + "permlevel": 1 + }, + { + "fieldname": "procedure_prescription", + "fieldtype": "Table", + "options": "Procedure Prescription", + "permlevel": 1 + }, + { + "depends_on": "eval:(doc.status != \"Admission Scheduled\")", + "fieldname": "sb_inpatient_occupancy", + "fieldtype": "Section Break", + "label": "Inpatient Occupancy" + }, + { + "fieldname": "admission_service_unit_type", + "fieldtype": "Link", + "label": "Admission Service Unit Type", + "mandatory_depends_on": "eval:!doc.admission_encounter;", + "options": "Healthcare Service Unit Type", + "set_only_once": 1 + }, + { + "fieldname": "inpatient_occupancies", + "fieldtype": "Table", + "options": "Inpatient Occupancy", + "permlevel": 2 + }, + { + "depends_on": "eval:doc.status == 'Admitted';", + "fieldname": "btn_transfer", + "fieldtype": "Button", + "label": "Transfer" + }, + { + "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")", + "fieldname": "sb_discharge_note", + "fieldtype": "Section Break", + "label": "Discharge Notes" + }, + { + "fieldname": "discharge_note", + "fieldtype": "Text Editor", + "permlevel": 1 + }, + { + "fetch_from": "admission_encounter.company", + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:(doc.status == \"Admitted\")", + "depends_on": "eval:!doc.__islocal", + "fieldname": "encounter_details_section", + "fieldtype": "Section Break", + "label": "Encounter Impression", + "permlevel": 1 + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "fieldname": "diagnosis", + "fieldtype": "Table MultiSelect", + "label": "Diagnosis", + "options": "Patient Encounter Diagnosis", + "permlevel": 1 + }, + { + "fieldname": "followup_date", + "fieldtype": "Date", + "label": "Follow Up Date" + }, + { + "collapsible": 1, + "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")", + "fieldname": "sb_discharge_details", + "fieldtype": "Section Break", + "label": "Discharge Details" + }, + { + "fieldname": "discharge_instructions", + "fieldtype": "Small Text", + "label": "Discharge Instructions" + }, + { + "fieldname": "discharge_ordered_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Discharge Ordered Date", + "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:!doc.__islocal;", + "fieldname": "rehabilitation_section", + "fieldtype": "Section Break", + "label": "Rehabilitation", + "permlevel": 1 + }, + { + "fieldname": "therapy_plan", + "fieldtype": "Link", + "hidden": 1, + "label": "Therapy Plan", + "options": "Therapy Plan", + "permlevel": 1, + "read_only": 1 + }, + { + "fieldname": "therapies", + "fieldtype": "Table", + "options": "Therapy Plan Detail", + "permlevel": 1 + }, + { + "fieldname": "discharge_datetime", + "fieldtype": "Datetime", + "label": "Discharge Date", + "permlevel": 2 + }, + { + "fieldname": "discharge_nursing_checklist_template", + "fieldtype": "Link", + "label": "Discharge Nursing Checklist Template", + "options": "Nursing Checklist Template" + }, + { + "fieldname": "admission_nursing_checklist_template", + "fieldtype": "Link", + "label": "Admission Nursing Checklist Template", + "options": "Nursing Checklist Template" + } + ], + "index_web_pages_for_search": 1, + "links": [ + { + "group": "Appointments", + "link_doctype": "Patient Appointment", + "link_fieldname": "inpatient_record" + }, + { + "group": "Clinical", + "link_doctype": "Patient Encounter", + "link_fieldname": "inpatient_record" + }, + { + "group": "Clinical", + "link_doctype": "Clinical Procedure", + "link_fieldname": "inpatient_record" + }, + { + "group": "Laboratory", + "link_doctype": "Sample Collection", + "link_fieldname": "inpatient_record" + }, + { + "group": "Laboratory", + "link_doctype": "Lab Test", + "link_fieldname": "inpatient_record" + }, + { + "group": "Nursing", + "link_doctype": "Nursing Task", + "link_fieldname": "reference_name" + }, + { + "group": "Nursing", + "link_doctype": "Vital Signs", + "link_fieldname": "inpatient_record" + }, + { + "collapsible": 1, + "fieldname": "cancellation_details_section", + "fieldtype": "Section Break", + "label": "Cancellation Details" + }, + { + "fieldname": "reason_for_cancellation", + "fieldtype": "Small Text", + "label": "Reason for Cancellation", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2022-04-28 17:26:28.973945", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Record", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "Physician", + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Nursing User" + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1 + } + ], + "links": [ + { + "link_doctype": "Nursing Task", + "link_fieldname": "reference_name" + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "patient", + "track_changes": 1 +} diff --git a/healthcare/healthcare/doctype/inpatient_record/inpatient_record.py b/healthcare/healthcare/doctype/inpatient_record/inpatient_record.py new file mode 100644 index 0000000..44fb84c --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_record/inpatient_record.py @@ -0,0 +1,381 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.desk.reportview import get_match_cond +from frappe.model.document import Document +from frappe.utils import get_datetime, get_link_to_form, getdate, now_datetime, today +from healthcare.healthcare.doctype.nursing_task.nursing_task import NursingTask +from healthcare.healthcare.utils import validate_nursing_tasks + + +class InpatientRecord(Document): + def after_insert(self): + frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name) + frappe.db.set_value("Patient", self.patient, "inpatient_status", self.status) + + if self.admission_encounter: # Update encounter + frappe.db.set_value( + "Patient Encounter", self.admission_encounter, "inpatient_record", self.name + ) + frappe.db.set_value( + "Patient Encounter", self.admission_encounter, "inpatient_status", self.status + ) + + if self.admission_nursing_checklist_template: + NursingTask.create_nursing_tasks_from_template( + template=self.admission_nursing_checklist_template, + doc=self, + ) + + def validate(self): + self.validate_dates() + self.validate_already_scheduled_or_admitted() + if self.status in ["Discharged", "Cancelled"]: + frappe.db.set_value( + "Patient", self.patient, {"inpatient_status": None, "inpatient_record": None} + ) + + def validate_dates(self): + if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or ( + getdate(self.discharge_ordered_date) < getdate(self.scheduled_date) + ): + frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date")) + + for entry in self.inpatient_occupancies: + if ( + entry.check_in + and entry.check_out + and get_datetime(entry.check_in) > get_datetime(entry.check_out) + ): + frappe.throw( + _("Row #{0}: Check Out datetime cannot be less than Check In datetime").format(entry.idx) + ) + + def validate_already_scheduled_or_admitted(self): + query = """ + select name, status + from `tabInpatient Record` + where (status = 'Admitted' or status = 'Admission Scheduled') + and name != %(name)s and patient = %(patient)s + """ + + ip_record = frappe.db.sql(query, {"name": self.name, "patient": self.patient}, as_dict=1) + + if ip_record: + msg = _( + ("Already {0} Patient {1} with Inpatient Record ").format(ip_record[0].status, self.patient) + + """ {0}""".format(ip_record[0].name) + ) + frappe.throw(msg) + + @frappe.whitelist() + def admit(self, service_unit, check_in, expected_discharge=None): + admit_patient(self, service_unit, check_in, expected_discharge) + + @frappe.whitelist() + def discharge(self): + discharge_patient(self) + + @frappe.whitelist() + def transfer(self, service_unit, check_in, leave_from): + if leave_from: + patient_leave_service_unit(self, check_in, leave_from) + if service_unit: + transfer_patient(self, service_unit, check_in) + + +@frappe.whitelist() +def schedule_inpatient(args): + admission_order = json.loads(args) # admission order via Encounter + if ( + not admission_order + or not admission_order["patient"] + or not admission_order["admission_encounter"] + ): + frappe.throw(_("Missing required details, did not create Inpatient Record")) + + inpatient_record = frappe.new_doc("Inpatient Record") + + # Admission order details + set_details_from_ip_order(inpatient_record, admission_order) + + # Patient details + patient = frappe.get_doc("Patient", admission_order["patient"]) + inpatient_record.patient = patient.name + inpatient_record.patient_name = patient.patient_name + inpatient_record.gender = patient.sex + inpatient_record.blood_group = patient.blood_group + inpatient_record.dob = patient.dob + inpatient_record.mobile = patient.mobile + inpatient_record.email = patient.email + inpatient_record.phone = patient.phone + inpatient_record.scheduled_date = today() + + # Set encounter details + encounter = frappe.get_doc("Patient Encounter", admission_order["admission_encounter"]) + if encounter and encounter.symptoms: # Symptoms + set_ip_child_records(inpatient_record, "chief_complaint", encounter.symptoms) + + if encounter and encounter.diagnosis: # Diagnosis + set_ip_child_records(inpatient_record, "diagnosis", encounter.diagnosis) + + if encounter and encounter.drug_prescription: # Medication + set_ip_child_records(inpatient_record, "drug_prescription", encounter.drug_prescription) + + if encounter and encounter.lab_test_prescription: # Lab Tests + set_ip_child_records(inpatient_record, "lab_test_prescription", encounter.lab_test_prescription) + + if encounter and encounter.procedure_prescription: # Procedure Prescription + set_ip_child_records( + inpatient_record, "procedure_prescription", encounter.procedure_prescription + ) + + if encounter and encounter.therapies: # Therapies + inpatient_record.therapy_plan = encounter.therapy_plan + set_ip_child_records(inpatient_record, "therapies", encounter.therapies) + + inpatient_record.status = "Admission Scheduled" + inpatient_record.save(ignore_permissions=True) + + +@frappe.whitelist() +def schedule_discharge(args): + discharge_order = json.loads(args) + inpatient_record_id = frappe.db.get_value( + "Patient", discharge_order["patient"], "inpatient_record" + ) + + if inpatient_record_id: + + inpatient_record = frappe.get_doc("Inpatient Record", inpatient_record_id) + check_out_inpatient(inpatient_record) + set_details_from_ip_order(inpatient_record, discharge_order) + inpatient_record.status = "Discharge Scheduled" + inpatient_record.save(ignore_permissions=True) + + frappe.db.set_value( + "Patient", discharge_order["patient"], "inpatient_status", inpatient_record.status + ) + if inpatient_record.discharge_encounter: + frappe.db.set_value( + "Patient Encounter", + inpatient_record.discharge_encounter, + "inpatient_status", + inpatient_record.status, + ) + + if inpatient_record.discharge_nursing_checklist_template: + NursingTask.create_nursing_tasks_from_template( + inpatient_record.discharge_nursing_checklist_template, + inpatient_record, + start_time=now_datetime(), + ) + + +def set_details_from_ip_order(inpatient_record, ip_order): + for key in ip_order: + inpatient_record.set(key, ip_order[key]) + + +def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child): + for item in encounter_child: + table = inpatient_record.append(inpatient_record_child) + for df in table.meta.get("fields"): + table.set(df.fieldname, item.get(df.fieldname)) + + +def check_out_inpatient(inpatient_record): + if inpatient_record.inpatient_occupancies: + for inpatient_occupancy in inpatient_record.inpatient_occupancies: + if inpatient_occupancy.left != 1: + inpatient_occupancy.left = True + inpatient_occupancy.check_out = now_datetime() + frappe.db.set_value( + "Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant" + ) + + +def discharge_patient(inpatient_record): + validate_nursing_tasks(inpatient_record) + + validate_inpatient_invoicing(inpatient_record) + + inpatient_record.discharge_datetime = now_datetime() + inpatient_record.status = "Discharged" + + inpatient_record.save(ignore_permissions=True) + + +def validate_inpatient_invoicing(inpatient_record): + if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"): + return + + pending_invoices = get_pending_invoices(inpatient_record) + + if pending_invoices: + message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ") + + formatted_doc_rows = "" + + for doctype, docnames in pending_invoices.items(): + formatted_doc_rows += """ + {0} + {1} + """.format( + doctype, docnames + ) + + message += """ + + + + + + {2} +
{0}{1}
+ """.format( + _("Healthcare Service"), _("Documents"), formatted_doc_rows + ) + + frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True) + + +def get_pending_invoices(inpatient_record): + pending_invoices = {} + if inpatient_record.inpatient_occupancies: + service_unit_names = False + for inpatient_occupancy in inpatient_record.inpatient_occupancies: + if not inpatient_occupancy.invoiced: + if is_service_unit_billable(inpatient_occupancy.service_unit): + if service_unit_names: + service_unit_names += ", " + inpatient_occupancy.service_unit + else: + service_unit_names = inpatient_occupancy.service_unit + if service_unit_names: + pending_invoices["Inpatient Occupancy"] = service_unit_names + + docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"] + + for doc in docs: + doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record) + if doc_name_list: + pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices) + + return pending_invoices + + +def get_pending_doc(doc, doc_name_list, pending_invoices): + if doc_name_list: + doc_ids = False + for doc_name in doc_name_list: + doc_link = get_link_to_form(doc, doc_name.name) + if doc_ids: + doc_ids += ", " + doc_link + else: + doc_ids = doc_link + if doc_ids: + pending_invoices[doc] = doc_ids + + return pending_invoices + + +def get_unbilled_inpatient_docs(doc, inpatient_record): + return frappe.db.get_list( + doc, + filters={ + "patient": inpatient_record.patient, + "inpatient_record": inpatient_record.name, + "docstatus": 1, + "invoiced": 0, + }, + ) + + +def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None): + validate_nursing_tasks(inpatient_record) + + inpatient_record.admitted_datetime = check_in + inpatient_record.status = "Admitted" + inpatient_record.expected_discharge = expected_discharge + + inpatient_record.set("inpatient_occupancies", []) + transfer_patient(inpatient_record, service_unit, check_in) + + frappe.db.set_value( + "Patient", + inpatient_record.patient, + {"inpatient_status": "Admitted", "inpatient_record": inpatient_record.name}, + ) + + +def transfer_patient(inpatient_record, service_unit, check_in): + item_line = inpatient_record.append("inpatient_occupancies", {}) + item_line.service_unit = service_unit + item_line.check_in = check_in + + inpatient_record.save(ignore_permissions=True) + + frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied") + + +def patient_leave_service_unit(inpatient_record, check_out, leave_from): + if inpatient_record.inpatient_occupancies: + for inpatient_occupancy in inpatient_record.inpatient_occupancies: + if inpatient_occupancy.left != 1 and inpatient_occupancy.service_unit == leave_from: + inpatient_occupancy.left = True + inpatient_occupancy.check_out = check_out + frappe.db.set_value( + "Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant" + ) + inpatient_record.save(ignore_permissions=True) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_leave_from(doctype, txt, searchfield, start, page_len, filters): + docname = filters["docname"] + + query = """select io.service_unit + from `tabInpatient Occupancy` io, `tabInpatient Record` ir + where io.parent = '{docname}' and io.parentfield = 'inpatient_occupancies' + and io.left!=1 and io.parent = ir.name""" + + return frappe.db.sql( + query.format( + **{"docname": docname, "searchfield": searchfield, "mcond": get_match_cond(doctype)} + ), + {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, + ) + + +def is_service_unit_billable(service_unit): + service_unit_doc = frappe.qb.DocType("Healthcare Service Unit") + service_unit_type = frappe.qb.DocType("Healthcare Service Unit Type") + result = ( + frappe.qb.from_(service_unit_doc) + .left_join(service_unit_type) + .on(service_unit_doc.service_unit_type == service_unit_type.name) + .select(service_unit_type.is_billable) + .where(service_unit_doc.name == service_unit) + ).run(as_dict=1) + return result[0].get("is_billable", 0) + + +@frappe.whitelist() +def set_ip_order_cancelled(inpatient_record, reason, encounter=None): + inpatient_record = frappe.get_doc("Inpatient Record", inpatient_record) + if inpatient_record.status == "Admission Scheduled": + inpatient_record.status = "Cancelled" + inpatient_record.reason_for_cancellation = reason + inpatient_record.save(ignore_permissions=True) + encounter_name = encounter if encounter else inpatient_record.admission_encounter + if encounter_name: + frappe.db.set_value( + "Patient Encounter", encounter_name, {"inpatient_status": None, "inpatient_record": None} + ) diff --git a/healthcare/healthcare/doctype/inpatient_record/inpatient_record_list.js b/healthcare/healthcare/doctype/inpatient_record/inpatient_record_list.js new file mode 100644 index 0000000..18839c2 --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_record/inpatient_record_list.js @@ -0,0 +1,17 @@ +// Copyright (c) 2021, healthcare and contributors +// For license information, please see license.txt + +frappe.listview_settings['Inpatient Record'] = { + filters: [['status', 'not in', ['Discharged']]], + get_indicator: function (doc) { + if (doc.status === 'Admission Scheduled') { + return [__('Admission Scheduled'), 'red', 'status, =, Admission Scheduled']; + } else if (doc.status === 'Admitted') { + return [__('Admitted'), 'blue', 'status, =, Admitted']; + } else if (doc.status === 'Discharge Scheduled') { + return [__('Discharge Scheduled'), 'orange', 'status, =, Discharge Scheduled']; + } else if (doc.status === 'Discharged') { + return [__('Discharged'), 'green', 'status, =, Discharged']; + } + } +}; \ No newline at end of file diff --git a/healthcare/healthcare/doctype/inpatient_record/test_inpatient_record.py b/healthcare/healthcare/doctype/inpatient_record/test_inpatient_record.py new file mode 100644 index 0000000..673c49c --- /dev/null +++ b/healthcare/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import now_datetime, today +from frappe.utils.make_random import get_random + +from healthcare.healthcare.doctype.inpatient_record.inpatient_record import ( + admit_patient, + discharge_patient, + schedule_discharge, +) +from healthcare.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter +from healthcare.healthcare.utils import get_encounters_to_invoice + + +class TestInpatientRecord(FrappeTestCase): + def test_admit_and_discharge(self): + frappe.db.sql("""delete from `tabInpatient Record`""") + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions=True) + self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record")) + self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status")) + + # Admit + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + self.assertEqual("Admitted", frappe.db.get_value("Patient", patient, "inpatient_status")) + self.assertEqual( + "Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status") + ) + + # Discharge + schedule_discharge(frappe.as_json({"patient": patient})) + self.assertEqual( + "Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status") + ) + + ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) + # Validate Pending Invoices + self.assertRaises(frappe.ValidationError, ip_record.discharge) + mark_invoiced_inpatient_occupancy(ip_record1) + + discharge_patient(ip_record1) + + self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record")) + self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status")) + + def test_allow_discharge_despite_unbilled_services(self): + frappe.db.sql("""delete from `tabInpatient Record`""") + setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=1) + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions=True) + + # Admit + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + + # Discharge + schedule_discharge(frappe.as_json({"patient": patient})) + self.assertEqual( + "Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status") + ) + + ip_record = frappe.get_doc("Inpatient Record", ip_record.name) + # Should not validate Pending Invoices + ip_record.discharge() + + self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record")) + self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status")) + + setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=0) + + def test_do_not_bill_patient_encounters_for_inpatients(self): + frappe.db.sql("""delete from `tabInpatient Record`""") + setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=1) + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions=True) + + # Admit + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + + # Patient Encounter + patient_encounter = create_patient_encounter() + encounters = get_encounters_to_invoice(patient, "_Test Company") + encounter_ids = [entry.reference_name for entry in encounters] + self.assertFalse(patient_encounter.name in encounter_ids) + + # Discharge + schedule_discharge(frappe.as_json({"patient": patient})) + self.assertEqual( + "Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status") + ) + + ip_record = frappe.get_doc("Inpatient Record", ip_record.name) + mark_invoiced_inpatient_occupancy(ip_record) + discharge_patient(ip_record) + setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=0) + + def test_validate_overlap_admission(self): + frappe.db.sql("""delete from `tabInpatient Record`""") + patient = create_patient() + + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions=True) + ip_record_new = create_inpatient(patient) + ip_record_new.expected_length_of_stay = 0 + self.assertRaises(frappe.ValidationError, ip_record_new.save) + + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + ip_record_new = create_inpatient(patient) + self.assertRaises(frappe.ValidationError, ip_record_new.save) + frappe.db.sql("""delete from `tabInpatient Record`""") + + +def mark_invoiced_inpatient_occupancy(ip_record): + if ip_record.inpatient_occupancies: + for inpatient_occupancy in ip_record.inpatient_occupancies: + inpatient_occupancy.invoiced = 1 + ip_record.save(ignore_permissions=True) + + +def setup_inpatient_settings(key, value): + settings = frappe.get_single("Healthcare Settings") + settings.set(key, value) + settings.save() + + +def create_inpatient(patient): + patient_obj = frappe.get_doc("Patient", patient) + inpatient_record = frappe.new_doc("Inpatient Record") + inpatient_record.patient = patient + inpatient_record.patient_name = patient_obj.patient_name + inpatient_record.gender = patient_obj.sex + inpatient_record.blood_group = patient_obj.blood_group + inpatient_record.dob = patient_obj.dob + inpatient_record.mobile = patient_obj.mobile + inpatient_record.email = patient_obj.email + inpatient_record.phone = patient_obj.phone + inpatient_record.inpatient = "Scheduled" + inpatient_record.scheduled_date = today() + inpatient_record.company = "_Test Company" + return inpatient_record + + +def get_healthcare_service_unit(unit_name=None): + if not unit_name: + service_unit = get_random( + "Healthcare Service Unit", filters={"inpatient_occupancy": 1, "company": "_Test Company"} + ) + else: + service_unit = frappe.db.exists( + "Healthcare Service Unit", {"healthcare_service_unit_name": unit_name} + ) + + if not service_unit: + service_unit = frappe.new_doc("Healthcare Service Unit") + service_unit.healthcare_service_unit_name = unit_name or "_Test Service Unit Ip Occupancy" + service_unit.company = "_Test Company" + service_unit.service_unit_type = get_service_unit_type() + service_unit.inpatient_occupancy = 1 + service_unit.occupancy_status = "Vacant" + service_unit.is_group = 0 + service_unit_parent_name = frappe.db.exists( + { + "doctype": "Healthcare Service Unit", + "healthcare_service_unit_name": "_Test All Healthcare Service Units", + "is_group": 1, + } + ) + if not service_unit_parent_name: + parent_service_unit = frappe.new_doc("Healthcare Service Unit") + parent_service_unit.healthcare_service_unit_name = "_Test All Healthcare Service Units" + parent_service_unit.is_group = 1 + parent_service_unit.save(ignore_permissions=True) + service_unit.parent_healthcare_service_unit = parent_service_unit.name + else: + service_unit.parent_healthcare_service_unit = service_unit_parent_name + service_unit.save(ignore_permissions=True) + return service_unit.name + return service_unit + + +def get_service_unit_type(): + service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1}) + + if not service_unit_type: + service_unit_type = frappe.new_doc("Healthcare Service Unit Type") + service_unit_type.service_unit_type = "_Test Service Unit Type Ip Occupancy" + service_unit_type.inpatient_occupancy = 1 + service_unit_type.save(ignore_permissions=True) + return service_unit_type.name + return service_unit_type + + +def create_patient(): + patient = frappe.db.exists("Patient", "_Test IPD Patient") + if not patient: + patient = frappe.new_doc("Patient") + patient.first_name = "_Test IPD Patient" + patient.sex = "Female" + patient.save(ignore_permissions=True) + patient = patient.name + return patient diff --git a/healthcare/healthcare/doctype/lab_prescription/__init__.py b/healthcare/healthcare/doctype/lab_prescription/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/lab_prescription/lab_prescription.json b/healthcare/healthcare/doctype/lab_prescription/lab_prescription.json new file mode 100644 index 0000000..0720bb4 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_prescription/lab_prescription.json @@ -0,0 +1,78 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-09-16 16:53:06.882970", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lab_test_code", + "lab_test_name", + "invoiced", + "column_break_4", + "lab_test_comment", + "lab_test_created" + ], + "fields": [ + { + "fieldname": "lab_test_code", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Lab Test", + "options": "Lab Test Template", + "reqd": 1 + }, + { + "fetch_from": "lab_test_code.lab_test_name", + "fieldname": "lab_test_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Lab Test Name" + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "lab_test_comment", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Comments" + }, + { + "default": "0", + "fieldname": "lab_test_created", + "fieldtype": "Check", + "hidden": 1, + "label": "Test Created", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1, + "search_index": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-02-26 17:03:00.255560", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Prescription", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/lab_prescription/lab_prescription.py b/healthcare/healthcare/doctype/lab_prescription/lab_prescription.py new file mode 100644 index 0000000..ee53fb7 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_prescription/lab_prescription.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class LabPrescription(Document): + pass diff --git a/healthcare/healthcare/doctype/lab_test/__init__.py b/healthcare/healthcare/doctype/lab_test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/lab_test/lab_test.js b/healthcare/healthcare/doctype/lab_test/lab_test.js new file mode 100644 index 0000000..2d2b7e4 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test/lab_test.js @@ -0,0 +1,263 @@ +// Copyright (c) 2016, ESS and contributors +// For license information, please see license.txt + +cur_frm.cscript.custom_refresh = function (doc) { + cur_frm.toggle_display('sb_sensitivity', doc.sensitivity_toggle); + cur_frm.toggle_display('organisms_section', doc.descriptive_toggle); + cur_frm.toggle_display('sb_descriptive', doc.descriptive_toggle); + cur_frm.toggle_display('sb_normal', doc.normal_toggle); + cur_frm.toggle_display('sb_descriptive_result', doc.imaging_toggle); +}; + +frappe.ui.form.on('Lab Test', { + setup: function (frm) { + frm.get_field('normal_test_items').grid.editable_fields = [ + { fieldname: 'lab_test_name', columns: 3 }, + { fieldname: 'lab_test_event', columns: 2 }, + { fieldname: 'result_value', columns: 2 }, + { fieldname: 'lab_test_uom', columns: 1 }, + { fieldname: 'normal_range', columns: 2 } + ]; + frm.get_field('descriptive_test_items').grid.editable_fields = [ + { fieldname: 'lab_test_particulars', columns: 3 }, + { fieldname: 'result_value', columns: 7 } + ]; + }, + refresh: function (frm) { + refresh_field('normal_test_items'); + refresh_field('descriptive_test_items'); + if (frm.doc.__islocal) { + frm.add_custom_button(__('Get from Patient Encounter'), function () { + get_lab_test_prescribed(frm); + }); + } + if (frappe.defaults.get_default('lab_test_approval_required') && frappe.user.has_role('LabTest Approver')) { + if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') { + frm.add_custom_button(__('Approve'), function () { + status_update(1, frm); + }, __('Actions')); + frm.add_custom_button(__('Reject'), function () { + status_update(0, frm); + }, __('Actions')); + } + } + + if (frm.doc.docstatus === 1 && frm.doc.sms_sent === 0 && frm.doc.status !== 'Rejected' ) { + frm.add_custom_button(__('Send SMS'), function () { + frappe.call({ + method: 'healthcare.healthcare.doctype.healthcare_settings.healthcare_settings.get_sms_text', + args: { doc: frm.doc.name }, + callback: function (r) { + if (!r.exc) { + var emailed = r.message.emailed; + var printed = r.message.printed; + make_dialog(frm, emailed, printed); + } + } + }); + }); + } + }, +}); + + +frappe.ui.form.on('Lab Test', 'patient', function (frm) { + if (frm.doc.patient) { + frappe.call({ + 'method': 'healthcare.healthcare.doctype.patient.patient.get_patient_detail', + args: { patient: frm.doc.patient }, + callback: function (data) { + var age = null; + if (data.message.dob) { + age = calculate_age(data.message.dob); + } + let values = { + 'patient_age': age, + 'patient_sex': data.message.sex, + 'email': data.message.email, + 'mobile': data.message.mobile, + 'report_preference': data.message.report_preference + }; + frm.set_value(values); + } + }); + } +}); + +frappe.ui.form.on('Normal Test Result', { + normal_test_items_remove: function () { + frappe.msgprint(__('Not permitted, configure Lab Test Template as required')); + cur_frm.reload_doc(); + } +}); + +frappe.ui.form.on('Descriptive Test Result', { + descriptive_test_items_remove: function () { + frappe.msgprint(__('Not permitted, configure Lab Test Template as required')); + cur_frm.reload_doc(); + } +}); + +var status_update = function (approve, frm) { + var doc = frm.doc; + var status = null; + if (approve == 1) { + status = 'Approved'; + } + else { + status = 'Rejected'; + } + frappe.call({ + method: 'healthcare.healthcare.doctype.lab_test.lab_test.update_status', + args: { status: status, name: doc.name }, + callback: function () { + cur_frm.reload_doc(); + } + }); +}; + +var get_lab_test_prescribed = function (frm) { + if (frm.doc.patient) { + frappe.call({ + method: 'healthcare.healthcare.doctype.lab_test.lab_test.get_lab_test_prescribed', + args: { patient: frm.doc.patient }, + callback: function (r) { + show_lab_tests(frm, r.message); + } + }); + } + else { + frappe.msgprint(__('Please select Patient to get Lab Tests')); + } +}; + +var show_lab_tests = function (frm, lab_test_list) { + var d = new frappe.ui.Dialog({ + title: __('Lab Tests'), + fields: [{ + fieldtype: 'HTML', fieldname: 'lab_test' + }] + }); + var html_field = d.fields_dict.lab_test.$wrapper; + html_field.empty(); + $.each(lab_test_list, function (x, y) { + var row = $(repl( + '
\ +
%(lab_test)s
\ +
%(practitioner_name)s
%(encounter)s
\ +
%(date)s
\ +
\ + \ +
\ +

', + { name: y[0], lab_test: y[1], encounter: y[2], invoiced: y[3], practitioner: y[4], practitioner_name: y[5], date: y[6] }) + ).appendTo(html_field); + + row.find("a").click(function () { + frm.doc.template = $(this).attr('data-lab-test'); + frm.doc.prescription = $(this).attr('data-name'); + frm.doc.practitioner = $(this).attr('data-practitioner'); + frm.set_df_property('template', 'read_only', 1); + frm.set_df_property('patient', 'read_only', 1); + frm.set_df_property('practitioner', 'read_only', 1); + frm.doc.invoiced = 0; + if ($(this).attr('data-invoiced') === 1) { + frm.doc.invoiced = 1; + } + refresh_field('invoiced'); + refresh_field('template'); + d.hide(); + return false; + }); + }); + if (!lab_test_list.length) { + var msg = __('No Lab Tests found for the Patient {0}', [frm.doc.patient_name.bold()]); + html_field.empty(); + $(repl('
%(msg)s
', { msg: msg })).appendTo(html_field); + } + d.show(); +}; + +var make_dialog = function (frm, emailed, printed) { + var number = frm.doc.mobile; + + var dialog = new frappe.ui.Dialog({ + title: 'Send SMS', + width: 400, + fields: [ + { fieldname: 'result_format', fieldtype: 'Select', label: 'Result Format', options: ['Emailed', 'Printed'] }, + { fieldname: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 }, + { fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 } + ], + primary_action_label: __('Send'), + primary_action: function () { + var values = dialog.fields_dict; + if (!values) { + return; + } + send_sms(values, frm); + dialog.hide(); + } + }); + if (frm.doc.report_preference === 'Print') { + dialog.set_values({ + 'result_format': 'Printed', + 'number': number, + 'message': printed + }); + } else { + dialog.set_values({ + 'result_format': 'Emailed', + 'number': number, + 'message': emailed + }); + } + var fd = dialog.fields_dict; + $(fd.result_format.input).change(function () { + if (dialog.get_value('result_format') === 'Emailed') { + dialog.set_values({ + 'number': number, + 'message': emailed + }); + } else { + dialog.set_values({ + 'number': number, + 'message': printed + }); + } + }); + dialog.show(); +}; + +var send_sms = function (vals, frm) { + var number = vals.number.value; + var message = vals.message.last_value; + + if (!number || !message) { + frappe.throw(__('Did not send SMS, missing patient mobile number or message content.')); + } + frappe.call({ + method: 'frappe.core.doctype.sms_settings.sms_settings.send_sms', + args: { + receiver_list: [number], + msg: message + }, + callback: function (r) { + if (r.exc) { + frappe.msgprint(r.exc); + } else { + frm.reload_doc(); + } + } + }); +}; + +var calculate_age = function (dob) { + var ageMS = Date.parse(Date()) - Date.parse(dob); + var age = new Date(); + age.setTime(ageMS); + var years = age.getFullYear() - 1970; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; +}; diff --git a/healthcare/healthcare/doctype/lab_test/lab_test.json b/healthcare/healthcare/doctype/lab_test/lab_test.json new file mode 100644 index 0000000..42a1a4d --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test/lab_test.json @@ -0,0 +1,648 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2016-03-29 17:34:47.509094", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "template", + "lab_test_name", + "lab_test_group", + "medical_code", + "department", + "column_break_26", + "company", + "status", + "submitted_date", + "result_date", + "approved_date", + "expected_result_date", + "expected_result_time", + "printed_on", + "invoiced", + "sb_first", + "patient", + "patient_name", + "patient_age", + "patient_sex", + "inpatient_record", + "report_preference", + "email", + "mobile", + "c_b", + "practitioner", + "practitioner_name", + "requesting_department", + "employee", + "employee_name", + "employee_designation", + "user", + "sample", + "sb_normal", + "lab_test_html", + "normal_test_items", + "sb_descriptive_result", + "descriptive_result", + "sb_descriptive", + "descriptive_test_items", + "organisms_section", + "organism_test_items", + "sb_sensitivity", + "sensitivity_test_items", + "sb_comments", + "lab_test_comment", + "sb_customresult", + "custom_result", + "worksheet_section", + "worksheet_instructions", + "result_legend_section", + "legend_print_position", + "result_legend", + "section_break_50", + "email_sent", + "sms_sent", + "printed", + "normal_toggle", + "imaging_toggle", + "descriptive_toggle", + "sensitivity_toggle", + "amended_from", + "prescription" + ], + "fields": [ + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-LAB-.YYYY.-", + "print_hide": 1, + "report_hide": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fetch_from": "inpatient_record.patient", + "fieldname": "patient", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1, + "search_index": 1, + "set_only_once": 1 + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "label": "Age", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "patient_sex", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Requesting Practitioner", + "no_copy": 1, + "options": "Healthcare Practitioner", + "search_index": 1 + }, + { + "fetch_from": "patient.email", + "fieldname": "email", + "fieldtype": "Data", + "hidden": 1, + "label": "Email", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fetch_from": "patient.mobile", + "fieldname": "mobile", + "fieldtype": "Data", + "hidden": 1, + "label": "Mobile", + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "search_index": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "c_b", + "fieldtype": "Column Break", + "print_hide": 1 + }, + { + "fetch_from": "template.department", + "fieldname": "department", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Medical Department", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "submitted_date", + "fieldtype": "Datetime", + "label": "Submitted Date", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "approved_date", + "fieldtype": "Datetime", + "hidden": 1, + "label": "Approved Date", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "sample", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_filter": 1, + "label": "Sample ID", + "options": "Sample Collection", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "default": "Today", + "fieldname": "expected_result_date", + "fieldtype": "Date", + "hidden": 1, + "label": "Expected Result Date", + "read_only": 1 + }, + { + "fieldname": "expected_result_time", + "fieldtype": "Time", + "hidden": 1, + "label": "Expected Result Time", + "read_only": 1 + }, + { + "fieldname": "result_date", + "fieldtype": "Date", + "label": "Result Date", + "read_only": 1, + "search_index": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "printed_on", + "fieldtype": "Datetime", + "label": "Printed on", + "read_only": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee (Lab Technician)", + "no_copy": 1, + "options": "Employee", + "print_hide": 1, + "report_hide": 1 + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Lab Technician Name", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fetch_from": "employee.designation", + "fieldname": "employee_designation", + "fieldtype": "Data", + "label": "Lab Technician Designation", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "user", + "fieldtype": "Link", + "hidden": 1, + "label": "User", + "no_copy": 1, + "options": "User", + "print_hide": 1, + "report_hide": 1 + }, + { + "fetch_from": "patient.report_preference", + "fieldname": "report_preference", + "fieldtype": "Data", + "label": "Report Preference", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "sb_first", + "fieldtype": "Section Break" + }, + { + "fieldname": "lab_test_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Test Name", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "search_index": 1 + }, + { + "fieldname": "template", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Test Template", + "options": "Lab Test Template", + "print_hide": 1, + "report_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "lab_test_group", + "fieldtype": "Link", + "hidden": 1, + "label": "Test Group", + "options": "Item Group", + "print_hide": 1, + "report_hide": 1 + }, + { + "fetch_from": "template.medical_code", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code", + "read_only": 1 + }, + { + "fieldname": "sb_normal", + "fieldtype": "Section Break", + "label": "Compound Test Result" + }, + { + "fieldname": "normal_test_items", + "fieldtype": "Table", + "label": "Normal Test Result", + "options": "Normal Test Result", + "print_hide": 1 + }, + { + "fieldname": "lab_test_html", + "fieldtype": "HTML" + }, + { + "depends_on": "descriptive_toggle", + "fieldname": "organisms_section", + "fieldtype": "Section Break", + "label": "Organism Test Result" + }, + { + "fieldname": "sb_sensitivity", + "fieldtype": "Section Break", + "label": "Sensitivity Test Result" + }, + { + "fieldname": "sensitivity_test_items", + "fieldtype": "Table", + "label": "Sensitivity Test Result", + "options": "Sensitivity Test Result", + "print_hide": 1, + "report_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_comments", + "fieldtype": "Section Break", + "label": "Comments" + }, + { + "fieldname": "lab_test_comment", + "fieldtype": "Text", + "ignore_xss_filter": 1, + "label": "Comments", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_customresult", + "fieldtype": "Section Break", + "label": "Custom Result" + }, + { + "fieldname": "custom_result", + "fieldtype": "Text Editor", + "ignore_xss_filter": 1, + "label": "Custom Result", + "print_hide": 1 + }, + { + "default": "0", + "fieldname": "email_sent", + "fieldtype": "Check", + "hidden": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "sms_sent", + "fieldtype": "Check", + "hidden": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "printed", + "fieldtype": "Check", + "hidden": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "normal_toggle", + "fieldtype": "Check", + "hidden": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "sensitivity_toggle", + "fieldtype": "Check", + "hidden": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Lab Test", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "prescription", + "fieldtype": "Link", + "hidden": 1, + "label": "Prescription", + "no_copy": 1, + "options": "Lab Prescription", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, + { + "fetch_from": "practitioner.department", + "fieldname": "requesting_department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Requesting Department", + "options": "Medical Department", + "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "label": "Requesting Practitioner", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "result_legend_section", + "fieldtype": "Section Break", + "label": "Result Legend Print" + }, + { + "fieldname": "legend_print_position", + "fieldtype": "Select", + "label": "Print Position", + "options": "\nBottom\nTop\nBoth", + "print_hide": 1 + }, + { + "fieldname": "result_legend", + "fieldtype": "Text Editor", + "label": "Result Legend", + "print_hide": 1 + }, + { + "fieldname": "section_break_50", + "fieldtype": "Section Break" + }, + { + "fieldname": "worksheet_instructions", + "fieldtype": "Text Editor", + "label": "Worksheet Instructions", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "worksheet_section", + "fieldtype": "Section Break", + "label": "Worksheet Print" + }, + { + "fieldname": "descriptive_test_items", + "fieldtype": "Table", + "label": "Descriptive Test Result", + "options": "Descriptive Test Result", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "sb_descriptive", + "fieldtype": "Section Break", + "label": "Descriptive Test Result" + }, + { + "default": "0", + "fieldname": "descriptive_toggle", + "fieldtype": "Check", + "hidden": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "organism_test_items", + "fieldtype": "Table", + "label": "Organism Test Result", + "options": "Organism Test Result", + "print_hide": 1 + }, + { + "fieldname": "descriptive_result", + "fieldtype": "Text Editor", + "label": "Descriptive Result" + }, + { + "fieldname": "sb_descriptive_result", + "fieldtype": "Section Break", + "label": "Descriptive Result" + }, + { + "default": "0", + "fieldname": "imaging_toggle", + "fieldtype": "Check" + } + ], + "is_submittable": 1, + "links": [ + { + "link_doctype": "Nursing Task", + "link_fieldname": "reference_name" + } + ], + "modified": "2022-10-17 05:26:49.198517", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LabTest Approver", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "select": 1, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient,practitioner,lab_test_name,sample", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "patient", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/lab_test/lab_test.py b/healthcare/healthcare/doctype/lab_test/lab_test.py new file mode 100644 index 0000000..a52482a --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test/lab_test.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import get_link_to_form, getdate, now_datetime +from healthcare.healthcare.doctype.nursing_task.nursing_task import NursingTask + + +class LabTest(Document): + def validate(self): + if not self.is_new(): + self.set_secondary_uom_result() + + def on_submit(self): + from healthcare.healthcare.utils import validate_nursing_tasks + + validate_nursing_tasks(self) + self.validate_result_values() + self.db_set("submitted_date", getdate()) + self.db_set("status", "Completed") + + def on_cancel(self): + self.db_set("status", "Cancelled") + self.reload() + + def on_update(self): + if self.sensitivity_test_items: + sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity) + for i, item in enumerate(sensitivity): + item.idx = i + 1 + self.sensitivity_test_items = sensitivity + + def after_insert(self): + if self.prescription: + frappe.db.set_value("Lab Prescription", self.prescription, "lab_test_created", 1) + if frappe.db.get_value("Lab Prescription", self.prescription, "invoiced"): + self.invoiced = True + if self.template: + self.load_test_from_template() + self.reload() + + # create nursing tasks + template = frappe.db.get_value("Lab Test Template", self.template, "nursing_checklist_template") + if template: + NursingTask.create_nursing_tasks_from_template(template, self, start_time=now_datetime()) + + def load_test_from_template(self): + lab_test = self + create_test_from_template(lab_test) + self.reload() + + def set_secondary_uom_result(self): + for item in self.normal_test_items: + if item.result_value and item.secondary_uom and item.conversion_factor: + try: + item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor) + except Exception: + item.secondary_uom_result = "" + frappe.msgprint( + _("Row #{0}: Result for Secondary UOM not calculated").format(item.idx), title=_("Warning") + ) + + def validate_result_values(self): + if self.normal_test_items: + for item in self.normal_test_items: + if not item.result_value and not item.allow_blank and item.require_result_value: + frappe.throw( + _("Row #{0}: Please enter the result value for {1}").format( + item.idx, frappe.bold(item.lab_test_name) + ), + title=_("Mandatory Results"), + ) + + if self.descriptive_test_items: + for item in self.descriptive_test_items: + if not item.result_value and not item.allow_blank and item.require_result_value: + frappe.throw( + _("Row #{0}: Please enter the result value for {1}").format( + item.idx, frappe.bold(item.lab_test_particulars) + ), + title=_("Mandatory Results"), + ) + + +def create_test_from_template(lab_test): + template = frappe.get_doc("Lab Test Template", lab_test.template) + patient = frappe.get_doc("Patient", lab_test.patient) + + lab_test.lab_test_name = template.lab_test_name + lab_test.result_date = getdate() + lab_test.department = template.department + lab_test.lab_test_group = template.lab_test_group + lab_test.legend_print_position = template.legend_print_position + lab_test.result_legend = template.result_legend + lab_test.worksheet_instructions = template.worksheet_instructions + + lab_test = create_sample_collection(lab_test, template, patient, None) + load_result_format(lab_test, template, None, None) + + +@frappe.whitelist() +def update_status(status, name): + if name and status: + frappe.db.set_value("Lab Test", name, {"status": status, "approved_date": getdate()}) + + +@frappe.whitelist() +def create_multiple(doctype, docname): + if not doctype or not docname: + frappe.throw( + _("Sales Invoice or Patient Encounter is required to create Lab Tests"), + title=_("Insufficient Data"), + ) + + lab_test_created = False + if doctype == "Sales Invoice": + lab_test_created = create_lab_test_from_invoice(docname) + elif doctype == "Patient Encounter": + lab_test_created = create_lab_test_from_encounter(docname) + + if lab_test_created: + frappe.msgprint( + _("Lab Test(s) {0} created successfully").format(lab_test_created), indicator="green" + ) + else: + frappe.msgprint(_("No Lab Tests created")) + + +def create_lab_test_from_encounter(encounter): + lab_test_created = False + encounter = frappe.get_doc("Patient Encounter", encounter) + + if encounter and encounter.lab_test_prescription: + patient = frappe.get_doc("Patient", encounter.patient) + for item in encounter.lab_test_prescription: + if not item.lab_test_created: + template = get_lab_test_template(item.lab_test_code) + if template: + lab_test = create_lab_test_doc( + item.invoiced, encounter.practitioner, patient, template, encounter.company + ) + lab_test.save(ignore_permissions=True) + frappe.db.set_value("Lab Prescription", item.name, "lab_test_created", 1) + if not lab_test_created: + lab_test_created = lab_test.name + else: + lab_test_created += ", " + lab_test.name + return lab_test_created + + +def create_lab_test_from_invoice(sales_invoice): + lab_tests_created = False + invoice = frappe.get_doc("Sales Invoice", sales_invoice) + if invoice and invoice.patient: + patient = frappe.get_doc("Patient", invoice.patient) + for item in invoice.items: + lab_test_created = 0 + if item.reference_dt == "Lab Prescription": + lab_test_created = frappe.db.get_value( + "Lab Prescription", item.reference_dn, "lab_test_created" + ) + elif item.reference_dt == "Lab Test": + lab_test_created = 1 + if lab_test_created != 1: + template = get_lab_test_template(item.item_code) + if template: + lab_test = create_lab_test_doc( + True, invoice.ref_practitioner, patient, template, invoice.company + ) + if item.reference_dt == "Lab Prescription": + lab_test.prescription = item.reference_dn + lab_test.save(ignore_permissions=True) + if item.reference_dt != "Lab Prescription": + frappe.db.set_value("Sales Invoice Item", item.name, "reference_dt", "Lab Test") + frappe.db.set_value("Sales Invoice Item", item.name, "reference_dn", lab_test.name) + if not lab_tests_created: + lab_tests_created = lab_test.name + else: + lab_tests_created += ", " + lab_test.name + return lab_tests_created + + +def get_lab_test_template(item): + template_id = frappe.db.exists("Lab Test Template", {"item": item}) + if template_id: + return frappe.get_doc("Lab Test Template", template_id) + return False + + +def create_lab_test_doc(invoiced, practitioner, patient, template, company): + lab_test = frappe.new_doc("Lab Test") + lab_test.invoiced = invoiced + lab_test.practitioner = practitioner + lab_test.patient = patient.name + lab_test.patient_age = patient.get_age() + lab_test.patient_sex = patient.sex + lab_test.email = patient.email + lab_test.mobile = patient.mobile + lab_test.report_preference = patient.report_preference + lab_test.department = template.department + lab_test.template = template.name + lab_test.lab_test_group = template.lab_test_group + lab_test.result_date = getdate() + lab_test.company = company + return lab_test + + +def create_normals(template, lab_test): + lab_test.normal_toggle = 1 + normal = lab_test.append("normal_test_items") + normal.lab_test_name = template.lab_test_name + normal.lab_test_uom = template.lab_test_uom + normal.secondary_uom = template.secondary_uom + normal.conversion_factor = template.conversion_factor + normal.normal_range = template.lab_test_normal_range + normal.require_result_value = 1 + normal.allow_blank = 0 + normal.template = template.name + + +def create_imaging(template, lab_test): + lab_test.imaging_toggle = 1 + lab_test.template = template.name + lab_test.lab_test_name = template.lab_test_name + lab_test.descriptive_result = template.descriptive_result + + +def create_compounds(template, lab_test, is_group): + lab_test.normal_toggle = 1 + for normal_test_template in template.normal_test_templates: + normal = lab_test.append("normal_test_items") + if is_group: + normal.lab_test_event = normal_test_template.lab_test_event + else: + normal.lab_test_name = normal_test_template.lab_test_event + + normal.lab_test_uom = normal_test_template.lab_test_uom + normal.secondary_uom = normal_test_template.secondary_uom + normal.conversion_factor = normal_test_template.conversion_factor + normal.normal_range = normal_test_template.normal_range + normal.require_result_value = 1 + normal.allow_blank = normal_test_template.allow_blank + normal.template = template.name + + +def create_descriptives(template, lab_test): + lab_test.descriptive_toggle = 1 + if template.sensitivity: + lab_test.sensitivity_toggle = 1 + for descriptive_test_template in template.descriptive_test_templates: + descriptive = lab_test.append("descriptive_test_items") + descriptive.lab_test_particulars = descriptive_test_template.particulars + descriptive.require_result_value = 1 + descriptive.allow_blank = descriptive_test_template.allow_blank + descriptive.template = template.name + + +def create_sample_doc(template, patient, invoice, company=None): + if template.sample: + sample_exists = frappe.db.exists( + { + "doctype": "Sample Collection", + "patient": patient.name, + "docstatus": 0, + "sample": template.sample, + } + ) + + if sample_exists: + # update sample collection by adding quantity + sample_collection = frappe.get_doc("Sample Collection", sample_exists[0][0]) + quantity = int(sample_collection.sample_qty) + int(template.sample_qty) + if template.sample_details: + sample_details = sample_collection.sample_details + "\n-\n" + _("Test :") + sample_details += (template.get("lab_test_name") or template.get("template")) + "\n" + sample_details += _("Collection Details:") + "\n\t" + template.sample_details + frappe.db.set_value( + "Sample Collection", sample_collection.name, "sample_details", sample_details + ) + + frappe.db.set_value("Sample Collection", sample_collection.name, "sample_qty", quantity) + + else: + # Create Sample Collection for template, copy vals from Invoice + sample_collection = frappe.new_doc("Sample Collection") + if invoice: + sample_collection.invoiced = True + + sample_collection.patient = patient.name + sample_collection.patient_age = patient.get_age() + sample_collection.patient_sex = patient.sex + sample_collection.sample = template.sample + sample_collection.sample_uom = template.sample_uom + sample_collection.sample_qty = template.sample_qty + sample_collection.company = company + + if template.sample_details: + sample_collection.sample_details = ( + _("Test :") + + (template.get("lab_test_name") or template.get("template")) + + "\n" + + "Collection Detials:\n\t" + + template.sample_details + ) + sample_collection.save(ignore_permissions=True) + + return sample_collection + + +def create_sample_collection(lab_test, template, patient, invoice): + if frappe.get_cached_value("Healthcare Settings", None, "create_sample_collection_for_lab_test"): + sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) + if sample_collection: + lab_test.sample = sample_collection.name + sample_collection_doc = get_link_to_form("Sample Collection", sample_collection.name) + frappe.msgprint( + _("Sample Collection {0} has been created").format(sample_collection_doc), + title=_("Sample Collection"), + indicator="green", + ) + return lab_test + + +def load_result_format(lab_test, template, prescription, invoice): + if template.lab_test_template_type == "Single": + create_normals(template, lab_test) + + elif template.lab_test_template_type == "Compound": + create_compounds(template, lab_test, False) + + elif template.lab_test_template_type == "Descriptive": + create_descriptives(template, lab_test) + + elif template.lab_test_template_type == "Imaging": + create_imaging(template, lab_test) + + elif template.lab_test_template_type == "Grouped": + # Iterate for each template in the group and create one result for all. + for lab_test_group in template.lab_test_groups: + # Template_in_group = None + if lab_test_group.lab_test_template: + template_in_group = frappe.get_doc("Lab Test Template", lab_test_group.lab_test_template) + if template_in_group: + if template_in_group.lab_test_template_type == "Single": + create_normals(template_in_group, lab_test) + + elif template_in_group.lab_test_template_type == "Compound": + normal_heading = lab_test.append("normal_test_items") + normal_heading.lab_test_name = template_in_group.lab_test_name + normal_heading.require_result_value = 0 + normal_heading.allow_blank = 1 + normal_heading.template = template_in_group.name + create_compounds(template_in_group, lab_test, True) + + elif template_in_group.lab_test_template_type == "Descriptive": + descriptive_heading = lab_test.append("descriptive_test_items") + descriptive_heading.lab_test_name = template_in_group.lab_test_name + descriptive_heading.require_result_value = 0 + descriptive_heading.allow_blank = 1 + descriptive_heading.template = template_in_group.name + create_descriptives(template_in_group, lab_test) + + else: # Lab Test Group - Add New Line + normal = lab_test.append("normal_test_items") + normal.lab_test_name = lab_test_group.group_event + normal.lab_test_uom = lab_test_group.group_test_uom + normal.secondary_uom = lab_test_group.secondary_uom + normal.conversion_factor = lab_test_group.conversion_factor + normal.normal_range = lab_test_group.group_test_normal_range + normal.allow_blank = lab_test_group.allow_blank + normal.require_result_value = 1 + normal.template = template.name + + if template.lab_test_template_type != "No Result": + if prescription: + lab_test.prescription = prescription + if invoice: + frappe.db.set_value("Lab Prescription", prescription, "invoiced", True) + lab_test.save(ignore_permissions=True) # Insert the result + return lab_test + + +@frappe.whitelist() +def get_employee_by_user_id(user_id): + emp_id = frappe.db.exists("Employee", {"user_id": user_id}) + if emp_id: + return frappe.get_doc("Employee", emp_id) + return None + + +@frappe.whitelist() +def get_lab_test_prescribed(patient): + return frappe.db.sql( + """ + select + lp.name, + lp.lab_test_code, + lp.parent, + lp.invoiced, + pe.practitioner, + pe.practitioner_name, + pe.encounter_date + from + `tabPatient Encounter` pe, `tabLab Prescription` lp + where + pe.patient=%s + and lp.parent=pe.name + and lp.lab_test_created=0 + """, + (patient), + ) diff --git a/healthcare/healthcare/doctype/lab_test/lab_test_list.js b/healthcare/healthcare/doctype/lab_test/lab_test_list.js new file mode 100644 index 0000000..ccaf2c3 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test/lab_test_list.js @@ -0,0 +1,71 @@ +/* +(c) ESS 2015-16 +*/ +frappe.listview_settings['Lab Test'] = { + add_fields: ['name', 'status', 'invoiced'], + filters: [['docstatus', '=', '1']], + get_indicator: function (doc) { + if (doc.status === 'Approved') { + return [__('Approved'), 'green', 'status, =, Approved']; + } else if (doc.status === 'Rejected') { + return [__('Rejected'), 'orange', 'status, =, Rejected']; + } else if (doc.status === 'Completed') { + return [__('Completed'), 'green', 'status, =, Completed']; + } else if (doc.status === 'Cancelled') { + return [__('Cancelled'), 'red', 'status, =, Cancelled']; + } + }, + onload: function (listview) { + listview.page.add_menu_item(__('Create Multiple'), function () { + create_multiple_dialog(listview); + }); + } +}; + +var create_multiple_dialog = function (listview) { + var dialog = new frappe.ui.Dialog({ + title: 'Create Multiple Lab Tests', + width: 100, + fields: [ + { fieldtype: 'Link', label: 'Patient', fieldname: 'patient', options: 'Patient', reqd: 1 }, + { + fieldtype: 'Select', label: 'Invoice / Patient Encounter', fieldname: 'doctype', + options: '\nSales Invoice\nPatient Encounter', reqd: 1 + }, + { + fieldtype: 'Dynamic Link', fieldname: 'docname', options: 'doctype', reqd: 1, + get_query: function () { + return { + filters: { + 'patient': dialog.get_value('patient'), + 'docstatus': 1 + } + }; + } + } + ], + primary_action_label: __('Create'), + primary_action: function () { + frappe.call({ + method: 'healthcare.healthcare.doctype.lab_test.lab_test.create_multiple', + args: { + 'doctype': dialog.get_value('doctype'), + 'docname': dialog.get_value('docname') + }, + callback: function (data) { + if (!data.exc) { + if (!data.message) { + frappe.msgprint(__('No Lab Tests created')); + } + listview.refresh(); + } + }, + freeze: true, + freeze_message: __('Creating Lab Tests...') + }); + dialog.hide(); + } + }); + + dialog.show(); +}; diff --git a/healthcare/healthcare/doctype/lab_test/test_lab_test.py b/healthcare/healthcare/doctype/lab_test/test_lab_test.py new file mode 100644 index 0000000..b77c3c4 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test/test_lab_test.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import getdate, nowtime + +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import ( + get_income_account, + get_receivable_account, +) +from healthcare.healthcare.doctype.lab_test.lab_test import create_multiple +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_patient, +) +from healthcare.healthcare.doctype.patient_medical_record.test_patient_medical_record import ( + create_lab_test_template as create_blood_test_template, +) + + +class TestLabTest(FrappeTestCase): + def test_lab_test_item(self): + lab_template = create_lab_test_template() + self.assertTrue(frappe.db.exists("Item", lab_template.item)) + self.assertEqual( + frappe.db.get_value("Item Price", {"item_code": lab_template.item}, "price_list_rate"), + lab_template.lab_test_rate, + ) + + lab_template.disabled = 1 + lab_template.save() + self.assertEqual(frappe.db.get_value("Item", lab_template.item, "disabled"), 1) + + lab_template.reload() + + lab_template.disabled = 0 + lab_template.save() + + def test_descriptive_lab_test(self): + lab_template = create_lab_test_template() + + # blank result value not allowed as per template + lab_test = create_lab_test(lab_template) + lab_test.descriptive_test_items[0].result_value = 12 + lab_test.descriptive_test_items[2].result_value = 1 + lab_test.save() + self.assertRaises(frappe.ValidationError, lab_test.submit) + + def test_sample_collection(self): + frappe.db.set_value( + "Healthcare Settings", "Healthcare Settings", "create_sample_collection_for_lab_test", 1 + ) + lab_template = create_lab_test_template() + + lab_test = create_lab_test(lab_template) + lab_test.descriptive_test_items[0].result_value = 12 + lab_test.descriptive_test_items[1].result_value = 1 + lab_test.descriptive_test_items[2].result_value = 2.3 + lab_test.save() + + # check sample collection created + self.assertTrue(frappe.db.exists("Sample Collection", {"sample": lab_template.sample})) + + frappe.db.set_value( + "Healthcare Settings", "Healthcare Settings", "create_sample_collection_for_lab_test", 0 + ) + lab_test = create_lab_test(lab_template) + lab_test.descriptive_test_items[0].result_value = 12 + lab_test.descriptive_test_items[1].result_value = 1 + lab_test.descriptive_test_items[2].result_value = 2.3 + lab_test.save() + + # sample collection should not be created + lab_test.reload() + self.assertEqual(lab_test.sample, None) + + def test_create_lab_tests_from_sales_invoice(self): + sales_invoice = create_sales_invoice() + create_multiple("Sales Invoice", sales_invoice.name) + sales_invoice.reload() + self.assertIsNotNone(sales_invoice.items[0].reference_dn) + self.assertIsNotNone(sales_invoice.items[1].reference_dn) + + def test_create_lab_tests_from_patient_encounter(self): + patient_encounter = create_patient_encounter() + create_multiple("Patient Encounter", patient_encounter.name) + patient_encounter.reload() + self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created) + self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created) + + +def create_lab_test_template(test_sensitivity=0, sample_collection=1): + medical_department = create_medical_department() + if frappe.db.exists("Lab Test Template", "Insulin Resistance"): + return frappe.get_doc("Lab Test Template", "Insulin Resistance") + template = frappe.new_doc("Lab Test Template") + template.lab_test_name = "Insulin Resistance" + template.lab_test_template_type = "Descriptive" + template.lab_test_code = "Insulin Resistance" + template.lab_test_group = "Services" + template.department = medical_department + template.is_billable = 1 + template.lab_test_description = "Insulin Resistance" + template.lab_test_rate = 2000 + + for entry in ["FBS", "Insulin", "IR"]: + template.append( + "descriptive_test_templates", {"particulars": entry, "allow_blank": 1 if entry == "IR" else 0} + ) + + if test_sensitivity: + template.sensitivity = 1 + + if sample_collection: + template.sample = create_lab_test_sample() + template.sample_qty = 5.0 + + template.save() + return template + + +def create_medical_department(): + medical_department = frappe.db.exists("Medical Department", "_Test Medical Department") + if not medical_department: + medical_department = frappe.new_doc("Medical Department") + medical_department.department = "_Test Medical Department" + medical_department.save() + medical_department = medical_department.name + + return medical_department + + +def create_lab_test(lab_template): + patient = create_patient() + lab_test = frappe.new_doc("Lab Test") + lab_test.template = lab_template.name + lab_test.patient = patient + lab_test.patient_sex = "Female" + lab_test.save() + + return lab_test + + +def create_lab_test_sample(): + blood_sample = frappe.db.exists("Lab Test Sample", "Blood Sample") + if blood_sample: + return blood_sample + + sample = frappe.new_doc("Lab Test Sample") + sample.sample = "Blood Sample" + sample.sample_uom = "U/ml" + sample.save() + + return sample.name + + +def create_sales_invoice(): + patient = create_patient() + medical_department = create_medical_department() + insulin_resistance_template = create_lab_test_template() + blood_test_template = create_blood_test_template(medical_department) + + sales_invoice = frappe.new_doc("Sales Invoice") + sales_invoice.patient = patient + sales_invoice.customer = frappe.db.get_value("Patient", patient, "customer") + sales_invoice.due_date = getdate() + sales_invoice.company = "_Test Company" + sales_invoice.debit_to = get_receivable_account("_Test Company") + + tests = [insulin_resistance_template, blood_test_template] + for entry in tests: + sales_invoice.append( + "items", + { + "item_code": entry.item, + "item_name": entry.lab_test_name, + "description": entry.lab_test_description, + "qty": 1, + "uom": "Nos", + "conversion_factor": 1, + "income_account": get_income_account(None, "_Test Company"), + "rate": entry.lab_test_rate, + "amount": entry.lab_test_rate, + }, + ) + + sales_invoice.set_missing_values() + + sales_invoice.submit() + return sales_invoice + + +def create_patient_encounter(): + patient = create_patient() + medical_department = create_medical_department() + insulin_resistance_template = create_lab_test_template() + blood_test_template = create_blood_test_template(medical_department) + + patient_encounter = frappe.new_doc("Patient Encounter") + patient_encounter.patient = patient + patient_encounter.practitioner = create_practitioner() + patient_encounter.encounter_date = getdate() + patient_encounter.encounter_time = nowtime() + + tests = [insulin_resistance_template, blood_test_template] + for entry in tests: + patient_encounter.append( + "lab_test_prescription", {"lab_test_code": entry.item, "lab_test_name": entry.lab_test_name} + ) + + patient_encounter.submit() + return patient_encounter + + +def create_practitioner(): + practitioner = frappe.db.exists("Healthcare Practitioner", "_Test Healthcare Practitioner") + + if not practitioner: + practitioner = frappe.new_doc("Healthcare Practitioner") + practitioner.first_name = "_Test Healthcare Practitioner" + practitioner.gender = "Female" + practitioner.op_consulting_charge = 500 + practitioner.inpatient_visit_charge = 500 + practitioner.save(ignore_permissions=True) + practitioner = practitioner.name + + return practitioner diff --git a/healthcare/healthcare/doctype/lab_test_group_template/__init__.py b/healthcare/healthcare/doctype/lab_test_group_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.json b/healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.json new file mode 100644 index 0000000..2767f7e --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.json @@ -0,0 +1,119 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-03-29 17:37:29.913583", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "template_or_new_line", + "lab_test_template", + "lab_test_rate", + "lab_test_description", + "group_event", + "group_test_uom", + "secondary_uom", + "conversion_factor", + "allow_blank", + "column_break_8", + "group_test_normal_range" + ], + "fields": [ + { + "default": "Add Test", + "fieldname": "template_or_new_line", + "fieldtype": "Select", + "options": "Add Test\nAdd New Line", + "print_hide": 1, + "report_hide": 1 + }, + { + "depends_on": "eval:doc.template_or_new_line == 'Add Test'", + "fieldname": "lab_test_template", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Test Name", + "options": "Lab Test Template" + }, + { + "fetch_from": "lab_test_template.lab_test_rate", + "fieldname": "lab_test_rate", + "fieldtype": "Currency", + "label": "Rate", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fetch_from": "lab_test_template.lab_test_description", + "fieldname": "lab_test_description", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Description", + "read_only": 1 + }, + { + "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", + "fieldname": "group_event", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Event" + }, + { + "depends_on": "eval:doc.template_or_new_line =='Add New Line'", + "fieldname": "group_test_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", + "fieldname": "group_test_normal_range", + "fieldtype": "Long Text", + "ignore_xss_filter": 1, + "label": "Normal Range" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.template_or_new_line =='Add New Line'", + "fieldname": "secondary_uom", + "fieldtype": "Link", + "label": "Secondary UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "mandatory_depends_on": "secondary_uom" + }, + { + "default": "0", + "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", + "fieldname": "allow_blank", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Blank" + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-30 12:36:03.082391", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test Group Template", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.py b/healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.py new file mode 100644 index 0000000..c5ba9c2 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_group_template/lab_test_group_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class LabTestGroupTemplate(Document): + pass diff --git a/healthcare/healthcare/doctype/lab_test_sample/__init__.py b/healthcare/healthcare/doctype/lab_test_sample/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.js b/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.js new file mode 100644 index 0000000..7bace5f --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Lab Test Sample', { +}); diff --git a/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.json b/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.json new file mode 100644 index 0000000..2830038 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.json @@ -0,0 +1,68 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:sample", + "beta": 1, + "creation": "2016-04-04 17:35:44.823951", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "sample", + "sample_uom" + ], + "fields": [ + { + "fieldname": "sample", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Sample", + "reqd": 1, + "unique": 1 + }, + { + "bold": 1, + "fieldname": "sample_uom", + "fieldtype": "Link", + "in_list_view": 1, + "label": "UOM", + "options": "Lab Test UOM" + } + ], + "links": [], + "modified": "2020-01-29 23:02:02.249839", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test Sample", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "share": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "sample", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.py b/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.py new file mode 100644 index 0000000..c1df6a3 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_sample/lab_test_sample.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class LabTestSample(Document): + pass diff --git a/healthcare/healthcare/doctype/lab_test_sample/test_lab_test_sample.py b/healthcare/healthcare/doctype/lab_test_sample/test_lab_test_sample.py new file mode 100644 index 0000000..57a99b5 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_sample/test_lab_test_sample.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestLabTestSample(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/lab_test_template/__init__.py b/healthcare/healthcare/doctype/lab_test_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/lab_test_template/lab_test_template.js b/healthcare/healthcare/doctype/lab_test_template/lab_test_template.js new file mode 100644 index 0000000..9d284e1 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_template/lab_test_template.js @@ -0,0 +1,124 @@ +// Copyright (c) 2016, ESS +// License: ESS license.txt + +frappe.ui.form.on('Lab Test Template', { + lab_test_name: function(frm) { + if (!frm.doc.lab_test_code) + frm.set_value('lab_test_code', frm.doc.lab_test_name); + if (!frm.doc.lab_test_description) + frm.set_value('lab_test_description', frm.doc.lab_test_name); + }, + refresh : function(frm) { + // Restrict Special, Grouped type templates in Child Test Groups + frm.set_query('lab_test_template', 'lab_test_groups', function() { + return { + filters: { + lab_test_template_type: ['in', ['Single','Compound']] + } + }; + }); + + frm.set_query('item', function() { + return { + filters: { + 'disabled': false, + 'is_stock_item': false + } + }; + }); + }, + medical_code: function(frm) { + frm.set_query('medical_code', function() { + return { + filters: { + medical_code_standard: frm.doc.medical_code_standard + } + }; + }); + }, + + link_existing_item: function (frm) { + if (frm.doc.link_existing_item) { + frm.set_value('lab_test_code', ''); + } else { + frm.set_value('item', ''); + } + }, + + item: function (frm) { + if (frm.doc.item) { + frappe.db.get_value('Item', frm.doc.item, ['item_group']) + .then(r => { + frm.set_value({ + 'lab_test_group': r.message.item_group, + 'lab_test_code': frm.doc.item + }); + }) + } + } +}); + +cur_frm.cscript.custom_refresh = function(doc) { + if (!doc.__islocal) { + cur_frm.add_custom_button(__('Change Template Code'), function() { + change_template_code(doc); + }); + } +}; + +let change_template_code = function(doc) { + let d = new frappe.ui.Dialog({ + title:__('Change Template Code'), + fields:[ + { + 'fieldtype': 'Data', + 'label': 'Lab Test Template Code', + 'fieldname': 'lab_test_code', + reqd: 1 + } + ], + primary_action: function() { + let values = d.get_values(); + if (values) { + frappe.call({ + 'method': 'healthcare.healthcare.doctype.lab_test_template.lab_test_template.change_test_code_from_template', + 'args': {lab_test_code: values.lab_test_code, doc: doc}, + callback: function (data) { + frappe.set_route('Form', 'Lab Test Template', data.message); + } + }); + } + d.hide(); + }, + primary_action_label: __('Change Template Code') + }); + d.show(); + + d.set_values({ + 'lab_test_code': doc.link_existing_item ? doc.item : doc.lab_test_code + }); +}; + +frappe.ui.form.on('Lab Test Template', 'lab_test_name', function(frm) { + frm.doc.change_in_item = 1; +}); + +frappe.ui.form.on('Lab Test Template', 'lab_test_rate', function(frm) { + frm.doc.change_in_item = 1; +}); + +frappe.ui.form.on('Lab Test Template', 'lab_test_group', function(frm) { + frm.doc.change_in_item = 1; +}); + +frappe.ui.form.on('Lab Test Template', 'lab_test_description', function(frm) { + frm.doc.change_in_item = 1; +}); + +frappe.ui.form.on('Lab Test Groups', 'template_or_new_line', function (frm, cdt, cdn) { + let child = locals[cdt][cdn]; + if (child.template_or_new_line == 'Add New Line') { + frappe.model.set_value(cdt, cdn, 'lab_test_template', ''); + frappe.model.set_value(cdt, cdn, 'lab_test_description', ''); + } +}); diff --git a/healthcare/healthcare/doctype/lab_test_template/lab_test_template.json b/healthcare/healthcare/doctype/lab_test_template/lab_test_template.json new file mode 100644 index 0000000..79f02d5 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_template/lab_test_template.json @@ -0,0 +1,391 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:lab_test_name", + "beta": 1, + "creation": "2016-03-29 17:35:36.761223", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "lab_test_name", + "link_existing_item", + "item", + "lab_test_code", + "lab_test_group", + "department", + "nursing_checklist_template", + "column_break_3", + "disabled", + "lab_test_template_type", + "is_billable", + "lab_test_rate", + "section_break_description", + "lab_test_description", + "section_break_descriptive_result", + "descriptive_result", + "section_break_normal", + "lab_test_uom", + "secondary_uom", + "conversion_factor", + "column_break_10", + "lab_test_normal_range", + "section_break_compound", + "normal_test_templates", + "section_break_special", + "sensitivity", + "descriptive_test_templates", + "section_break_group", + "lab_test_groups", + "sb_sample_collection", + "sample", + "sample_uom", + "sample_qty", + "column_break_33", + "sample_details", + "medical_coding_section", + "medical_code", + "medical_code_standard", + "worksheet_section", + "worksheet_instructions", + "result_legend_section", + "legend_print_position", + "result_legend", + "change_in_item" + ], + "fields": [ + { + "fieldname": "lab_test_name", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Test Name", + "no_copy": 1, + "reqd": 1, + "unique": 1 + }, + { + "depends_on": "eval: !doc.__islocal || doc.link_existing_item", + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "mandatory_depends_on": "eval:doc.link_existing_item", + "no_copy": 1, + "options": "Item", + "read_only_depends_on": "eval: !doc.__islocal", + "search_index": 1 + }, + { + "fieldname": "lab_test_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Code", + "mandatory_depends_on": "eval:!doc.link_existing_item", + "no_copy": 1, + "read_only_depends_on": "eval:!doc.__islocal" + }, + { + "fieldname": "lab_test_group", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Item Group", + "options": "Item Group", + "read_only_depends_on": "eval:doc.link_existing_item", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "department", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Medical Department", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "description": "Single: Results which require only a single input.\n
\nCompound: Results which require multiple event inputs.\n
\nDescriptive: Tests which have multiple result components with manual result entry.\n
\nGrouped: Test templates which are a group of other test templates.\n
\nNo Result: Tests with no results, can be ordered and billed but no Lab Test will be created. e.g.. Sub Tests for Grouped results", + "fieldname": "lab_test_template_type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Result Format", + "options": "\nSingle\nCompound\nDescriptive\nGrouped\nImaging\nNo Result" + }, + { + "default": "1", + "depends_on": "eval:doc.lab_test_template_type != 'Grouped' && (!doc.link_existing_item || !doc.__islocal);", + "description": "If unchecked, the item will not be available in Sales Invoices for billing but can be used in group test creation. ", + "fieldname": "is_billable", + "fieldtype": "Check", + "label": "Is Billable", + "search_index": 1 + }, + { + "depends_on": "eval:doc.is_billable && (!doc.link_existing_item || !doc.__islocal)", + "description": "This value is updated in the Default Sales Price List.", + "fieldname": "lab_test_rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "mandatory_depends_on": "eval:doc.is_billable && !doc.link_existing_item" + }, + { + "collapsible": 1, + "fieldname": "medical_coding_section", + "fieldtype": "Section Break", + "label": "Medical Coding" + }, + { + "depends_on": "medical_code_standard", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code" + }, + { + "fieldname": "medical_code_standard", + "fieldtype": "Link", + "label": "Medical Code Standard", + "options": "Medical Code Standard" + }, + { + "depends_on": "eval:doc.lab_test_template_type == 'Single'", + "fieldname": "section_break_normal", + "fieldtype": "Section Break", + "label": "Lab Routine" + }, + { + "fieldname": "lab_test_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "UOM", + "options": "Lab Test UOM" + }, + { + "fieldname": "lab_test_normal_range", + "fieldtype": "Long Text", + "ignore_xss_filter": 1, + "label": "Normal Range" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.lab_test_template_type == 'Compound'", + "fieldname": "section_break_compound", + "fieldtype": "Section Break", + "label": "Compound" + }, + { + "fieldname": "normal_test_templates", + "fieldtype": "Table", + "options": "Normal Test Template" + }, + { + "depends_on": "eval:doc.lab_test_template_type == 'Descriptive'", + "fieldname": "section_break_special", + "fieldtype": "Section Break", + "label": "Descriptive Test" + }, + { + "default": "0", + "fieldname": "sensitivity", + "fieldtype": "Check", + "label": "Sensitivity" + }, + { + "depends_on": "eval:doc.lab_test_template_type == 'Grouped'", + "fieldname": "section_break_group", + "fieldtype": "Section Break", + "label": "Group Tests" + }, + { + "fieldname": "lab_test_groups", + "fieldtype": "Table", + "options": "Lab Test Group Template" + }, + { + "collapsible": 1, + "fieldname": "section_break_description", + "fieldtype": "Section Break", + "label": "Description " + }, + { + "depends_on": "eval:doc.lab_test_template_type == 'Imaging'", + "fieldname": "lab_test_description", + "fieldtype": "Text Editor", + "ignore_xss_filter": 1, + "label": "Description", + "no_copy": 1 + }, + { + "fieldname": "sb_sample_collection", + "fieldtype": "Section Break", + "label": "Sample Collection" + }, + { + "fieldname": "sample", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Sample", + "options": "Lab Test Sample" + }, + { + "fetch_from": "sample.sample_uom", + "fieldname": "sample_uom", + "fieldtype": "Data", + "label": "UOM", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "change_in_item", + "fieldtype": "Check", + "hidden": 1, + "label": "Change In Item", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "default": "0", + "fieldname": "sample_qty", + "fieldtype": "Float", + "label": "Quantity" + }, + { + "fieldname": "sample_details", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Collection Details" + }, + { + "collapsible": 1, + "description": "Information to help easily interpret the test report, will be printed as part of the Lab Test result.", + "fieldname": "result_legend_section", + "fieldtype": "Section Break", + "label": "Result Legend Print" + }, + { + "fieldname": "result_legend", + "fieldtype": "Text Editor", + "label": "Result Legend" + }, + { + "fieldname": "legend_print_position", + "fieldtype": "Select", + "label": "Print Position", + "options": "Bottom\nTop\nBoth" + }, + { + "fieldname": "secondary_uom", + "fieldtype": "Link", + "label": "Secondary UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "mandatory_depends_on": "secondary_uom" + }, + { + "description": "Instructions to be printed on the worksheet", + "fieldname": "worksheet_instructions", + "fieldtype": "Text Editor", + "label": "Worksheet Instructions" + }, + { + "collapsible": 1, + "fieldname": "worksheet_section", + "fieldtype": "Section Break", + "label": "Worksheet Print" + }, + { + "fieldname": "descriptive_test_templates", + "fieldtype": "Table", + "options": "Descriptive Test Template" + }, + { + "fieldname": "column_break_33", + "fieldtype": "Column Break" + }, + { + "fieldname": "nursing_checklist_template", + "fieldtype": "Link", + "label": "Nursing Checklist Template", + "options": "Nursing Checklist Template" + }, + { + "fieldname": "descriptive_result", + "fieldtype": "Text Editor", + "label": "Descriptive Result" + }, + { + "depends_on": "eval:doc.lab_test_template_type == 'Imaging'", + "fieldname": "section_break_descriptive_result", + "fieldtype": "Section Break", + "label": "Descriptive Result" + }, + { + "default": "0", + "depends_on": "eval: doc.__islocal", + "fieldname": "link_existing_item", + "fieldtype": "Check", + "label": "Link existing Item" + } + ], + "links": [], + "migration_hash": "04ea062dcd8e59976e0c33bb708f4d77", + "modified": "2022-06-10 12:57:31.787436", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test Template", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "share": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "lab_test_code,lab_test_name,lab_test_template_type", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "lab_test_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/lab_test_template/lab_test_template.py b/healthcare/healthcare/doctype/lab_test_template/lab_test_template.py new file mode 100644 index 0000000..7457c8a --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_template/lab_test_template.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc +from frappe.utils import flt, today +from healthcare.healthcare.doctype.clinical_procedure_template.clinical_procedure_template import ( + make_item_price, + update_item_and_item_price, +) + + +class LabTestTemplate(Document): + def before_insert(self): + if self.link_existing_item and self.item: + price_list = frappe.db.get_all( + "Item Price", {"item_code": self.item}, ["price_list_rate"], order_by="valid_from desc" + ) + if price_list: + self.lab_test_rate = price_list[0].get("price_list_rate") + + def after_insert(self): + if not self.item and not self.link_existing_item: + create_item_from_template(self) + + def validate(self): + if ( + self.is_billable + and not self.link_existing_item + and (not self.lab_test_rate or self.lab_test_rate <= 0.0) + ): + frappe.throw(_("Standard Selling Rate should be greater than zero.")) + + if self.sample and flt(self.sample_qty) <= 0: + frappe.throw(_("Sample Quantity cannot be negative or 0"), title=_("Invalid Quantity")) + + self.validate_conversion_factor() + self.enable_disable_item() + + def on_update(self): + # If change_in_item update Item and Price List + if self.change_in_item: + update_item_and_item_price(self) + + def on_trash(self): + # Remove template reference from item and disable item + if self.item: + try: + item = self.item + self.db_set("item", "") + frappe.delete_doc("Item", item) + except Exception: + frappe.throw(_("Not permitted. Please disable the Lab Test Template")) + + def enable_disable_item(self): + if self.is_billable: + if self.disabled: + frappe.db.set_value("Item", self.item, "disabled", 1) + else: + frappe.db.set_value("Item", self.item, "disabled", 0) + + def update_item(self): + item = frappe.get_doc("Item", self.item) + if item: + item.update( + { + "item_name": self.lab_test_name, + "item_group": self.lab_test_group, + "disabled": 0, + "standard_rate": self.lab_test_rate, + "description": self.lab_test_description, + } + ) + item.flags.ignore_mandatory = True + item.save(ignore_permissions=True) + + def item_price_exists(self): + item_price = frappe.db.exists("Item Price", {"item_code": self.item, "valid_from": today()}) + if item_price: + return item_price + return False + + def validate_conversion_factor(self): + if self.lab_test_template_type == "Single" and self.secondary_uom and not self.conversion_factor: + frappe.throw(_("Conversion Factor is mandatory")) + if self.lab_test_template_type == "Compound": + for item in self.normal_test_templates: + if item.secondary_uom and not item.conversion_factor: + frappe.throw(_("Row #{0}: Conversion Factor is mandatory").format(item.idx)) + if self.lab_test_template_type == "Grouped": + for group in self.lab_test_groups: + if ( + group.template_or_new_line == "Add New Line" + and group.secondary_uom + and not group.conversion_factor + ): + frappe.throw(_("Row #{0}: Conversion Factor is mandatory").format(group.idx)) + + +def create_item_from_template(doc): + uom = frappe.db.exists("UOM", "Unit") or frappe.db.get_single_value("Stock Settings", "stock_uom") + # Insert item + item = frappe.get_doc( + { + "doctype": "Item", + "item_code": doc.lab_test_code, + "item_name": doc.lab_test_name, + "item_group": doc.lab_test_group, + "description": doc.lab_test_description, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "disabled": 0 if doc.is_billable and not doc.disabled else doc.disabled, + "stock_uom": uom, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + # Insert item price + if doc.is_billable and doc.lab_test_rate != 0.0: + price_list_name = frappe.db.get_value( + "Selling Settings", None, "selling_price_list" + ) or frappe.db.get_value("Price List", {"selling": 1}) + if doc.lab_test_rate: + make_item_price(item.name, doc.lab_test_rate) + else: + make_item_price(item.name, 0.0) + # Set item in the template + frappe.db.set_value("Lab Test Template", doc.name, "item", item.name) + + doc.reload() + + +@frappe.whitelist() +def change_test_code_from_template(lab_test_code, doc): + doc = frappe._dict(json.loads(doc)) + if frappe.db.exists({"doctype": "Item", "item_code": lab_test_code}): + frappe.throw(_("Lab Test Item {0} already exist").format(lab_test_code)) + else: + rename_doc("Item", doc.item, lab_test_code, ignore_permissions=True) + frappe.db.set_value( + "Lab Test Template", doc.name, {"lab_test_code": lab_test_code, "lab_test_name": lab_test_code} + ) + rename_doc("Lab Test Template", doc.name, lab_test_code, ignore_permissions=True) + return lab_test_code diff --git a/healthcare/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py b/healthcare/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py new file mode 100644 index 0000000..fef381a --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py @@ -0,0 +1,8 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "template", + "transactions": [{"label": _("Lab Tests"), "items": ["Lab Test"]}], + } diff --git a/healthcare/healthcare/doctype/lab_test_template/lab_test_template_list.js b/healthcare/healthcare/doctype/lab_test_template/lab_test_template_list.js new file mode 100644 index 0000000..08fc2cd --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_template/lab_test_template_list.js @@ -0,0 +1,7 @@ +/* +(c) ESS 2015-16 +*/ +frappe.listview_settings['Lab Test Template'] = { + add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'], + filters: [['disabled', '=', 'No']] +}; diff --git a/healthcare/healthcare/doctype/lab_test_template/test_lab_test_template.py b/healthcare/healthcare/doctype/lab_test_template/test_lab_test_template.py new file mode 100644 index 0000000..9a96366 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_template/test_lab_test_template.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Lab Test Template') + + +class TestLabTestTemplate(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/lab_test_uom/__init__.py b/healthcare/healthcare/doctype/lab_test_uom/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.js b/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.js new file mode 100644 index 0000000..2107e79 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.js @@ -0,0 +1,5 @@ +// Copyright (c) 2016, ESS and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Lab Test UOM', { +}); diff --git a/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.json b/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.json new file mode 100644 index 0000000..a6d5224 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.json @@ -0,0 +1,148 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:lab_test_uom", + "beta": 1, + "creation": "2016-03-29 17:28:08.630148", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "lab_test_uom", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Lab Test UOM", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "uom_description", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-09-04 11:02:53.202718", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test UOM", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "lab_test_uom", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "lab_test_uom", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.py b/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.py new file mode 100644 index 0000000..70c2ec2 --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_uom/lab_test_uom.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class LabTestUOM(Document): + pass diff --git a/healthcare/healthcare/doctype/lab_test_uom/test_lab_test_uom.py b/healthcare/healthcare/doctype/lab_test_uom/test_lab_test_uom.py new file mode 100644 index 0000000..3f37ccf --- /dev/null +++ b/healthcare/healthcare/doctype/lab_test_uom/test_lab_test_uom.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Lab Test UOM') + + +class TestLabTestUOM(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/medical_code/__init__.py b/healthcare/healthcare/doctype/medical_code/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/medical_code/medical_code.js b/healthcare/healthcare/doctype/medical_code/medical_code.js new file mode 100644 index 0000000..858d171 --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code/medical_code.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Medical Code', { +}); diff --git a/healthcare/healthcare/doctype/medical_code/medical_code.json b/healthcare/healthcare/doctype/medical_code/medical_code.json new file mode 100644 index 0000000..5d69830 --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code/medical_code.json @@ -0,0 +1,69 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "allow_rename": 1, + "beta": 1, + "creation": "2017-06-21 13:02:56.122897", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "medical_code_standard", + "code", + "description" + ], + "fields": [ + { + "fieldname": "medical_code_standard", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Medical Code Standard", + "options": "Medical Code Standard", + "reqd": 1 + }, + { + "fieldname": "code", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Code", + "reqd": 1, + "unique": 1 + }, + { + "bold": 1, + "fieldname": "description", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Description" + } + ], + "links": [], + "modified": "2020-06-29 14:02:30.980032", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Medical Code", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "search_fields": "code, description", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/medical_code/medical_code.py b/healthcare/healthcare/doctype/medical_code/medical_code.py new file mode 100644 index 0000000..e090c9b --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code/medical_code.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class MedicalCode(Document): + def autoname(self): + self.name = self.medical_code_standard + " " + self.code diff --git a/healthcare/healthcare/doctype/medical_code/test_medical_code.py b/healthcare/healthcare/doctype/medical_code/test_medical_code.py new file mode 100644 index 0000000..8c775ed --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code/test_medical_code.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestMedicalCode(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/medical_code_standard/__init__.py b/healthcare/healthcare/doctype/medical_code_standard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.js b/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.js new file mode 100644 index 0000000..ec986b0 --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Medical Code Standard', { +}); diff --git a/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.json b/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.json new file mode 100644 index 0000000..886938d --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.json @@ -0,0 +1,94 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:medical_code", + "beta": 1, + "creation": "2017-06-21 13:07:00.463176", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "medical_code", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Medical Code", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-08-31 14:15:40.820693", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Medical Code Standard", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.py b/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.py new file mode 100644 index 0000000..72c922b --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code_standard/medical_code_standard.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class MedicalCodeStandard(Document): + pass diff --git a/healthcare/healthcare/doctype/medical_code_standard/test_medical_code_standard.py b/healthcare/healthcare/doctype/medical_code_standard/test_medical_code_standard.py new file mode 100644 index 0000000..691ca47 --- /dev/null +++ b/healthcare/healthcare/doctype/medical_code_standard/test_medical_code_standard.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestMedicalCodeStandard(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/medical_department/__init__.py b/healthcare/healthcare/doctype/medical_department/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/medical_department/medical_department.js b/healthcare/healthcare/doctype/medical_department/medical_department.js new file mode 100644 index 0000000..25aeeb8 --- /dev/null +++ b/healthcare/healthcare/doctype/medical_department/medical_department.js @@ -0,0 +1,5 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Medical Department', { +}); diff --git a/healthcare/healthcare/doctype/medical_department/medical_department.json b/healthcare/healthcare/doctype/medical_department/medical_department.json new file mode 100644 index 0000000..40f14ca --- /dev/null +++ b/healthcare/healthcare/doctype/medical_department/medical_department.json @@ -0,0 +1,156 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:department", + "beta": 1, + "creation": "2017-02-27 13:38:30.806362", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Department", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-08-31 13:41:59.611698", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Medical Department", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "department", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "department", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/medical_department/medical_department.py b/healthcare/healthcare/doctype/medical_department/medical_department.py new file mode 100644 index 0000000..12af4e9 --- /dev/null +++ b/healthcare/healthcare/doctype/medical_department/medical_department.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class MedicalDepartment(Document): + pass diff --git a/healthcare/healthcare/doctype/medical_department/test_medical_department.py b/healthcare/healthcare/doctype/medical_department/test_medical_department.py new file mode 100644 index 0000000..ed4a439 --- /dev/null +++ b/healthcare/healthcare/doctype/medical_department/test_medical_department.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Medical Department') + + +class TestMedicalDepartment(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/normal_test_result/__init__.py b/healthcare/healthcare/doctype/normal_test_result/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/normal_test_result/normal_test_result.json b/healthcare/healthcare/doctype/normal_test_result/normal_test_result.json new file mode 100644 index 0000000..c8f43d3 --- /dev/null +++ b/healthcare/healthcare/doctype/normal_test_result/normal_test_result.json @@ -0,0 +1,186 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 15:06:08.295224", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lab_test_name", + "lab_test_event", + "result_value", + "lab_test_uom", + "secondary_uom_result", + "secondary_uom", + "conversion_factor", + "column_break_10", + "allow_blank", + "normal_range", + "lab_test_comment", + "bold", + "italic", + "underline", + "template", + "require_result_value" + ], + "fields": [ + { + "fieldname": "lab_test_name", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Test Name", + "read_only": 1 + }, + { + "fieldname": "lab_test_event", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Event", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "result_value", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Result Value" + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "lab_test_uom", + "fieldtype": "Link", + "label": "UOM", + "options": "Lab Test UOM", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "normal_range", + "fieldtype": "Long Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Normal Range", + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "lab_test_comment", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Comment", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "template", + "fieldtype": "Link", + "hidden": 1, + "label": "Template", + "options": "Lab Test Template", + "print_hide": 1, + "report_hide": 1 + }, + { + "depends_on": "eval:doc.require_result_value", + "fieldname": "secondary_uom", + "fieldtype": "Link", + "label": "Secondary UOM", + "options": "Lab Test UOM", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "mandatory_depends_on": "secondary_uom", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.require_result_value && doc.result_value", + "fieldname": "secondary_uom_result", + "fieldtype": "Data", + "label": "Secondary UOM Result", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "depends_on": "eval:doc.require_result_value", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "depends_on": "eval:doc.require_result_value", + "fieldname": "italic", + "fieldtype": "Check", + "label": "Italic", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "depends_on": "eval:doc.require_result_value", + "fieldname": "underline", + "fieldtype": "Check", + "label": "Underline", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "require_result_value", + "fieldtype": "Check", + "hidden": 1, + "label": "Require Result Value", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.require_result_value", + "fieldname": "allow_blank", + "fieldtype": "Check", + "label": "Allow Blank", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-08 16:03:17.522893", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Normal Test Result", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/normal_test_result/normal_test_result.py b/healthcare/healthcare/doctype/normal_test_result/normal_test_result.py new file mode 100644 index 0000000..26c34d9 --- /dev/null +++ b/healthcare/healthcare/doctype/normal_test_result/normal_test_result.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class NormalTestResult(Document): + pass diff --git a/healthcare/healthcare/doctype/normal_test_template/__init__.py b/healthcare/healthcare/doctype/normal_test_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/normal_test_template/normal_test_template.json b/healthcare/healthcare/doctype/normal_test_template/normal_test_template.json new file mode 100644 index 0000000..8dd6476 --- /dev/null +++ b/healthcare/healthcare/doctype/normal_test_template/normal_test_template.json @@ -0,0 +1,84 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2016-02-22 16:09:54.310628", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "heading_text", + "lab_test_event", + "allow_blank", + "lab_test_uom", + "secondary_uom", + "conversion_factor", + "column_break_5", + "normal_range" + ], + "fields": [ + { + "fieldname": "heading_text", + "fieldtype": "Heading", + "ignore_xss_filter": 1, + "label": "Test" + }, + { + "fieldname": "lab_test_event", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Event" + }, + { + "fieldname": "lab_test_uom", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "UOM", + "options": "Lab Test UOM" + }, + { + "fieldname": "normal_range", + "fieldtype": "Long Text", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Normal Range" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "secondary_uom", + "fieldtype": "Link", + "label": "Secondary UOM", + "options": "Lab Test UOM" + }, + { + "depends_on": "secondary_uom", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor", + "mandatory_depends_on": "secondary_uom" + }, + { + "default": "0", + "fieldname": "allow_blank", + "fieldtype": "Check", + "label": "Allow Blank" + } + ], + "istable": 1, + "links": [], + "modified": "2020-06-23 13:28:40.156224", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Normal Test Template", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/normal_test_template/normal_test_template.py b/healthcare/healthcare/doctype/normal_test_template/normal_test_template.py new file mode 100644 index 0000000..912f4fd --- /dev/null +++ b/healthcare/healthcare/doctype/normal_test_template/normal_test_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class NormalTestTemplate(Document): + pass diff --git a/healthcare/healthcare/doctype/nursing_checklist_template/__init__.py b/healthcare/healthcare/doctype/nursing_checklist_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.js b/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.js new file mode 100644 index 0000000..e3ad5c8 --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, healthcare and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Nursing Checklist Template', { + // refresh: function(frm) { + + // } +}); diff --git a/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.json b/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.json new file mode 100644 index 0000000..42f8942 --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.json @@ -0,0 +1,94 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:title", + "creation": "2021-10-20 12:03:38.538651", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "disabled", + "section_break_2", + "title", + "department", + "checklist_section", + "tasks" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Medical Department" + }, + { + "fieldname": "tasks", + "fieldtype": "Table", + "label": "Tasks", + "options": "Nursing Checklist Template Task", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "checklist_section", + "fieldtype": "Section Break", + "label": "Checklist" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2022-07-25 19:27:22.028262", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Nursing Checklist Template", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.py b/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.py new file mode 100644 index 0000000..757d03a --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_checklist_template/nursing_checklist_template.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, healthcare and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class NursingChecklistTemplate(Document): + pass diff --git a/healthcare/healthcare/doctype/nursing_checklist_template/test_nursing_checklist_template.py b/healthcare/healthcare/doctype/nursing_checklist_template/test_nursing_checklist_template.py new file mode 100644 index 0000000..274c88e --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_checklist_template/test_nursing_checklist_template.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, healthcare and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestNursingChecklistTemplate(unittest.TestCase): + pass diff --git a/healthcare/healthcare/doctype/nursing_checklist_template/test_records.json b/healthcare/healthcare/doctype/nursing_checklist_template/test_records.json new file mode 100644 index 0000000..e277f25 --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_checklist_template/test_records.json @@ -0,0 +1,18 @@ +[ + { + "doctype": "Nursing Checklist Template", + "name": "Discharge checklist", + "title": "Discharge checklist", + "tasks": [ + { + "doctype": "Nursing Checklist Template Task", + "activity": "BP Check", + "mandatory": 1, + "task_duration": 600, + "parent": "Discharge checklist", + "parentfield": "tasks", + "parenttype": "Nursing Checklist Template" + } + ] + } +] \ No newline at end of file diff --git a/healthcare/healthcare/doctype/nursing_checklist_template_task/__init__.py b/healthcare/healthcare/doctype/nursing_checklist_template_task/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.json b/healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.json new file mode 100644 index 0000000..4f07cd0 --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.json @@ -0,0 +1,104 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-10-28 06:34:02.606386", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "activity", + "mandatory", + "type", + "time_offset", + "description", + "column_break_5", + "task_duration", + "task_doctype", + "role" + ], + "fields": [ + { + "fieldname": "activity", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Activity", + "options": "Healthcare Activity" + }, + { + "columns": 3, + "fetch_from": "activity.description", + "fetch_if_empty": 1, + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "columns": 1, + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "\nPre-Op\nPost-Op" + }, + { + "fetch_from": "activity.role", + "fetch_if_empty": 1, + "fieldname": "role", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Role", + "options": "Role" + }, + { + "columns": 1, + "default": "0", + "fieldname": "mandatory", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Mandatory" + }, + { + "description": "System will validate if a document of this type is created and linked before completing the Nursing Task - for example, Vital Signs if the task is supposed to capture the vitals of the Patient", + "fetch_from": "activity.task_doctype", + "fetch_if_empty": 1, + "fieldname": "task_doctype", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Task Doctype", + "options": "DocType" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "description": "Default duration which the task can take to complete", + "fetch_from": "activity.activity_duration", + "fetch_if_empty": 1, + "fieldname": "task_duration", + "fieldtype": "Duration", + "in_list_view": 1, + "label": "Task Duration" + }, + { + "description": "The time duration before or after the event on which the Nursing Task is to be performed. The Nursing Task start time will be set based on this.", + "fieldname": "time_offset", + "fieldtype": "Duration", + "in_list_view": 1, + "label": "Time Offset" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2022-07-25 19:33:44.109535", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Nursing Checklist Template Task", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.py b/healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.py new file mode 100644 index 0000000..dc95ea3 --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_checklist_template_task/nursing_checklist_template_task.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, healthcare and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class NursingChecklistTemplateTask(Document): + pass diff --git a/healthcare/healthcare/doctype/nursing_task/__init__.py b/healthcare/healthcare/doctype/nursing_task/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/nursing_task/nursing_task.js b/healthcare/healthcare/doctype/nursing_task/nursing_task.js new file mode 100644 index 0000000..89fb14d --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_task/nursing_task.js @@ -0,0 +1,107 @@ +// Copyright (c) 2021, healthcare and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Nursing Task', { + + onload: function(frm) { + + const task_document_field = frm.get_docfield("task_document_name"); + task_document_field.get_route_options_for_new_doc = () => { + if (frm.is_new()) return; + return { + 'company': frm.doc.company, + 'medical_department': frm.doc.medical_department, + 'service_unit': frm.doc.service_unit, + 'patient': frm.doc.patient, + }; + }; + + if (frm.doc.task_doctype) { + // set description + frm.set_df_property('task_document_name', 'description', + `Create and link a new ${frm.doc.task_doctype} document to complete this task`); + + // set filters + let filters = {}; + frappe.model.with_doctype(frm.doc.task_doctype, function() { + if (frappe.meta.has_field(frm.doc.task_doctype, 'patient')) { + filters['patient'] = frm.doc.patient; + } + + frm.set_query('task_document_name', () => { + return { + filters: filters + } + }); + }); + } + }, + + refresh: function(frm) { + + // TODO: handle routing back to nursing task form + // frm.trigger('show_form_route_button'); + + if (frm.is_new()) { + frm.set_value('status', 'Draft'); + } + + let status_list = ['Accepted', 'Received', 'Rejected', + 'Ready', 'Failed', 'Entered in Error', 'On Hold']; + if (status_list.includes(frm.doc.status) || frm.doc.status == 'Requested') { + + // set primary action to start + frm.page.set_primary_action(__('Start'), () => { + frm.events.update_status(frm, 'In Progress'); + }); + + // optionally allow updating other statuses + status_list.filter(status => status != frm.doc.status).forEach(status => { + + frm.add_custom_button(__(`${status}`), () => { + frm.events.update_status(frm, `${status}`); + }, __('Update Status')); + + }); + } + + if (frm.doc.status == 'In Progress') { + frm.page.set_primary_action(__('Complete'), () => { + if (frm.doc.task_doctype){ + frm.set_df_property('task_document_name', 'reqd', 1); + } + frm.events.update_status(frm, 'Completed'); + }); + + frm.add_custom_button(__('Hold'), () => { + frm.set_df_property('task_document_name', 'reqd', 0); + frm.events.update_status(frm, 'On Hold'); + }); + } + }, + + update_status(frm, status) { + frm.set_value('status', status); + frm.save('Update'); + frm.reload_doc(); + }, + + show_form_route_button: function(frm) { + + // add custom button to route to new Task DocType form + if (!frm.is_new() && !frm.is_dirty() && frm.doc.task_doctype) { + + frm.add_custom_button(__(`New ${frm.doc.task_doctype}`), () => { + frappe.route_options = { + 'patient': frm.doc.patient, + 'medical_department': frm.doc.medical_department, + 'company': frm.doc.company, + 'service_unit': frm.doc.service_unit, + }; + + frappe.new_doc(frm.doc.task_doctype); + }); + + } + } +}); diff --git a/healthcare/healthcare/doctype/nursing_task/nursing_task.json b/healthcare/healthcare/doctype/nursing_task/nursing_task.json new file mode 100644 index 0000000..6334033 --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_task/nursing_task.json @@ -0,0 +1,384 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2022-01-18 15:18:15.657622", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "title", + "date", + "user", + "column_break_5", + "company", + "service_unit", + "medical_department", + "status", + "task_details", + "activity", + "mandatory", + "column_break_2", + "description", + "patient_details_section", + "patient", + "patient_name", + "age", + "gender", + "column_break_6", + "inpatient_record", + "inpatient_status", + "task_schedule_section", + "requested_start_time", + "requested_end_time", + "duration", + "column_break_18", + "task_start_time", + "task_end_time", + "task_duration", + "source_section", + "reference_doctype", + "amended_from", + "column_break_8", + "reference_name", + "section_break_28", + "task_doctype", + "task_document_name", + "notes" + ], + "fields": [ + { + "fieldname": "activity", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Activity", + "options": "Healthcare Activity", + "read_only_depends_on": "eval:doc.reference_name", + "reqd": 1 + }, + { + "fetch_from": "activity.description", + "fetch_if_empty": 1, + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "read_only": 1 + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "label": "Reference Doctype", + "no_copy": 1, + "options": "DocType", + "read_only": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Name", + "no_copy": 1, + "options": "reference_doctype", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Draft\nRequested\nReceived\nAccepted\nRejected\nReady\nIn Progress\nOn Hold\nCompleted\nFailed\nCancelled\nEntered in Error", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Nursing Task", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "task_details", + "fieldtype": "Section Break", + "label": "Task Details" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "HLC-NUR-.YYYY.-" + }, + { + "default": "0", + "fieldname": "mandatory", + "fieldtype": "Check", + "label": "Mandatory", + "read_only_depends_on": "eval:doc.reference_name" + }, + { + "fetch_from": "activity.task_doctype", + "fetch_if_empty": 1, + "fieldname": "task_doctype", + "fieldtype": "Link", + "label": "Task Doctype", + "options": "DocType" + }, + { + "allow_on_submit": 1, + "description": "Create and link the document to complete this task", + "fieldname": "task_document_name", + "fieldtype": "Dynamic Link", + "label": "Task Document Name", + "no_copy": 1, + "options": "task_doctype", + "read_only": 1, + "read_only_depends_on": "eval:in_list([\"Completed\", \"Failed\", \"Cancelled\"], doc.status)" + }, + { + "fieldname": "patient_details_section", + "fieldtype": "Section Break", + "label": "Patient Details" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fieldname": "age", + "fieldtype": "Data", + "label": "Age", + "read_only": 1 + }, + { + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "fetch_from": "patient.inpatient_status", + "fieldname": "inpatient_status", + "fieldtype": "Data", + "label": "Inpatient Status", + "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only_depends_on": "eval:doc.reference_name", + "reqd": 1 + }, + { + "fieldname": "service_unit", + "fieldtype": "Link", + "label": "Service Unit", + "options": "Healthcare Service Unit", + "read_only_depends_on": "eval:doc.reference_name" + }, + { + "default": "Today", + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Date", + "reqd": 1 + }, + { + "fieldname": "section_break_28", + "fieldtype": "Section Break", + "label": "Task Resolution" + }, + { + "allow_on_submit": 1, + "fieldname": "notes", + "fieldtype": "Text Editor", + "label": "Notes", + "no_copy": 1, + "read_only_depends_on": "eval:in_list([\"Completed\", \"Failed\", \"Cancelled\"], doc.status)" + }, + { + "bold": 1, + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "read_only_depends_on": "eval:doc.reference_name", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "requested_start_time", + "fieldtype": "Datetime", + "label": "Requested Start Time", + "read_only_depends_on": "eval:doc.reference_name" + }, + { + "fieldname": "requested_end_time", + "fieldtype": "Datetime", + "label": "Requested End Time", + "read_only_depends_on": "eval:doc.reference_name" + }, + { + "fetch_from": "activity.activity_duration", + "fetch_if_empty": 1, + "fieldname": "duration", + "fieldtype": "Duration", + "label": "Duration", + "read_only_depends_on": "eval:doc.reference_name" + }, + { + "fieldname": "task_schedule_section", + "fieldtype": "Section Break", + "label": "Task Schedule" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "task_start_time", + "fieldtype": "Datetime", + "label": "Task Start Time", + "read_only_depends_on": "eval:in_list([\"Completed\", \"Failed\", \"Cancelled\"], doc.status)" + }, + { + "allow_on_submit": 1, + "fieldname": "task_end_time", + "fieldtype": "Datetime", + "label": "Task End Time", + "read_only_depends_on": "eval:in_list([\"Completed\", \"Failed\", \"Cancelled\"], doc.status)" + }, + { + "allow_on_submit": 1, + "fieldname": "task_duration", + "fieldtype": "Duration", + "label": "Task Duration", + "read_only": 1 + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "label": "Medical Department", + "options": "Medical Department", + "read_only_depends_on": "eval:doc.reference_name" + }, + { + "fieldname": "source_section", + "fieldtype": "Section Break", + "label": "Source" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-01-19 15:34:31.760433", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Nursing Task", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "search_fields": "patient, activity, user", + "sort_field": "requested_start_time", + "sort_order": "DESC", + "states": [], + "title_field": "title", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/nursing_task/nursing_task.py b/healthcare/healthcare/doctype/nursing_task/nursing_task.py new file mode 100644 index 0000000..b244e8f --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_task/nursing_task.py @@ -0,0 +1,141 @@ +# Copyright (c) 2021, healthcare and contributors +# For license information, please see license.txt +import json +from six import string_types + +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.utils import now_datetime, add_to_date, getdate, time_diff_in_seconds, get_datetime +from erpnext import get_default_company + + +class NursingTask(Document): + def before_insert(self): + # set requested start / end + self.set_task_schedule() + + self.title = "{} - {}".format(_(self.patient), _(self.activity)) + + self.age = frappe.get_doc("Patient", self.patient).get_age() + + def validate(self): + if self.status == "Requested": + # auto submit if status is Requested + self.docstatus = 1 + + def on_submit(self): + self.db_set("status", "Requested") + + def on_cancel(self): + if self.status == "Completed": + frappe.throw(_("Not Allowed to cancel Nursing Task with status 'Completed'")) + + self.db_set("status", "Cancelled") + + def on_update_after_submit(self): + if self.status == "Completed" and self.task_doctype and not self.task_document_name: + frappe.throw(_("Not Allowed to 'Complete' Nursing Task without linking Task Document")) + + if self.status == "Draft": + frappe.throw(_("Nursing Task cannot be 'Draft' after submission")) + + if self.status == "In Progress": + if not self.task_start_time: + self.db_set("task_start_time", now_datetime()) + + elif self.status == "Completed": + self.db_set( + { + "task_end_time": now_datetime(), + "task_duration": time_diff_in_seconds(self.task_end_time, self.task_start_time), + } + ) + + self.notify_update() + + def set_task_schedule(self): + if not self.requested_start_time or (get_datetime(self.requested_start_time) < now_datetime()): + self.requested_start_time = now_datetime() + + if not self.requested_end_time: + if not self.duration: + self.duration = frappe.db.get_value("Healthcare Activity", self.activity, "duration") + self.requested_end_time = add_to_date(self.requested_start_time, seconds=self.duration) + + # set date based on requested_start_time + self.date = getdate(self.requested_start_time) + + @classmethod + def create_nursing_tasks_from_template( + cls, template, doc, start_time=now_datetime(), post_event=True + ): + tasks = frappe.get_all( + "Nursing Checklist Template Task", + filters={"parent": template}, + fields=["*"], + ) + NursingTask.create_nursing_tasks(tasks, doc, start_time, post_event) + + @classmethod + def create_nursing_tasks(cls, tasks, doc, start_time, post_event=True): + for task in tasks: + medical_department = ( + doc.get("department") if doc.get("department") else doc.get("medical_department") + ) + if doc.get("doctype") == "Inpatient Record": + service_unit = ( + frappe.db.get_value("Inpatient Occupancy", {"parent": doc.name, "left": 0}, "service_unit"), + ) + else: + service_unit = ( + doc.get("service_unit") if doc.get("service_unit") else doc.get("healthcare_service_unit") + ) + + options = { + "doctype": "Nursing Task", + "status": "Requested", + "company": doc.get("company", get_default_company()), + "service_unit": service_unit, + "medical_department": medical_department, + "reference_doctype": doc.get("doctype"), + "reference_name": doc.get("name"), + "patient": doc.get("patient"), + "activity": task.activity, + "mandatory": task.mandatory, + "duration": task.task_duration, + "task_doctype": task.task_doctype, + } + + if task.time_offset: + time_offset = task.time_offset if not post_event else 0 - task.time_offset + requested_start_time = add_to_date(start_time, seconds=time_offset) + else: + requested_start_time = start_time + + options.update({"requested_start_time": requested_start_time}) + + options = {key: value for (key, value) in options.items() if value} + frappe.get_doc(options).insert() + + @classmethod + def cancel_nursing_tasks(cls, dt, dn): + tasks = frappe.db.get_all( + "Nursing Task", + filters={ + "reference_doctype": dt, + "reference_document": dn, + "status": ["!=", "Completed", "Cancelled"], + }, + ) + + for task in tasks: + frappe.get_doc("Nursing Task", task).cancel() + + +@frappe.whitelist() +def create_nursing_tasks_from_template(template, doc, start_time=now_datetime(), post_event=True): + if isinstance(doc, string_types): + doc = json.loads(doc) + + NursingTask.create_nursing_tasks_from_template(template, doc, start_time, post_event) diff --git a/healthcare/healthcare/doctype/nursing_task/nursing_task_list.js b/healthcare/healthcare/doctype/nursing_task/nursing_task_list.js new file mode 100644 index 0000000..219603d --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_task/nursing_task_list.js @@ -0,0 +1,37 @@ +// Copyright (c) 2021, healthcare and contributors +// For license information, please see license.txt + +frappe.listview_settings['Nursing Task'] = { + filters: [['status', 'not in', ['Failed', 'Cancelled', 'Completed']]], + hide_name_column: true, + has_indicator_for_draft: true, + get_indicator: function (doc) { + if (doc.status === 'Draft') { + return [__('Draft'), 'orange', 'status, =, Draft']; + + } else if (doc.status === 'Requested') { + return [__('Requested'), 'orange', 'status, =, Requested']; + + } else if (doc.status === 'Rejected') { + return [__('Rejected'), 'red', 'status, =, Rejected']; + + } else if (doc.status === 'Entered in Error') { + return [__('Entered in Error'), 'red', 'status, =, Entered In Error']; + + } else if (doc.status === 'Completed') { + return [__('Completed'), 'green', 'status, =, Completed']; + + } else if (doc.status === 'Cancelled') { + return [__('Cancelled'), 'grey', 'status, =, Cancelled']; + + } else if (doc.status === 'Failed') { + return [__('Failed'), 'red', 'status, =, Failed']; + + } else if (doc.status === 'In Progress') { + return [__('In Progress'), 'blue', 'status, =, In Progress']; + + } else { // Received, On Hold, Accepted, Ready + return [__(doc.status), 'light-blue', `status, =, ${doc.status}`]; + } + } +}; \ No newline at end of file diff --git a/healthcare/healthcare/doctype/nursing_task/test_nursing_task.py b/healthcare/healthcare/doctype/nursing_task/test_nursing_task.py new file mode 100644 index 0000000..03e92b2 --- /dev/null +++ b/healthcare/healthcare/doctype/nursing_task/test_nursing_task.py @@ -0,0 +1,134 @@ +# Copyright (c) 2021, healthcare and Contributors +# See license.txt + +import frappe + +from frappe.tests.utils import FrappeTestCase +from frappe.utils import now_datetime +from healthcare.healthcare.doctype.clinical_procedure.test_clinical_procedure import ( + create_procedure, +) +from healthcare.healthcare.doctype.inpatient_record.inpatient_record import ( + admit_patient, + discharge_patient, +) +from healthcare.healthcare.doctype.inpatient_record.test_inpatient_record import ( + create_inpatient, + get_healthcare_service_unit, +) +from healthcare.healthcare.doctype.lab_test.test_lab_test import ( + create_lab_test_template, + create_lab_test, +) +from healthcare.healthcare.doctype.nursing_task.nursing_task import NursingTask +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_clinical_procedure_template, + create_healthcare_docs, +) +from healthcare.healthcare.doctype.therapy_plan.test_therapy_plan import create_therapy_plan +from healthcare.healthcare.doctype.therapy_session.test_therapy_session import ( + create_therapy_session, +) +from healthcare.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type + + +class TestNursingTask(FrappeTestCase): + def setUp(self) -> None: + nursing_checklist_templates = frappe.get_test_records("Nursing Checklist Template") + self.nc_template = frappe.get_doc(nursing_checklist_templates[0]).insert( + ignore_if_duplicate=True + ) + + self.settings = frappe.get_single("Healthcare Settings") + self.settings.validate_nursing_checklists = 1 + self.settings.save() + + self.patient, self.practitioner = create_healthcare_docs() + + def test_lab_test_submission_should_validate_pending_nursing_tasks(self): + self.lt_template = create_lab_test_template() + self.lt_template.nursing_checklist_template = self.nc_template.name + self.lt_template.save() + + lab_test = create_lab_test(self.lt_template) + lab_test.descriptive_test_items[0].result_value = 12 + lab_test.descriptive_test_items[1].result_value = 1 + lab_test.descriptive_test_items[2].result_value = 2.3 + lab_test.save() + + self.assertRaises(frappe.ValidationError, lab_test.submit) + + complete_nusing_tasks(lab_test) + lab_test.submit() + + def test_start_clinical_procedure_should_validate_pending_nursing_tasks(self): + procedure_template = create_clinical_procedure_template() + procedure_template.allow_stock_consumption = 1 + procedure_template.pre_op_nursing_checklist_template = self.nc_template.name + procedure_template.save() + + procedure = create_procedure(procedure_template, self.patient, self.practitioner) + self.assertRaises(frappe.ValidationError, procedure.start_procedure) + + complete_nusing_tasks(procedure) + procedure.start_procedure() + + def test_admit_inpatient_should_validate_pending_nursing_tasks(self): + self.settings.allow_discharge_despite_unbilled_services = 1 + self.settings.save() + + ip_record = create_inpatient(self.patient) + ip_record.admission_nursing_checklist_template = self.nc_template.name + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions=True) + NursingTask.create_nursing_tasks_from_template( + ip_record.admission_nursing_checklist_template, ip_record, start_time=now_datetime() + ) + + service_unit = get_healthcare_service_unit() + kwargs = { + "inpatient_record": ip_record, + "service_unit": service_unit, + "check_in": now_datetime(), + } + self.assertRaises(frappe.ValidationError, admit_patient, **kwargs) + + complete_nusing_tasks(ip_record) + admit_patient(**kwargs) + + ip_record.discharge_nursing_checklist_template = self.nc_template.name + ip_record.save() + NursingTask.create_nursing_tasks_from_template( + ip_record.admission_nursing_checklist_template, ip_record, start_time=now_datetime() + ) + + self.assertRaises(frappe.ValidationError, discharge_patient, inpatient_record=ip_record) + + complete_nusing_tasks(ip_record) + discharge_patient(ip_record) + + def test_submit_therapy_session_should_validate_pending_nursing_tasks(self): + therapy_type = create_therapy_type() + therapy_type.nursing_checklist_template = self.nc_template.name + therapy_type.save() + + therapy_plan = create_therapy_plan() + therapy_session = create_therapy_session(self.patient, therapy_type.name, therapy_plan.name) + + self.assertRaises(frappe.ValidationError, therapy_session.submit) + + complete_nusing_tasks(therapy_session) + therapy_session.submit() + + +def complete_nusing_tasks(document): + filters = { + "reference_name": document.name, + "mandatory": 1, + "status": ["not in", ["Completed", "Cancelled"]], + } + tasks = frappe.get_all("Nursing Task", filters=filters) + for task_name in tasks: + task = frappe.get_doc("Nursing Task", task_name) + task.status = "Completed" + task.save() diff --git a/healthcare/healthcare/doctype/organism/__init__.py b/healthcare/healthcare/doctype/organism/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/organism/organism.js b/healthcare/healthcare/doctype/organism/organism.js new file mode 100644 index 0000000..e839d8f --- /dev/null +++ b/healthcare/healthcare/doctype/organism/organism.js @@ -0,0 +1,5 @@ +// Copyright (c) 2019, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Organism', { +}); diff --git a/healthcare/healthcare/doctype/organism/organism.json b/healthcare/healthcare/doctype/organism/organism.json new file mode 100644 index 0000000..88a7686 --- /dev/null +++ b/healthcare/healthcare/doctype/organism/organism.json @@ -0,0 +1,152 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:organism", + "beta": 1, + "creation": "2019-09-06 16:29:07.797960", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "organism", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Organism", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "abbr", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Abbr", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 1 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-10-04 19:45:33.353753", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Organism", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "organism, abbr", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "organism", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/organism/organism.py b/healthcare/healthcare/doctype/organism/organism.py new file mode 100644 index 0000000..8cfeb1e --- /dev/null +++ b/healthcare/healthcare/doctype/organism/organism.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class Organism(Document): + pass diff --git a/healthcare/healthcare/doctype/organism/test_organism.py b/healthcare/healthcare/doctype/organism/test_organism.py new file mode 100644 index 0000000..3a77c91 --- /dev/null +++ b/healthcare/healthcare/doctype/organism/test_organism.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestOrganism(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/organism_test_item/__init__.py b/healthcare/healthcare/doctype/organism_test_item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/organism_test_item/organism_test_item.json b/healthcare/healthcare/doctype/organism_test_item/organism_test_item.json new file mode 100644 index 0000000..56d0a4d --- /dev/null +++ b/healthcare/healthcare/doctype/organism_test_item/organism_test_item.json @@ -0,0 +1,144 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2019-09-06 16:37:59.698996", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "organism", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Organism", + "length": 0, + "no_copy": 0, + "options": "Organism", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_population", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony Population", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony UOM", + "length": 0, + "no_copy": 0, + "options": "Lab Test UOM", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2019-10-04 19:48:04.104234", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Organism Test Item", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/organism_test_item/organism_test_item.py b/healthcare/healthcare/doctype/organism_test_item/organism_test_item.py new file mode 100644 index 0000000..39dab76 --- /dev/null +++ b/healthcare/healthcare/doctype/organism_test_item/organism_test_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class OrganismTestItem(Document): + pass diff --git a/healthcare/healthcare/doctype/organism_test_result/__init__.py b/healthcare/healthcare/doctype/organism_test_result/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/organism_test_result/organism_test_result.json b/healthcare/healthcare/doctype/organism_test_result/organism_test_result.json new file mode 100644 index 0000000..8b238de --- /dev/null +++ b/healthcare/healthcare/doctype/organism_test_result/organism_test_result.json @@ -0,0 +1,144 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2019-09-06 16:37:59.698996", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "organism", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Organism", + "length": 0, + "no_copy": 0, + "options": "Organism", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_population", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony Population", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "colony_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Colony UOM", + "length": 0, + "no_copy": 0, + "options": "Lab Test UOM", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2019-10-04 19:48:04.104234", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Organism Test Result", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/organism_test_result/organism_test_result.py b/healthcare/healthcare/doctype/organism_test_result/organism_test_result.py new file mode 100644 index 0000000..b69b231 --- /dev/null +++ b/healthcare/healthcare/doctype/organism_test_result/organism_test_result.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class OrganismTestResult(Document): + pass diff --git a/healthcare/healthcare/doctype/patient/__init__.py b/healthcare/healthcare/doctype/patient/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient/patient.js b/healthcare/healthcare/doctype/patient/patient.js new file mode 100644 index 0000000..9bef27b --- /dev/null +++ b/healthcare/healthcare/doctype/patient/patient.js @@ -0,0 +1,146 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Patient', { + refresh: function (frm) { + frm.set_query('patient', 'patient_relation', function () { + return { + filters: [ + ['Patient', 'name', '!=', frm.doc.name] + ] + }; + }); + frm.set_query('customer_group', {'is_group': 0}); + frm.set_query('default_price_list', { 'selling': 1}); + + if (frappe.defaults.get_default('patient_name_by') != 'Naming Series') { + frm.toggle_display('naming_series', false); + } else { + erpnext.toggle_naming_series(); + } + + if (frappe.defaults.get_default('collect_registration_fee') && frm.doc.status == 'Disabled') { + frm.add_custom_button(__('Invoice Patient Registration'), function () { + invoice_registration(frm); + }); + } + + if (frm.doc.patient_name && frappe.user.has_role('Physician')) { + frm.add_custom_button(__('Patient Progress'), function() { + frappe.route_options = {'patient': frm.doc.name}; + frappe.set_route('patient-progress'); + }, __('View')); + + frm.add_custom_button(__('Patient History'), function() { + frappe.route_options = {'patient': frm.doc.name}; + frappe.set_route('patient_history'); + }, __('View')); + } + + frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Patient'}; + frm.toggle_display(['address_html', 'contact_html'], !frm.is_new()); + + if (!frm.is_new()) { + if ((frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) { + frm.add_custom_button(__('Medical Record'), function () { + create_medical_record(frm); + }, __('Create')); + frm.toggle_enable(['customer'], 0); + } + frappe.contacts.render_address_and_contact(frm); + erpnext.utils.set_party_dashboard_indicators(frm); + } else { + frappe.contacts.clear_address_and_contact(frm); + } + }, + + onload: function (frm) { + if (frm.doc.dob) { + $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`); + } else { + $(frm.fields_dict['age_html'].wrapper).html(''); + } + } +}); + +frappe.ui.form.on('Patient', 'dob', function(frm) { + if (frm.doc.dob) { + let today = new Date(); + let birthDate = new Date(frm.doc.dob); + if (today < birthDate) { + frappe.msgprint(__('Please select a valid Date')); + frappe.model.set_value(frm.doctype,frm.docname, 'dob', ''); + } else { + let age_str = get_age(frm.doc.dob); + $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`); + } + } else { + $(frm.fields_dict['age_html'].wrapper).html(''); + } +}); + +frappe.ui.form.on('Patient Relation', { + patient_relation_add: function(frm){ + frm.fields_dict['patient_relation'].grid.get_field('patient').get_query = function(doc){ + let patient_list = []; + if(!doc.__islocal) patient_list.push(doc.name); + $.each(doc.patient_relation, function(idx, val){ + if (val.patient) patient_list.push(val.patient); + }); + return { filters: [['Patient', 'name', 'not in', patient_list]] }; + }; + } +}); + +let create_medical_record = function (frm) { + frappe.route_options = { + 'patient': frm.doc.name, + 'status': 'Open', + 'reference_doctype': 'Patient Medical Record', + 'reference_owner': frm.doc.owner + }; + frappe.new_doc('Patient Medical Record'); +}; + +let get_age = function (birth) { + let ageMS = Date.parse(Date()) - Date.parse(birth); + let age = new Date(); + age.setTime(ageMS); + let years = age.getFullYear() - 1970; + return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; +}; + +let create_vital_signs = function (frm) { + if (!frm.doc.name) { + frappe.throw(__('Please save the patient first')); + } + frappe.route_options = { + 'patient': frm.doc.name, + }; + frappe.new_doc('Vital Signs'); +}; + +let create_encounter = function (frm) { + if (!frm.doc.name) { + frappe.throw(__('Please save the patient first')); + } + frappe.route_options = { + 'patient': frm.doc.name, + }; + frappe.new_doc('Patient Encounter'); +}; + +let invoice_registration = function (frm) { + frappe.call({ + doc: frm.doc, + method: 'invoice_patient_registration', + callback: function(data) { + if (!data.exc) { + if (data.message.invoice) { + frappe.set_route('Form', 'Sales Invoice', data.message.invoice); + } + cur_frm.reload_doc(); + } + } + }); +}; diff --git a/healthcare/healthcare/doctype/patient/patient.json b/healthcare/healthcare/doctype/patient/patient.json new file mode 100644 index 0000000..4092a6a --- /dev/null +++ b/healthcare/healthcare/doctype/patient/patient.json @@ -0,0 +1,542 @@ +{ + "actions": [], + "allow_events_in_timeline": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2017-01-23 14:03:49.084370", + "description": "Patient", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "basic_info", + "naming_series", + "first_name", + "middle_name", + "last_name", + "patient_name", + "sex", + "blood_group", + "dob", + "age_html", + "image", + "column_break_14", + "status", + "uid", + "inpatient_record", + "inpatient_status", + "report_preference", + "mobile", + "phone", + "email", + "invite_user", + "user_id", + "address_contacts", + "address_html", + "column_break_22", + "contact_html", + "customer_details_section", + "customer", + "customer_group", + "territory", + "column_break_24", + "default_currency", + "default_price_list", + "language", + "personal_and_social_history", + "occupation", + "column_break_25", + "marital_status", + "sb_relation", + "patient_relation", + "allergy_medical_and_surgical_history", + "allergies", + "medication", + "column_break_20", + "medical_history", + "surgical_history", + "risk_factors", + "tobacco_past_use", + "tobacco_current_use", + "alcohol_past_use", + "alcohol_current_use", + "column_break_32", + "surrounding_factors", + "other_risk_factors", + "more_info", + "patient_details" + ], + "fields": [ + { + "fieldname": "basic_info", + "fieldtype": "Section Break", + "label": "Patient Demographics", + "oldfieldtype": "Section Break", + "options": "fa fa-user" + }, + { + "fieldname": "inpatient_status", + "fieldtype": "Select", + "in_preview": 1, + "label": "Inpatient Status", + "no_copy": 1, + "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled", + "read_only": 1 + }, + { + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "no_copy": 1, + "options": "Inpatient Record", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-PAT-.YYYY.-", + "print_hide": 1, + "report_hide": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "patient_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Full Name", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "sex", + "fieldtype": "Link", + "in_preview": 1, + "label": "Gender", + "options": "Gender", + "reqd": 1 + }, + { + "bold": 1, + "fieldname": "blood_group", + "fieldtype": "Select", + "in_preview": 1, + "label": "Blood Group", + "no_copy": 1, + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative" + }, + { + "bold": 1, + "fieldname": "dob", + "fieldtype": "Date", + "in_preview": 1, + "label": "Date of birth", + "no_copy": 1 + }, + { + "fieldname": "age_html", + "fieldtype": "HTML", + "label": "Age", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_filter": 1, + "in_list_view": 1, + "label": "Status", + "no_copy": 1, + "options": "Active\nDisabled", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "in_preview": 1, + "label": "Image", + "no_copy": 1, + "print_hide": 1, + "width": "50%" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "description": "If \"Link Customer to Patient\" is checked in Healthcare Settings and an existing Customer is not selected then, a Customer will be created for this Patient for recording transactions in Accounts module.", + "fieldname": "customer", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Customer", + "no_copy": 1, + "options": "Customer", + "set_only_once": 1 + }, + { + "fieldname": "report_preference", + "fieldtype": "Select", + "label": "Report Preference", + "options": "\nEmail\nPrint" + }, + { + "bold": 1, + "fieldname": "mobile", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Mobile", + "no_copy": 1, + "options": "Phone" + }, + { + "bold": 1, + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Email", + "no_copy": 1, + "options": "Email" + }, + { + "fieldname": "phone", + "fieldtype": "Data", + "in_filter": 1, + "label": "Phone", + "no_copy": 1, + "options": "Phone" + }, + { + "collapsible": 1, + "fieldname": "sb_relation", + "fieldtype": "Section Break", + "label": "Patient Relation" + }, + { + "fieldname": "patient_relation", + "fieldtype": "Table", + "label": "Patient Relation", + "options": "Patient Relation" + }, + { + "collapsible": 1, + "fieldname": "allergy_medical_and_surgical_history", + "fieldtype": "Section Break", + "label": "Allergies, Medical and Surgical History" + }, + { + "fieldname": "allergies", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Allergies", + "no_copy": 1 + }, + { + "fieldname": "medication", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Medication", + "no_copy": 1 + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, + { + "fieldname": "medical_history", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Medical History", + "no_copy": 1 + }, + { + "fieldname": "surgical_history", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Surgical History", + "no_copy": 1 + }, + { + "collapsible": 1, + "fieldname": "personal_and_social_history", + "fieldtype": "Section Break", + "label": "Personal and Social History" + }, + { + "fieldname": "occupation", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "label": "Occupation", + "no_copy": 1 + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "fieldname": "marital_status", + "fieldtype": "Select", + "label": "Marital Status", + "no_copy": 1, + "options": "\nSingle\nMarried\nDivorced\nWidow" + }, + { + "collapsible": 1, + "fieldname": "risk_factors", + "fieldtype": "Section Break", + "label": "Risk Factors" + }, + { + "fieldname": "tobacco_past_use", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "label": "Tobacco Consumption (Past)", + "no_copy": 1 + }, + { + "fieldname": "tobacco_current_use", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "label": "Tobacco Consumption (Present)", + "no_copy": 1 + }, + { + "fieldname": "alcohol_past_use", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "label": "Alcohol Consumption (Past)", + "no_copy": 1 + }, + { + "fieldname": "alcohol_current_use", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Alcohol Consumption (Present)", + "no_copy": 1 + }, + { + "fieldname": "column_break_32", + "fieldtype": "Column Break" + }, + { + "fieldname": "surrounding_factors", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Occupational Hazards and Environmental Factors", + "no_copy": 1 + }, + { + "fieldname": "other_risk_factors", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Other Risk Factors", + "no_copy": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "patient_details", + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text" + }, + { + "description": "Additional information regarding the patient", + "fieldname": "patient_details", + "fieldtype": "Text", + "ignore_xss_filter": 1, + "label": "Patient Details", + "no_copy": 1 + }, + { + "fieldname": "default_currency", + "fieldtype": "Link", + "label": "Billing Currency", + "options": "Currency" + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name", + "no_copy": 1 + }, + { + "fieldname": "first_name", + "fieldtype": "Data", + "label": "First Name", + "no_copy": 1, + "oldfieldtype": "Data", + "reqd": 1 + }, + { + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name (optional)", + "no_copy": 1 + }, + { + "collapsible": 1, + "fieldname": "customer_details_section", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_price_list", + "fieldtype": "Link", + "label": "Default Price List", + "options": "Price List" + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "address_contacts", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-map-marker" + }, + { + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML", + "no_copy": 1, + "read_only": 1 + }, + { + "allow_in_quick_entry": 1, + "default": "1", + "fieldname": "invite_user", + "fieldtype": "Check", + "label": "Invite as User", + "no_copy": 1, + "read_only_depends_on": "eval: doc.user_id" + }, + { + "fieldname": "user_id", + "fieldtype": "Read Only", + "label": "User ID", + "no_copy": 1, + "options": "User" + }, + { + "allow_in_quick_entry": 1, + "bold": 1, + "fieldname": "uid", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Identification Number (UID)", + "unique": 1 + } + ], + "icon": "fa fa-user", + "image_field": "image", + "links": [], + "max_attachments": 50, + "modified": "2021-03-14 13:21:09.759906", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient", + "name_case": "Title Case", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "search_fields": "patient_name,mobile,email,phone,uid", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "ASC", + "title_field": "patient_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient/patient.py b/healthcare/healthcare/doctype/patient/patient.py new file mode 100644 index 0000000..0c55687 --- /dev/null +++ b/healthcare/healthcare/doctype/patient/patient.py @@ -0,0 +1,340 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import dateutil +import frappe +from frappe import _ +from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.contacts.doctype.contact.contact import get_default_contact +from frappe.model.document import Document +from frappe.model.naming import set_name_by_naming_series +from frappe.utils import cint, cstr, getdate +from frappe.utils.nestedset import get_root_of + +from erpnext import get_default_currency +from erpnext.accounts.party import get_dashboard_info +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import ( + get_income_account, + get_receivable_account, + send_registration_sms, +) + + +class Patient(Document): + def onload(self): + """Load address and contacts in `__onload`""" + load_address_and_contact(self) + self.load_dashboard_info() + + def validate(self): + self.set_full_name() + + def before_insert(self): + self.set_missing_customer_details() + + def after_insert(self): + if frappe.db.get_single_value("Healthcare Settings", "collect_registration_fee"): + frappe.db.set_value("Patient", self.name, "status", "Disabled") + else: + send_registration_sms(self) + self.reload() + + def on_update(self): + if frappe.db.get_single_value("Healthcare Settings", "link_customer_to_patient"): + if self.customer: + customer = frappe.get_doc("Customer", self.customer) + if self.customer_group: + customer.customer_group = self.customer_group + if self.territory: + customer.territory = self.territory + customer.customer_name = self.patient_name + customer.default_price_list = self.default_price_list + customer.default_currency = self.default_currency + customer.language = self.language + customer.image = self.image + customer.ignore_mandatory = True + customer.save(ignore_permissions=True) + else: + create_customer(self) + + self.set_contact() # add or update contact + + if not self.user_id and self.email and self.invite_user: + self.create_website_user() + + def load_dashboard_info(self): + if self.customer: + info = get_dashboard_info("Customer", self.customer, None) + self.set_onload("dashboard_info", info) + + def set_full_name(self): + if self.last_name: + self.patient_name = " ".join(filter(None, [self.first_name, self.last_name])) + else: + self.patient_name = self.first_name + + def set_missing_customer_details(self): + if not self.customer_group: + self.customer_group = frappe.db.get_single_value( + "Selling Settings", "customer_group" + ) or get_root_of("Customer Group") + if not self.territory: + self.territory = frappe.db.get_single_value("Selling Settings", "territory") or get_root_of( + "Territory" + ) + if not self.default_price_list: + self.default_price_list = frappe.db.get_single_value("Selling Settings", "selling_price_list") + + if not self.customer_group or not self.territory or not self.default_price_list: + frappe.msgprint( + _( + "Please set defaults for Customer Group, Territory and Selling Price List in Selling Settings" + ), + alert=True, + ) + + if not self.default_currency: + self.default_currency = get_default_currency() + if not self.language: + self.language = frappe.db.get_single_value("System Settings", "language") + + def create_website_user(self): + users = frappe.db.get_all( + "User", + fields=["email", "mobile_no"], + or_filters={"email": self.email, "mobile_no": self.mobile}, + ) + if users and users[0]: + frappe.throw( + _( + "User exists with Email {}, Mobile {}
Please check email / mobile or disable 'Invite as User' to skip creating User" + ).format(frappe.bold(users[0].email), frappe.bold(users[0].mobile_no)), + frappe.DuplicateEntryError, + ) + + user = frappe.get_doc( + { + "doctype": "User", + "first_name": self.first_name, + "last_name": self.last_name, + "email": self.email, + "user_type": "Website User", + "gender": self.sex, + "phone": self.phone, + "mobile_no": self.mobile, + "birth_date": self.dob, + } + ) + user.flags.ignore_permissions = True + user.enabled = True + user.send_welcome_email = True + user.add_roles("Patient") + self.db_set("user_id", user.name) + + def autoname(self): + patient_name_by = frappe.db.get_single_value("Healthcare Settings", "patient_name_by") + if patient_name_by == "Patient Name": + self.name = self.get_patient_name() + else: + set_name_by_naming_series(self) + + def get_patient_name(self): + self.set_full_name() + name = self.patient_name + if frappe.db.get_value("Patient", name): + count = frappe.db.sql( + """select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabPatient + where name like %s""", + "%{0} - %".format(name), + as_list=1, + )[0][0] + count = cint(count) + 1 + return "{0} - {1}".format(name, cstr(count)) + + return name + + @property + def age(self): + if not self.dob: + return + dob = getdate(self.dob) + age = dateutil.relativedelta.relativedelta(getdate(), dob) + return age + + def get_age(self): + age = self.age + if not age: + return + age_str = f'{str(age.years)} {_("Year(s)")} {str(age.months)} {_("Month(s)")} {str(age.days)} {_("Day(s)")}' + return age_str + + @frappe.whitelist() + def invoice_patient_registration(self): + if frappe.db.get_single_value("Healthcare Settings", "registration_fee"): + company = frappe.defaults.get_user_default("company") + if not company: + company = frappe.db.get_single_value("Global Defaults", "default_company") + + sales_invoice = make_invoice(self.name, company) + sales_invoice.save(ignore_permissions=True) + frappe.db.set_value("Patient", self.name, "status", "Active") + send_registration_sms(self) + + return {"invoice": sales_invoice.name} + + def set_contact(self): + contact = get_default_contact(self.doctype, self.name) + + if contact: + old_doc = self.get_doc_before_save() + if not old_doc: + return + + if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone: + self.update_contact(contact) + else: + if self.customer: + # customer contact exists, link patient + contact = get_default_contact("Customer", self.customer) + + if contact: + self.update_contact(contact) + else: + self.reload() + if self.email or self.mobile or self.phone: + contact = frappe.get_doc( + { + "doctype": "Contact", + "first_name": self.first_name, + "middle_name": self.middle_name, + "last_name": self.last_name, + "gender": self.sex, + "is_primary_contact": 1, + } + ) + contact.append("links", dict(link_doctype="Patient", link_name=self.name)) + if self.customer: + contact.append("links", dict(link_doctype="Customer", link_name=self.customer)) + + contact.insert(ignore_permissions=True) + self.update_contact(contact.name) + + def update_contact(self, contact): + contact = frappe.get_doc("Contact", contact) + + if not contact.has_link(self.doctype, self.name): + contact.append("links", dict(link_doctype=self.doctype, link_name=self.name)) + + if self.email and self.email != contact.email_id: + for email in contact.email_ids: + email.is_primary = True if email.email_id == self.email else False + contact.add_email(self.email, is_primary=True) + contact.set_primary_email() + + if self.mobile and self.mobile != contact.mobile_no: + for mobile in contact.phone_nos: + mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False + contact.add_phone(self.mobile, is_primary_mobile_no=True) + contact.set_primary("mobile_no") + + if self.phone and self.phone != contact.phone: + for phone in contact.phone_nos: + phone.is_primary_phone = True if phone.phone == self.phone else False + contact.add_phone(self.phone, is_primary_phone=True) + contact.set_primary("phone") + + contact.flags.skip_patient_update = True + contact.save(ignore_permissions=True) + + +def create_customer(doc): + customer = frappe.get_doc( + { + "doctype": "Customer", + "customer_name": doc.patient_name, + "customer_group": doc.customer_group + or frappe.db.get_single_value("Selling Settings", "customer_group"), + "territory": doc.territory or frappe.db.get_single_value("Selling Settings", "territory"), + "customer_type": "Individual", + "default_currency": doc.default_currency, + "default_price_list": doc.default_price_list, + "language": doc.language, + "image": doc.image, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + frappe.db.set_value("Patient", doc.name, "customer", customer.name) + frappe.msgprint(_("Customer {0} is created.").format(customer.name), alert=True) + + +def make_invoice(patient, company): + uom = frappe.db.exists("UOM", "Nos") or frappe.db.get_single_value("Stock Settings", "stock_uom") + sales_invoice = frappe.new_doc("Sales Invoice") + sales_invoice.customer = frappe.db.get_value("Patient", patient, "customer") + sales_invoice.due_date = getdate() + sales_invoice.company = company + sales_invoice.is_pos = 0 + sales_invoice.debit_to = get_receivable_account(company) + + item_line = sales_invoice.append("items") + item_line.item_name = "Registration Fee" + item_line.description = "Registration Fee" + item_line.qty = 1 + item_line.uom = uom + item_line.conversion_factor = 1 + item_line.income_account = get_income_account(None, company) + item_line.rate = frappe.db.get_single_value("Healthcare Settings", "registration_fee") + item_line.amount = item_line.rate + sales_invoice.set_missing_values() + return sales_invoice + + +@frappe.whitelist() +def get_patient_detail(patient): + patient_dict = frappe.db.sql("""select * from tabPatient where name=%s""", (patient), as_dict=1) + if not patient_dict: + frappe.throw(_("Patient not found")) + vital_sign = frappe.db.sql( + """select * from `tabVital Signs` where patient=%s + order by signs_date desc limit 1""", + (patient), + as_dict=1, + ) + + details = patient_dict[0] + if vital_sign: + vital_sign[0].pop("inpatient_record") + details.update(vital_sign[0]) + return details + + +def get_timeline_data(doctype, name): + """ + Return Patient's timeline data from medical records + Also include the associated Customer timeline data + """ + patient_timeline_data = dict( + frappe.db.sql( + """ + SELECT + unix_timestamp(communication_date), count(*) + FROM + `tabPatient Medical Record` + WHERE + patient=%s + and `communication_date` > date_sub(curdate(), interval 1 year) + GROUP BY communication_date""", + name, + ) + ) + + customer = frappe.db.get_value(doctype, name, "customer") + if customer: + from erpnext.accounts.party import get_timeline_data + + customer_timeline_data = get_timeline_data("Customer", customer) + patient_timeline_data.update(customer_timeline_data) + + return patient_timeline_data diff --git a/healthcare/healthcare/doctype/patient/patient_dashboard.py b/healthcare/healthcare/doctype/patient/patient_dashboard.py new file mode 100644 index 0000000..198c279 --- /dev/null +++ b/healthcare/healthcare/doctype/patient/patient_dashboard.py @@ -0,0 +1,26 @@ +from frappe import _ + + +def get_data(): + return { + "heatmap": True, + "heatmap_message": _( + "This is based on transactions against this Patient. See timeline below for details" + ), + "fieldname": "patient", + "non_standard_fieldnames": {"Payment Entry": "party"}, + "transactions": [ + { + "label": _("Appointments and Encounters"), + "items": ["Patient Appointment", "Vital Signs", "Patient Encounter"], + }, + {"label": _("Lab Tests and Vital Signs"), "items": ["Lab Test", "Sample Collection"]}, + { + "label": _("Rehab and Physiotherapy"), + "items": ["Patient Assessment", "Therapy Session", "Therapy Plan"], + }, + {"label": _("Surgery"), "items": ["Clinical Procedure"]}, + {"label": _("Admissions"), "items": ["Inpatient Record", "Inpatient Medication Order"]}, + {"label": _("Billing and Payments"), "items": ["Sales Invoice", "Payment Entry"]}, + ], + } diff --git a/healthcare/healthcare/doctype/patient/test_patient.py b/healthcare/healthcare/doctype/patient/test_patient.py new file mode 100644 index 0000000..9225df8 --- /dev/null +++ b/healthcare/healthcare/doctype/patient/test_patient.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +import os + +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_patient, +) + + +class TestPatient(FrappeTestCase): + def test_customer_created(self): + frappe.db.sql("""delete from `tabPatient`""") + frappe.db.set_value("Healthcare Settings", None, "link_customer_to_patient", 1) + patient = create_patient() + self.assertTrue(frappe.db.get_value("Patient", patient, "customer")) + + def test_patient_registration(self): + frappe.db.sql("""delete from `tabPatient`""") + settings = frappe.get_single("Healthcare Settings") + settings.collect_registration_fee = 1 + settings.registration_fee = 500 + settings.save() + + patient = create_patient() + patient = frappe.get_doc("Patient", patient) + self.assertEqual(patient.status, "Disabled") + + # check sales invoice and patient status + result = patient.invoice_patient_registration() + self.assertTrue(frappe.db.exists("Sales Invoice", result.get("invoice"))) + self.assertTrue(patient.status, "Active") + + settings.collect_registration_fee = 0 + settings.save() + + def test_patient_contact(self): + frappe.db.sql("""delete from `tabPatient` where name like '_Test Patient%'""") + frappe.db.sql("""delete from `tabCustomer` where name like '_Test Patient%'""") + frappe.db.sql("""delete from `tabContact` where name like'_Test Patient%'""") + frappe.db.sql("""delete from `tabDynamic Link` where parent like '_Test Patient%'""") + + patient = create_patient( + patient_name="_Test Patient Contact", email="test-patient@example.com", mobile="+91 0000000001" + ) + customer = frappe.db.get_value("Patient", patient, "customer") + self.assertTrue(customer) + self.assertTrue( + frappe.db.exists( + "Dynamic Link", {"parenttype": "Contact", "link_doctype": "Patient", "link_name": patient} + ) + ) + self.assertTrue( + frappe.db.exists( + "Dynamic Link", {"parenttype": "Contact", "link_doctype": "Customer", "link_name": customer} + ) + ) + + # a second patient linking with same customer + new_patient = create_patient( + email="test-patient@example.com", mobile="+91 0000000009", customer=customer + ) + self.assertTrue( + frappe.db.exists( + "Dynamic Link", {"parenttype": "Contact", "link_doctype": "Patient", "link_name": new_patient} + ) + ) + self.assertTrue( + frappe.db.exists( + "Dynamic Link", {"parenttype": "Contact", "link_doctype": "Customer", "link_name": customer} + ) + ) + + def test_patient_user(self): + frappe.db.sql("""delete from `tabUser` where email='test-patient-user@example.com'""") + frappe.db.sql("""delete from `tabDynamic Link` where parent like '_Test Patient%'""") + frappe.db.sql("""delete from `tabPatient` where name like '_Test Patient%'""") + + patient = create_patient( + patient_name="_Test Patient User", + email="test-patient-user@example.com", + mobile="+91 0000000009", + create_user=True, + ) + user = frappe.db.get_value("Patient", patient, "user_id") + self.assertTrue(frappe.db.exists("User", user)) + + new_patient = frappe.get_doc( + { + "doctype": "Patient", + "first_name": "_Test Patient Duplicate User", + "sex": "Male", + "email": "test-patient-user@example.com", + "mobile": "+91 0000000009", + "invite_user": 1, + } + ) + + self.assertRaises(frappe.exceptions.DuplicateEntryError, new_patient.insert) + + def test_patient_image_update_should_update_customer_image(self): + settings = frappe.get_single("Healthcare Settings") + settings.link_customer_to_patient = 1 + settings.save() + + patient_name = create_patient() + patient = frappe.get_doc("Patient", patient_name) + patient.image = os.path.abspath("assets/frappe/images/default-avatar.png") + patient.save() + + customer = frappe.get_doc("Customer", patient.customer) + self.assertEqual(customer.image, patient.image) diff --git a/healthcare/healthcare/doctype/patient_appointment/__init__.py b/healthcare/healthcare/doctype/patient_appointment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_appointment/patient_appointment.js b/healthcare/healthcare/doctype/patient_appointment/patient_appointment.js new file mode 100644 index 0000000..66cb585 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_appointment/patient_appointment.js @@ -0,0 +1,659 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt +frappe.provide('erpnext.queries'); +frappe.ui.form.on('Patient Appointment', { + setup: function(frm) { + frm.custom_make_buttons = { + 'Vital Signs': 'Vital Signs', + 'Patient Encounter': 'Patient Encounter' + }; + }, + + onload: function(frm) { + if (frm.is_new()) { + frm.set_value('appointment_time', null); + frm.disable_save(); + } + }, + + refresh: function(frm) { + frm.set_query('patient', function() { + return { + filters: { 'status': 'Active' } + }; + }); + + frm.set_query('practitioner', function() { + if (frm.doc.department) { + return { + filters: { + 'department': frm.doc.department + } + }; + } + }); + + frm.set_query('service_unit', function() { + return { + query: 'healthcare.controllers.queries.get_healthcare_service_units', + filters: { + company: frm.doc.company, + inpatient_record: frm.doc.inpatient_record + } + }; + }); + + frm.set_query('therapy_plan', function() { + return { + filters: { + 'patient': frm.doc.patient + } + }; + }); + + frm.trigger('set_therapy_type_filter'); + + if (frm.is_new()) { + frm.page.set_primary_action(__('Check Availability'), function() { + if (!frm.doc.patient) { + frappe.msgprint({ + title: __('Not Allowed'), + message: __('Please select Patient first'), + indicator: 'red' + }); + } else { + frappe.call({ + method: 'healthcare.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', + args: { 'patient': frm.doc.patient }, + callback: function(data) { + if (data.message == true) { + if (frm.doc.mode_of_payment && frm.doc.paid_amount) { + check_and_set_availability(frm); + } + if (!frm.doc.mode_of_payment) { + frappe.msgprint({ + title: __('Not Allowed'), + message: __('Please select a Mode of Payment first'), + indicator: 'red' + }); + } + if (!frm.doc.paid_amount) { + frappe.msgprint({ + title: __('Not Allowed'), + message: __('Please set the Paid Amount first'), + indicator: 'red' + }); + } + } else { + check_and_set_availability(frm); + } + } + }); + } + }); + } else { + frm.page.set_primary_action(__('Save'), () => frm.save()); + } + + if (frm.doc.patient) { + frm.add_custom_button(__('Patient History'), function() { + frappe.route_options = { 'patient': frm.doc.patient }; + frappe.set_route('patient_history'); + }, __('View')); + } + + if (frm.doc.status == 'Open' || (frm.doc.status == 'Scheduled' && !frm.doc.__islocal)) { + frm.add_custom_button(__('Cancel'), function() { + update_status(frm, 'Cancelled'); + }); + frm.add_custom_button(__('Reschedule'), function() { + check_and_set_availability(frm); + }); + + if (frm.doc.procedure_template) { + frm.add_custom_button(__('Clinical Procedure'), function() { + frappe.model.open_mapped_doc({ + method: 'healthcare.healthcare.doctype.clinical_procedure.clinical_procedure.make_procedure', + frm: frm, + }); + }, __('Create')); + } else if (frm.doc.therapy_type) { + frm.add_custom_button(__('Therapy Session'), function() { + frappe.model.open_mapped_doc({ + method: 'healthcare.healthcare.doctype.therapy_session.therapy_session.create_therapy_session', + frm: frm, + }) + }, 'Create'); + } else { + frm.add_custom_button(__('Patient Encounter'), function() { + frappe.model.open_mapped_doc({ + method: 'healthcare.healthcare.doctype.patient_appointment.patient_appointment.make_encounter', + frm: frm, + }); + }, __('Create')); + } + + frm.add_custom_button(__('Vital Signs'), function() { + create_vital_signs(frm); + }, __('Create')); + } + }, + + patient: function(frm) { + if (frm.doc.patient) { + frm.trigger('toggle_payment_fields'); + frappe.call({ + method: 'frappe.client.get', + args: { + doctype: 'Patient', + name: frm.doc.patient + }, + callback: function(data) { + let age = null; + if (data.message.dob) { + age = calculate_age(data.message.dob); + } + frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age); + } + }); + } else { + frm.set_value('patient_name', ''); + frm.set_value('patient_sex', ''); + frm.set_value('patient_age', ''); + frm.set_value('inpatient_record', ''); + } + }, + + practitioner: function(frm) { + if (frm.doc.practitioner) { + frm.events.set_payment_details(frm); + } + }, + + appointment_type: function(frm) { + if (frm.doc.appointment_type) { + frm.events.set_payment_details(frm); + } + }, + + set_payment_details: function(frm) { + frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing').then(val => { + if (val) { + frappe.call({ + method: 'healthcare.healthcare.utils.get_service_item_and_practitioner_charge', + args: { + doc: frm.doc + }, + callback: function(data) { + if (data.message) { + frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.practitioner_charge); + frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.service_item); + } + } + }); + } + }); + }, + + therapy_plan: function(frm) { + frm.trigger('set_therapy_type_filter'); + }, + + set_therapy_type_filter: function(frm) { + if (frm.doc.therapy_plan) { + frm.call('get_therapy_types').then(r => { + frm.set_query('therapy_type', function() { + return { + filters: { + 'name': ['in', r.message] + } + }; + }); + }); + } + }, + + therapy_type: function(frm) { + if (frm.doc.therapy_type) { + frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => { + if (r.default_duration) { + frm.set_value('duration', r.default_duration) + } + }); + } + }, + + get_procedure_from_encounter: function(frm) { + get_prescribed_procedure(frm); + }, + + toggle_payment_fields: function(frm) { + frappe.call({ + method: 'healthcare.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', + args: { 'patient': frm.doc.patient }, + callback: function(data) { + if (data.message.fee_validity) { + // if fee validity exists and automated appointment invoicing is enabled, + // show payment fields as non-mandatory + frm.toggle_display('mode_of_payment', 0); + frm.toggle_display('paid_amount', 0); + frm.toggle_display('billing_item', 0); + frm.toggle_reqd('mode_of_payment', 0); + frm.toggle_reqd('paid_amount', 0); + frm.toggle_reqd('billing_item', 0); + } else if (data.message) { + frm.toggle_display('mode_of_payment', 1); + frm.toggle_display('paid_amount', 1); + frm.toggle_display('billing_item', 1); + frm.toggle_reqd('mode_of_payment', 1); + frm.toggle_reqd('paid_amount', 1); + frm.toggle_reqd('billing_item', 1); + } else { + // if automated appointment invoicing is disabled, hide fields + frm.toggle_display('mode_of_payment', data.message ? 1 : 0); + frm.toggle_display('paid_amount', data.message ? 1 : 0); + frm.toggle_display('billing_item', data.message ? 1 : 0); + frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0); + frm.toggle_reqd('paid_amount', data.message ? 1 : 0); + frm.toggle_reqd('billing_item', data.message ? 1 : 0); + } + } + }); + }, + + get_prescribed_therapies: function(frm) { + if (frm.doc.patient) { + frappe.call({ + method: "healthcare.healthcare.doctype.patient_appointment.patient_appointment.get_prescribed_therapies", + args: { patient: frm.doc.patient }, + callback: function(r) { + if (r.message) { + show_therapy_types(frm, r.message); + } else { + frappe.msgprint({ + title: __('Not Therapies Prescribed'), + message: __('There are no Therapies prescribed for Patient {0}', [frm.doc.patient.bold()]), + indicator: 'blue' + }); + } + } + }); + } + } +}); + +let check_and_set_availability = function(frm) { + let selected_slot = null; + let service_unit = null; + let duration = null; + + show_availability(); + + function show_empty_state(practitioner, appointment_date) { + frappe.msgprint({ + title: __('Not Available'), + message: __('Healthcare Practitioner {0} not available on {1}', [practitioner.bold(), appointment_date.bold()]), + indicator: 'red' + }); + } + + function show_availability() { + let selected_practitioner = ''; + let d = new frappe.ui.Dialog({ + title: __('Available slots'), + fields: [ + { fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department' }, + { fieldtype: 'Column Break' }, + { fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner' }, + { fieldtype: 'Column Break' }, + { fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date', min_date: new Date(frappe.datetime.get_today()) }, + { fieldtype: 'Section Break' }, + { fieldtype: 'HTML', fieldname: 'available_slots' } + + ], + primary_action_label: __('Book'), + primary_action: function() { + frm.set_value('appointment_time', selected_slot); + if (!frm.doc.duration) { + frm.set_value('duration', duration); + } + frm.set_value('practitioner', d.get_value('practitioner')); + frm.set_value('department', d.get_value('department')); + frm.set_value('appointment_date', d.get_value('appointment_date')); + if (service_unit) { + frm.set_value('service_unit', service_unit); + } + d.hide(); + frm.enable_save(); + frm.save(); + d.get_primary_btn().attr('disabled', true); + } + }); + + d.set_values({ + 'department': frm.doc.department, + 'practitioner': frm.doc.practitioner, + 'appointment_date': frm.doc.appointment_date + }); + + d.fields_dict['department'].df.onchange = () => { + d.set_values({ + 'practitioner': '' + }); + let department = d.get_value('department'); + if (department) { + d.fields_dict.practitioner.get_query = function() { + return { + filters: { + 'department': department + } + }; + }; + } + }; + + // disable dialog action initially + d.get_primary_btn().attr('disabled', true); + + // Field Change Handler + + let fd = d.fields_dict; + + d.fields_dict['appointment_date'].df.onchange = () => { + show_slots(d, fd); + }; + d.fields_dict['practitioner'].df.onchange = () => { + if (d.get_value('practitioner') && d.get_value('practitioner') != selected_practitioner) { + selected_practitioner = d.get_value('practitioner'); + show_slots(d, fd); + } + }; + d.show(); + } + + function show_slots(d, fd) { + if (d.get_value('appointment_date') && d.get_value('practitioner')) { + fd.available_slots.html(''); + frappe.call({ + method: 'healthcare.healthcare.doctype.patient_appointment.patient_appointment.get_availability_data', + args: { + practitioner: d.get_value('practitioner'), + date: d.get_value('appointment_date') + }, + callback: (r) => { + let data = r.message; + if (data.slot_details.length > 0) { + let $wrapper = d.fields_dict.available_slots.$wrapper; + + // make buttons for each slot + let slot_html = get_slots(data.slot_details); + + $wrapper + .css('margin-bottom', 0) + .addClass('text-center') + .html(slot_html); + + // highlight button when clicked + $wrapper.on('click', 'button', function() { + let $btn = $(this); + $wrapper.find('button').removeClass('btn-outline-primary'); + $btn.addClass('btn-outline-primary'); + selected_slot = $btn.attr('data-name'); + service_unit = $btn.attr('data-service-unit'); + duration = $btn.attr('data-duration'); + // enable primary action 'Book' + d.get_primary_btn().attr('disabled', null); + }); + + } else { + // fd.available_slots.html('Please select a valid date.'.bold()) + show_empty_state(d.get_value('practitioner'), d.get_value('appointment_date')); + } + }, + freeze: true, + freeze_message: __('Fetching Schedule...') + }); + } else { + fd.available_slots.html(__('Appointment date and Healthcare Practitioner are Mandatory').bold()); + } + } + + function get_slots(slot_details) { + let slot_html = ''; + let appointment_count = 0; + let disabled = false; + let start_str, slot_start_time, slot_end_time, interval, count, count_class, tool_tip, available_slots; + + slot_details.forEach((slot_info) => { + slot_html += `
+ ${__('Practitioner Schedule:')} ${slot_info.slot_name}
+ ${__('Service Unit:')} ${slot_info.service_unit} `; + + if (slot_info.service_unit_capacity) { + slot_html += `
${__('Maximum Capacity:')} ${slot_info.service_unit_capacity} `; + } + + slot_html += '

'; + + slot_html += slot_info.avail_slot.map(slot => { + appointment_count = 0; + disabled = false; + count_class = tool_tip = ''; + start_str = slot.from_time; + slot_start_time = moment(slot.from_time, 'HH:mm:ss'); + slot_end_time = moment(slot.to_time, 'HH:mm:ss'); + interval = (slot_end_time - slot_start_time) / 60000 | 0; + + // iterate in all booked appointments, update the start time and duration + slot_info.appointments.forEach((booked) => { + let booked_moment = moment(booked.appointment_time, 'HH:mm:ss'); + let end_time = booked_moment.clone().add(booked.duration, 'minutes'); + + // Deal with 0 duration appointments + if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_end_time)) { + if (booked.duration == 0) { + disabled = true; + return false; + } + } + + // Check for overlaps considering appointment duration + if (slot_info.allow_overlap != 1) { + if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { + // There is an overlap + disabled = true; + return false; + } + } else { + if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { + appointment_count++; + } + if (appointment_count >= slot_info.service_unit_capacity) { + // There is an overlap + disabled = true; + return false; + } + } + }); + + if (slot_info.allow_overlap == 1 && slot_info.service_unit_capacity > 1) { + available_slots = slot_info.service_unit_capacity - appointment_count; + count = `${(available_slots > 0 ? available_slots : __('Full'))}`; + count_class = `${(available_slots > 0 ? 'badge-success' : 'badge-danger')}`; + tool_tip =`${available_slots} ${__('slots available for booking')}`; + } + return ` + `; + + }).join(""); + + if (slot_info.service_unit_capacity) { + slot_html += `
${__('Each slot indicates the capacity currently available for booking')}`; + } + slot_html += `

`; + }); + + return slot_html; + } +}; + +let get_prescribed_procedure = function(frm) { + if (frm.doc.patient) { + frappe.call({ + method: 'healthcare.healthcare.doctype.patient_appointment.patient_appointment.get_procedure_prescribed', + args: { patient: frm.doc.patient }, + callback: function(r) { + if (r.message && r.message.length) { + show_procedure_templates(frm, r.message); + } else { + frappe.msgprint({ + title: __('Not Found'), + message: __('No Prescribed Procedures found for the selected Patient') + }); + } + } + }); + } else { + frappe.msgprint({ + title: __('Not Allowed'), + message: __('Please select a Patient first') + }); + } +}; + +let show_procedure_templates = function(frm, result) { + let d = new frappe.ui.Dialog({ + title: __('Prescribed Procedures'), + fields: [ + { + fieldtype: 'HTML', fieldname: 'procedure_template' + } + ] + }); + let html_field = d.fields_dict.procedure_template.$wrapper; + html_field.empty(); + $.each(result, function(x, y) { + let row = $(repl('
\ +
%(encounter)s
%(consulting_practitioner)s
%(encounter_date)s
\ +
%(procedure_template)s
%(practitioner)s
%(date)s
\ +

', { + name: y[0], procedure_template: y[1], + encounter: y[2], consulting_practitioner: y[3], encounter_date: y[4], + practitioner: y[5] ? y[5] : '', date: y[6] ? y[6] : '', department: y[7] ? y[7] : '' + })).appendTo(html_field); + row.find("a").click(function() { + frm.doc.procedure_template = $(this).attr('data-procedure-template'); + frm.doc.procedure_prescription = $(this).attr('data-name'); + frm.doc.practitioner = $(this).attr('data-practitioner'); + frm.doc.appointment_date = $(this).attr('data-date'); + frm.doc.department = $(this).attr('data-department'); + refresh_field('procedure_template'); + refresh_field('procedure_prescription'); + refresh_field('appointment_date'); + refresh_field('practitioner'); + refresh_field('department'); + d.hide(); + return false; + }); + }); + if (!result) { + let msg = __('There are no procedure prescribed for ') + frm.doc.patient; + $(repl('
%(msg)s
', { msg: msg })).appendTo(html_field); + } + d.show(); +}; + +let show_therapy_types = function(frm, result) { + var d = new frappe.ui.Dialog({ + title: __('Prescribed Therapies'), + fields: [ + { + fieldtype: 'HTML', fieldname: 'therapy_type' + } + ] + }); + var html_field = d.fields_dict.therapy_type.$wrapper; + $.each(result, function(x, y) { + var row = $(repl('
\ +
%(encounter)s
%(practitioner)s
%(date)s
\ +
%(therapy)s
\ +

', { + therapy: y[0], + name: y[1], encounter: y[2], practitioner: y[3], date: y[4], + department: y[6] ? y[6] : '', therapy_plan: y[5] + })).appendTo(html_field); + + row.find("a").click(function() { + frm.doc.therapy_type = $(this).attr("data-therapy"); + frm.doc.practitioner = $(this).attr("data-practitioner"); + frm.doc.department = $(this).attr("data-department"); + frm.doc.therapy_plan = $(this).attr("data-therapy-plan"); + frm.refresh_field("therapy_type"); + frm.refresh_field("practitioner"); + frm.refresh_field("department"); + frm.refresh_field("therapy-plan"); + frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => { + if (r.default_duration) { + frm.set_value('duration', r.default_duration) + } + }); + d.hide(); + return false; + }); + }); + d.show(); +}; + +let create_vital_signs = function(frm) { + if (!frm.doc.patient) { + frappe.throw(__('Please select patient')); + } + frappe.route_options = { + 'patient': frm.doc.patient, + 'appointment': frm.doc.name, + 'company': frm.doc.company + }; + frappe.new_doc('Vital Signs'); +}; + +let update_status = function(frm, status) { + let doc = frm.doc; + frappe.confirm(__('Are you sure you want to cancel this appointment?'), + function() { + frappe.call({ + method: 'healthcare.healthcare.doctype.patient_appointment.patient_appointment.update_status', + args: { appointment_id: doc.name, status: status }, + callback: function(data) { + if (!data.exc) { + frm.reload_doc(); + } + } + }); + } + ); +}; + +let calculate_age = function(birth) { + let ageMS = Date.parse(Date()) - Date.parse(birth); + let age = new Date(); + age.setTime(ageMS); + let years = age.getFullYear() - 1970; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; +}; diff --git a/healthcare/healthcare/doctype/patient_appointment/patient_appointment.json b/healthcare/healthcare/doctype/patient_appointment/patient_appointment.json new file mode 100644 index 0000000..9508020 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_appointment/patient_appointment.json @@ -0,0 +1,403 @@ +{ + "actions": [], + "allow_copy": 0, + "allow_import": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2017-05-04 11:52:40.941507", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "title", + "status", + "patient", + "patient_name", + "patient_sex", + "patient_age", + "inpatient_record", + "column_break_1", + "company", + "practitioner", + "practitioner_name", + "department", + "service_unit", + "section_break_12", + "appointment_type", + "duration", + "procedure_template", + "get_procedure_from_encounter", + "procedure_prescription", + "therapy_plan", + "therapy_type", + "get_prescribed_therapies", + "column_break_17", + "appointment_date", + "appointment_time", + "appointment_datetime", + "section_break_16", + "mode_of_payment", + "billing_item", + "invoiced", + "column_break_2", + "paid_amount", + "ref_sales_invoice", + "section_break_3", + "referring_practitioner", + "reminded", + "column_break_36", + "notes" + ], + "fields": [ + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1, + "search_index": 1, + "set_only_once": 1 + }, + { + "fieldname": "appointment_type", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Appointment Type", + "options": "Appointment Type", + "set_only_once": 1 + }, + { + "fetch_from": "appointment_type.default_duration", + "fieldname": "duration", + "fieldtype": "Int", + "in_filter": 1, + "label": "Duration (In Minutes)", + "set_only_once": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "read_only": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "status", + "fieldtype": "Select", + "in_filter": 1, + "in_list_view": 1, + "label": "Status", + "options": "\nScheduled\nOpen\nClosed\nCancelled", + "read_only": 1, + "search_index": 1 + }, + { + "depends_on": "eval:doc.patient;", + "fieldname": "procedure_template", + "fieldtype": "Link", + "label": "Clinical Procedure Template", + "options": "Clinical Procedure Template", + "set_only_once": 1 + }, + { + "depends_on": "eval:doc.__islocal && doc.patient", + "fieldname": "get_procedure_from_encounter", + "fieldtype": "Button", + "label": "Get Prescribed Clinical Procedures" + }, + { + "fieldname": "procedure_prescription", + "fieldtype": "Link", + "hidden": 1, + "label": "Procedure Prescription", + "no_copy": 1, + "options": "Procedure Prescription", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "service_unit", + "fieldtype": "Link", + "label": "Service Unit", + "options": "Healthcare Service Unit", + "read_only": 1 + }, + { + "depends_on": "eval:doc.practitioner;", + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "label": "Appointment Details" + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "reqd": 1, + "search_index": 1, + "set_only_once": 1 + }, + { + "fetch_from": "practitioner.department", + "fieldname": "department", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Medical Department", + "search_index": 1, + "set_only_once": 1 + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "appointment_date", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Date", + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "appointment_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "Time", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break", + "label": "Payments" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fetch_from": "patient.sex", + "fieldname": "patient_sex", + "fieldtype": "Link", + "label": "Gender", + "no_copy": 1, + "options": "Gender", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "label": "Patient Age", + "read_only": 1 + }, + { + "fieldname": "appointment_datetime", + "fieldtype": "Datetime", + "hidden": 1, + "label": "Appointment Datetime", + "print_hide": 1, + "read_only": 1, + "report_hide": 1, + "search_index": 1 + }, + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment", + "read_only_depends_on": "invoiced" + }, + { + "fieldname": "paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount", + "read_only_depends_on": "invoiced" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "no_copy": 1, + "options": "Company", + "reqd": 1, + "set_only_once": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_3", + "fieldtype": "Section Break", + "label": "More Info" + }, + { + "fieldname": "notes", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Notes" + }, + { + "fieldname": "referring_practitioner", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Referring Practitioner", + "options": "Healthcare Practitioner" + }, + { + "default": "0", + "fieldname": "reminded", + "fieldtype": "Check", + "hidden": 1, + "label": "Reminded", + "print_hide": 1, + "report_hide": 1 + }, + { + "depends_on": "eval:doc.patient && doc.therapy_plan;", + "fieldname": "therapy_type", + "fieldtype": "Link", + "label": "Therapy", + "options": "Therapy Type", + "set_only_once": 1 + }, + { + "depends_on": "eval:doc.patient && doc.therapy_plan && doc.__islocal;", + "fieldname": "get_prescribed_therapies", + "fieldtype": "Button", + "label": "Get Prescribed Therapies" + }, + { + "depends_on": "eval: doc.patient;", + "fieldname": "therapy_plan", + "fieldtype": "Link", + "label": "Therapy Plan", + "options": "Therapy Plan", + "set_only_once": 1 + }, + { + "fieldname": "ref_sales_invoice", + "fieldtype": "Link", + "label": "Reference Sales Invoice", + "options": "Sales Invoice", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-APP-.YYYY.-", + "set_only_once": 1 + }, + { + "fieldname": "billing_item", + "fieldtype": "Link", + "label": "Billing Item", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "column_break_36", + "fieldtype": "Column Break" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "label": "Practitioner Name", + "read_only": 1 + } + ], + "links": [], + "modified": "2022-02-14 09:00:41.329387", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Appointment", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient, practitioner, department, appointment_date, appointment_time", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1, + "track_seen": 1 +} diff --git a/healthcare/healthcare/doctype/patient_appointment/patient_appointment.py b/healthcare/healthcare/doctype/patient_appointment/patient_appointment.py new file mode 100644 index 0000000..340c396 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_appointment/patient_appointment.py @@ -0,0 +1,680 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import datetime +import json + +import frappe +from frappe import _ +from frappe.core.doctype.sms_settings.sms_settings import send_sms +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import flt, get_link_to_form, get_time, getdate + +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import ( + get_income_account, + get_receivable_account, +) +from healthcare.healthcare.utils import ( + check_fee_validity, + get_service_item_and_practitioner_charge, + manage_fee_validity, +) + +# todo: clean up imports +try: + from erpnext.hr.doctype.employee.employee import is_holiday +except ImportError: + from erpnext.setup.doctype.employee.employee import is_holiday + + +class MaximumCapacityError(frappe.ValidationError): + pass + + +class OverlapError(frappe.ValidationError): + pass + + +class PatientAppointment(Document): + def validate(self): + self.validate_overlaps() + self.validate_service_unit() + self.set_appointment_datetime() + self.validate_customer_created() + self.set_status() + self.set_title() + + def after_insert(self): + self.update_prescription_details() + self.set_payment_details() + invoice_appointment(self) + self.update_fee_validity() + send_confirmation_msg(self) + + def set_title(self): + self.title = _("{0} with {1}").format( + self.patient_name or self.patient, self.practitioner_name or self.practitioner + ) + + def set_status(self): + today = getdate() + appointment_date = getdate(self.appointment_date) + + # If appointment is created for today set status as Open else Scheduled + if appointment_date == today: + self.status = "Open" + elif appointment_date > today: + self.status = "Scheduled" + + def validate_overlaps(self): + end_time = datetime.datetime.combine( + getdate(self.appointment_date), get_time(self.appointment_time) + ) + datetime.timedelta(minutes=flt(self.duration)) + + # all appointments for both patient and practitioner overlapping the duration of this appointment + overlapping_appointments = frappe.db.sql( + """ + SELECT + name, practitioner, patient, appointment_time, duration, service_unit + FROM + `tabPatient Appointment` + WHERE + appointment_date=%(appointment_date)s AND name!=%(name)s AND status NOT IN ("Closed", "Cancelled") AND + (practitioner=%(practitioner)s OR patient=%(patient)s) AND + ((appointment_time<%(appointment_time)s AND appointment_time + INTERVAL duration MINUTE>%(appointment_time)s) OR + (appointment_time>%(appointment_time)s AND appointment_time<%(end_time)s) OR + (appointment_time=%(appointment_time)s)) + """, + { + "appointment_date": self.appointment_date, + "name": self.name, + "practitioner": self.practitioner, + "patient": self.patient, + "appointment_time": self.appointment_time, + "end_time": end_time.time(), + }, + as_dict=True, + ) + + if not overlapping_appointments: + return # No overlaps, nothing to validate! + + if self.service_unit: # validate service unit capacity if overlap enabled + allow_overlap, service_unit_capacity = frappe.get_value( + "Healthcare Service Unit", self.service_unit, ["overlap_appointments", "service_unit_capacity"] + ) + if allow_overlap: + service_unit_appointments = list( + filter( + lambda appointment: appointment["service_unit"] == self.service_unit + and appointment["patient"] != self.patient, + overlapping_appointments, + ) + ) # if same patient already booked, it should be an overlap + if len(service_unit_appointments) >= (service_unit_capacity or 1): + frappe.throw( + _("Not allowed, {} cannot exceed maximum capacity {}").format( + frappe.bold(self.service_unit), frappe.bold(service_unit_capacity or 1) + ), + MaximumCapacityError, + ) + else: # service_unit_appointments within capacity, remove from overlapping_appointments + overlapping_appointments = [ + appointment + for appointment in overlapping_appointments + if appointment not in service_unit_appointments + ] + + if overlapping_appointments: + frappe.throw( + _("Not allowed, cannot overlap appointment {}").format( + frappe.bold(", ".join([appointment["name"] for appointment in overlapping_appointments])) + ), + OverlapError, + ) + + def validate_service_unit(self): + if self.inpatient_record and self.service_unit: + from healthcare.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( + get_current_healthcare_service_unit, + ) + + is_inpatient_occupancy_unit = frappe.db.get_value( + "Healthcare Service Unit", self.service_unit, "inpatient_occupancy" + ) + service_unit = get_current_healthcare_service_unit(self.inpatient_record) + if is_inpatient_occupancy_unit and service_unit != self.service_unit: + msg = ( + _("Patient {0} is not admitted in the service unit {1}").format( + frappe.bold(self.patient), frappe.bold(self.service_unit) + ) + + "
" + ) + msg += _( + "Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted." + ) + frappe.throw(msg, title=_("Invalid Healthcare Service Unit")) + + def set_appointment_datetime(self): + self.appointment_datetime = "%s %s" % ( + self.appointment_date, + self.appointment_time or "00:00:00", + ) + + def set_payment_details(self): + if frappe.db.get_single_value("Healthcare Settings", "automate_appointment_invoicing"): + details = get_service_item_and_practitioner_charge(self) + self.db_set("billing_item", details.get("service_item")) + if not self.paid_amount: + self.db_set("paid_amount", details.get("practitioner_charge")) + + def validate_customer_created(self): + if frappe.db.get_single_value("Healthcare Settings", "automate_appointment_invoicing"): + if not frappe.db.get_value("Patient", self.patient, "customer"): + msg = _("Please set a Customer linked to the Patient") + msg += " {0}".format(self.patient) + frappe.throw(msg, title=_("Customer Not Found")) + + def update_prescription_details(self): + if self.procedure_prescription: + frappe.db.set_value( + "Procedure Prescription", self.procedure_prescription, "appointment_booked", 1 + ) + if self.procedure_template: + comments = frappe.db.get_value( + "Procedure Prescription", self.procedure_prescription, "comments" + ) + if comments: + frappe.db.set_value("Patient Appointment", self.name, "notes", comments) + + def update_fee_validity(self): + if not frappe.db.get_single_value("Healthcare Settings", "enable_free_follow_ups"): + return + + fee_validity = manage_fee_validity(self) + if fee_validity: + frappe.msgprint( + _("{0}: {1} has fee validity till {2}").format( + self.patient, frappe.bold(self.patient_name), fee_validity.valid_till + ) + ) + + @frappe.whitelist() + def get_therapy_types(self): + if not self.therapy_plan: + return + + therapy_types = [] + doc = frappe.get_doc("Therapy Plan", self.therapy_plan) + for entry in doc.therapy_plan_details: + therapy_types.append(entry.therapy_type) + + return therapy_types + + +@frappe.whitelist() +def check_payment_fields_reqd(patient): + automate_invoicing = frappe.db.get_single_value( + "Healthcare Settings", "automate_appointment_invoicing" + ) + free_follow_ups = frappe.db.get_single_value("Healthcare Settings", "enable_free_follow_ups") + if automate_invoicing: + if free_follow_ups: + fee_validity = frappe.db.exists("Fee Validity", {"patient": patient, "status": "Pending"}) + if fee_validity: + return {"fee_validity": fee_validity} + return True + return False + + +def invoice_appointment(appointment_doc): + automate_invoicing = frappe.db.get_single_value( + "Healthcare Settings", "automate_appointment_invoicing" + ) + appointment_invoiced = frappe.db.get_value( + "Patient Appointment", appointment_doc.name, "invoiced" + ) + enable_free_follow_ups = frappe.db.get_single_value( + "Healthcare Settings", "enable_free_follow_ups" + ) + if enable_free_follow_ups: + fee_validity = check_fee_validity(appointment_doc) + if fee_validity and fee_validity.status == "Completed": + fee_validity = None + elif not fee_validity: + if frappe.db.exists("Fee Validity Reference", {"appointment": appointment_doc.name}): + return + else: + fee_validity = None + + if automate_invoicing and not appointment_invoiced and not fee_validity: + create_sales_invoice(appointment_doc) + + +def create_sales_invoice(appointment_doc): + sales_invoice = frappe.new_doc("Sales Invoice") + sales_invoice.patient = appointment_doc.patient + sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer") + sales_invoice.appointment = appointment_doc.name + sales_invoice.due_date = getdate() + sales_invoice.company = appointment_doc.company + sales_invoice.debit_to = get_receivable_account(appointment_doc.company) + + item = sales_invoice.append("items", {}) + item = get_appointment_item(appointment_doc, item) + + # Add payments if payment details are supplied else proceed to create invoice as Unpaid + if appointment_doc.mode_of_payment and appointment_doc.paid_amount: + sales_invoice.is_pos = 1 + payment = sales_invoice.append("payments", {}) + payment.mode_of_payment = appointment_doc.mode_of_payment + payment.amount = appointment_doc.paid_amount + + sales_invoice.set_missing_values(for_validate=True) + sales_invoice.flags.ignore_mandatory = True + sales_invoice.save(ignore_permissions=True) + sales_invoice.submit() + frappe.msgprint(_("Sales Invoice {0} created").format(sales_invoice.name), alert=True) + frappe.db.set_value( + "Patient Appointment", + appointment_doc.name, + {"invoiced": 1, "ref_sales_invoice": sales_invoice.name}, + ) + + +def check_is_new_patient(patient, name=None): + filters = {"patient": patient, "status": ("!=", "Cancelled")} + if name: + filters["name"] = ("!=", name) + + has_previous_appointment = frappe.db.exists("Patient Appointment", filters) + return not has_previous_appointment + + +def get_appointment_item(appointment_doc, item): + details = get_service_item_and_practitioner_charge(appointment_doc) + charge = appointment_doc.paid_amount or details.get("practitioner_charge") + item.item_code = details.get("service_item") + item.description = _("Consulting Charges: {0}").format(appointment_doc.practitioner) + item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company) + item.cost_center = frappe.get_cached_value("Company", appointment_doc.company, "cost_center") + item.rate = charge + item.amount = charge + item.qty = 1 + item.reference_dt = "Patient Appointment" + item.reference_dn = appointment_doc.name + return item + + +def cancel_appointment(appointment_id): + appointment = frappe.get_doc("Patient Appointment", appointment_id) + if appointment.invoiced: + sales_invoice = check_sales_invoice_exists(appointment) + if sales_invoice and cancel_sales_invoice(sales_invoice): + msg = _("Appointment {0} and Sales Invoice {1} cancelled").format( + appointment.name, sales_invoice.name + ) + else: + msg = _("Appointment Cancelled. Please review and cancel the invoice {0}").format( + sales_invoice.name + ) + else: + fee_validity = manage_fee_validity(appointment) + msg = _("Appointment Cancelled.") + if fee_validity: + msg += _("Fee Validity {0} updated.").format(fee_validity.name) + + frappe.msgprint(msg) + + +def cancel_sales_invoice(sales_invoice): + if frappe.db.get_single_value("Healthcare Settings", "automate_appointment_invoicing"): + if len(sales_invoice.items) == 1: + sales_invoice.cancel() + return True + return False + + +def check_sales_invoice_exists(appointment): + sales_invoice = frappe.db.get_value( + "Sales Invoice Item", + {"reference_dt": "Patient Appointment", "reference_dn": appointment.name}, + "parent", + ) + + if sales_invoice: + sales_invoice = frappe.get_doc("Sales Invoice", sales_invoice) + return sales_invoice + return False + + +@frappe.whitelist() +def get_availability_data(date, practitioner): + """ + Get availability data of 'practitioner' on 'date' + :param date: Date to check in schedule + :param practitioner: Name of the practitioner + :return: dict containing a list of available slots, list of appointments and time of appointments + """ + + date = getdate(date) + weekday = date.strftime("%A") + + practitioner_doc = frappe.get_doc("Healthcare Practitioner", practitioner) + + check_employee_wise_availability(date, practitioner_doc) + + if practitioner_doc.practitioner_schedules: + slot_details = get_available_slots(practitioner_doc, date) + else: + frappe.throw( + _( + "{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master" + ).format(practitioner), + title=_("Practitioner Schedule Not Found"), + ) + + if not slot_details: + # TODO: return available slots in nearby dates + frappe.throw( + _("Healthcare Practitioner not available on {0}").format(weekday), title=_("Not Available") + ) + + return {"slot_details": slot_details} + + +def check_employee_wise_availability(date, practitioner_doc): + employee = None + if practitioner_doc.employee: + employee = practitioner_doc.employee + elif practitioner_doc.user_id: + employee = frappe.db.get_value("Employee", {"user_id": practitioner_doc.user_id}, "name") + + if employee: + # check holiday + if is_holiday(employee, date): + frappe.throw(_("{0} is a holiday".format(date)), title=_("Not Available")) + + # check leave status + leave_record = frappe.db.sql( + """select half_day from `tabLeave Application` + where employee = %s and %s between from_date and to_date + and docstatus = 1""", + (employee, date), + as_dict=True, + ) + if leave_record: + if leave_record[0].half_day: + frappe.throw( + _("{0} is on a Half day Leave on {1}").format(practitioner_doc.name, date), + title=_("Not Available"), + ) + else: + frappe.throw( + _("{0} is on Leave on {1}").format(practitioner_doc.name, date), title=_("Not Available") + ) + + +def get_available_slots(practitioner_doc, date): + available_slots = slot_details = [] + weekday = date.strftime("%A") + practitioner = practitioner_doc.name + + for schedule_entry in practitioner_doc.practitioner_schedules: + validate_practitioner_schedules(schedule_entry, practitioner) + practitioner_schedule = frappe.get_doc("Practitioner Schedule", schedule_entry.schedule) + + if practitioner_schedule and not practitioner_schedule.disabled: + available_slots = [] + for time_slot in practitioner_schedule.time_slots: + if weekday == time_slot.day: + available_slots.append(time_slot) + + if available_slots: + appointments = [] + allow_overlap = 0 + service_unit_capacity = 0 + # fetch all appointments to practitioner by service unit + filters = { + "practitioner": practitioner, + "service_unit": schedule_entry.service_unit, + "appointment_date": date, + "status": ["not in", ["Cancelled"]], + } + + if schedule_entry.service_unit: + slot_name = f"{schedule_entry.schedule}" + allow_overlap, service_unit_capacity = frappe.get_value( + "Healthcare Service Unit", + schedule_entry.service_unit, + ["overlap_appointments", "service_unit_capacity"], + ) + if not allow_overlap: + # fetch all appointments to service unit + filters.pop("practitioner") + else: + slot_name = schedule_entry.schedule + # fetch all appointments to practitioner without service unit + filters["practitioner"] = practitioner + filters.pop("service_unit") + + appointments = frappe.get_all( + "Patient Appointment", + filters=filters, + fields=["name", "appointment_time", "duration", "status"], + ) + + slot_details.append( + { + "slot_name": slot_name, + "service_unit": schedule_entry.service_unit, + "avail_slot": available_slots, + "appointments": appointments, + "allow_overlap": allow_overlap, + "service_unit_capacity": service_unit_capacity, + } + ) + + return slot_details + + +def validate_practitioner_schedules(schedule_entry, practitioner): + if schedule_entry.schedule: + if not schedule_entry.service_unit: + frappe.throw( + _( + "Practitioner {0} does not have a Service Unit set against the Practitioner Schedule {1}." + ).format( + get_link_to_form("Healthcare Practitioner", practitioner), + frappe.bold(schedule_entry.schedule), + ), + title=_("Service Unit Not Found"), + ) + + else: + frappe.throw( + _("Practitioner {0} does not have a Practitioner Schedule assigned.").format( + get_link_to_form("Healthcare Practitioner", practitioner) + ), + title=_("Practitioner Schedule Not Found"), + ) + + +@frappe.whitelist() +def update_status(appointment_id, status): + frappe.db.set_value("Patient Appointment", appointment_id, "status", status) + appointment_booked = True + if status == "Cancelled": + appointment_booked = False + cancel_appointment(appointment_id) + + procedure_prescription = frappe.db.get_value( + "Patient Appointment", appointment_id, "procedure_prescription" + ) + if procedure_prescription: + frappe.db.set_value( + "Procedure Prescription", procedure_prescription, "appointment_booked", appointment_booked + ) + + +def send_confirmation_msg(doc): + if frappe.db.get_single_value("Healthcare Settings", "send_appointment_confirmation"): + message = frappe.db.get_single_value("Healthcare Settings", "appointment_confirmation_msg") + try: + send_message(doc, message) + except Exception: + frappe.log_error(frappe.get_traceback(), _("Appointment Confirmation Message Not Sent")) + frappe.msgprint(_("Appointment Confirmation Message Not Sent"), indicator="orange") + + +@frappe.whitelist() +def make_encounter(source_name, target_doc=None): + doc = get_mapped_doc( + "Patient Appointment", + source_name, + { + "Patient Appointment": { + "doctype": "Patient Encounter", + "field_map": [ + ["appointment", "name"], + ["patient", "patient"], + ["practitioner", "practitioner"], + ["medical_department", "department"], + ["patient_sex", "patient_sex"], + ["invoiced", "invoiced"], + ["company", "company"], + ], + } + }, + target_doc, + ) + return doc + + +def send_appointment_reminder(): + if frappe.db.get_single_value("Healthcare Settings", "send_appointment_reminder"): + remind_before = datetime.datetime.strptime( + frappe.db.get_single_value("Healthcare Settings", "remind_before"), "%H:%M:%S" + ) + reminder_dt = datetime.datetime.now() + datetime.timedelta( + hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second + ) + + appointment_list = frappe.db.get_all( + "Patient Appointment", + { + "appointment_datetime": ["between", (datetime.datetime.now(), reminder_dt)], + "reminded": 0, + "status": ["!=", "Cancelled"], + }, + ) + + for appointment in appointment_list: + doc = frappe.get_doc("Patient Appointment", appointment.name) + message = frappe.db.get_single_value("Healthcare Settings", "appointment_reminder_msg") + send_message(doc, message) + frappe.db.set_value("Patient Appointment", doc.name, "reminded", 1) + + +def send_message(doc, message): + patient_mobile = frappe.db.get_value("Patient", doc.patient, "mobile") + if patient_mobile: + context = {"doc": doc, "alert": doc, "comments": None} + if doc.get("_comments"): + context["comments"] = json.loads(doc.get("_comments")) + + # jinja to string convertion happens here + message = frappe.render_template(message, context) + number = [patient_mobile] + try: + send_sms(number, message) + except Exception as e: + frappe.msgprint(_("SMS not sent, please check SMS Settings"), alert=True) + + +@frappe.whitelist() +def get_events(start, end, filters=None): + """Returns events for Gantt / Calendar view rendering. + + :param start: Start date-time. + :param end: End date-time. + :param filters: Filters (JSON). + """ + from frappe.desk.calendar import get_event_conditions + + conditions = get_event_conditions("Patient Appointment", filters) + + data = frappe.db.sql( + """ + select + `tabPatient Appointment`.name, `tabPatient Appointment`.patient, + `tabPatient Appointment`.practitioner, `tabPatient Appointment`.status, + `tabPatient Appointment`.duration, + timestamp(`tabPatient Appointment`.appointment_date, `tabPatient Appointment`.appointment_time) as 'start', + `tabAppointment Type`.color + from + `tabPatient Appointment` + left join `tabAppointment Type` on `tabPatient Appointment`.appointment_type=`tabAppointment Type`.name + where + (`tabPatient Appointment`.appointment_date between %(start)s and %(end)s) + and `tabPatient Appointment`.status != 'Cancelled' and `tabPatient Appointment`.docstatus < 2 {conditions}""".format( + conditions=conditions + ), + {"start": start, "end": end}, + as_dict=True, + update={"allDay": 0}, + ) + + for item in data: + item.end = item.start + datetime.timedelta(minutes=item.duration) + + return data + + +@frappe.whitelist() +def get_procedure_prescribed(patient): + return frappe.db.sql( + """ + SELECT + pp.name, pp.procedure, pp.parent, ct.practitioner, + ct.encounter_date, pp.practitioner, pp.date, pp.department + FROM + `tabPatient Encounter` ct, `tabProcedure Prescription` pp + WHERE + ct.patient=%(patient)s and pp.parent=ct.name and pp.appointment_booked=0 + ORDER BY + ct.creation desc + """, + {"patient": patient}, + ) + + +@frappe.whitelist() +def get_prescribed_therapies(patient): + return frappe.db.sql( + """ + SELECT + t.therapy_type, t.name, t.parent, e.practitioner, + e.encounter_date, e.therapy_plan, e.medical_department + FROM + `tabPatient Encounter` e, `tabTherapy Plan Detail` t + WHERE + e.patient=%(patient)s and t.parent=e.name + ORDER BY + e.creation desc + """, + {"patient": patient}, + ) + + +def update_appointment_status(): + # update the status of appointments daily + appointments = frappe.get_all( + "Patient Appointment", {"status": ("not in", ["Closed", "Cancelled"])}, as_dict=1 + ) + + for appointment in appointments: + frappe.get_doc("Patient Appointment", appointment.name).set_status() diff --git a/healthcare/healthcare/doctype/patient_appointment/patient_appointment_calendar.js b/healthcare/healthcare/doctype/patient_appointment/patient_appointment_calendar.js new file mode 100644 index 0000000..5976cac --- /dev/null +++ b/healthcare/healthcare/doctype/patient_appointment/patient_appointment_calendar.js @@ -0,0 +1,14 @@ + +frappe.views.calendar["Patient Appointment"] = { + field_map: { + "start": "start", + "end": "end", + "id": "name", + "title": "patient", + "allDay": "allDay", + "eventColor": "color" + }, + order_by: "appointment_date", + gantt: true, + get_events_method: "healthcare.healthcare.doctype.patient_appointment.patient_appointment.get_events" +}; diff --git a/healthcare/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py b/healthcare/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py new file mode 100644 index 0000000..3eeb153 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py @@ -0,0 +1,14 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "appointment", + "non_standard_fieldnames": {"Patient Medical Record": "reference_name"}, + "transactions": [ + { + "label": _("Consultations"), + "items": ["Patient Encounter", "Vital Signs", "Patient Medical Record"], + } + ], + } diff --git a/healthcare/healthcare/doctype/patient_appointment/patient_appointment_list.js b/healthcare/healthcare/doctype/patient_appointment/patient_appointment_list.js new file mode 100644 index 0000000..721887b --- /dev/null +++ b/healthcare/healthcare/doctype/patient_appointment/patient_appointment_list.js @@ -0,0 +1,16 @@ +/* +(c) ESS 2015-16 +*/ +frappe.listview_settings['Patient Appointment'] = { + filters: [["status", "=", "Open"]], + get_indicator: function(doc) { + var colors = { + "Open": "orange", + "Scheduled": "yellow", + "Closed": "green", + "Cancelled": "red", + "Expired": "grey" + }; + return [__(doc.status), colors[doc.status], "status,=," + doc.status]; + } +}; diff --git a/healthcare/healthcare/doctype/patient_appointment/test_patient_appointment.py b/healthcare/healthcare/doctype/patient_appointment/test_patient_appointment.py new file mode 100644 index 0000000..353b62c --- /dev/null +++ b/healthcare/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -0,0 +1,570 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import add_days, now_datetime, nowdate + +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile +from healthcare.healthcare.doctype.patient_appointment.patient_appointment import ( + check_is_new_patient, + check_payment_fields_reqd, + make_encounter, + update_status, +) + + +class TestPatientAppointment(FrappeTestCase): + def setUp(self): + frappe.db.sql("""delete from `tabPatient Appointment`""") + frappe.db.sql("""delete from `tabFee Validity`""") + frappe.db.sql("""delete from `tabPatient Encounter`""") + make_pos_profile() + frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test %'""") + frappe.db.sql( + """delete from `tabHealthcare Service Unit` where name like '_Test Service Unit Type%'""" + ) + + def test_status(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 0) + appointment = create_appointment(patient, practitioner, nowdate()) + self.assertEqual(appointment.status, "Open") + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2)) + self.assertEqual(appointment.status, "Scheduled") + encounter = create_encounter(appointment) + self.assertEqual( + frappe.db.get_value("Patient Appointment", appointment.name, "status"), "Closed" + ) + encounter.cancel() + self.assertEqual(frappe.db.get_value("Patient Appointment", appointment.name, "status"), "Open") + + def test_start_encounter(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice=1) + appointment.reload() + self.assertEqual(appointment.invoiced, 1) + encounter = make_encounter(appointment.name) + self.assertTrue(encounter) + self.assertEqual(encounter.company, appointment.company) + self.assertEqual(encounter.practitioner, appointment.practitioner) + self.assertEqual(encounter.patient, appointment.patient) + # invoiced flag mapped from appointment + self.assertEqual( + encounter.invoiced, frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") + ) + + def test_auto_invoicing(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 0) + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 0) + appointment = create_appointment(patient, practitioner, nowdate()) + self.assertEqual(frappe.db.get_value("Patient Appointment", appointment.name, "invoiced"), 0) + + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1) + self.assertEqual(frappe.db.get_value("Patient Appointment", appointment.name, "invoiced"), 1) + sales_invoice_name = frappe.db.get_value( + "Sales Invoice Item", {"reference_dn": appointment.name}, "parent" + ) + self.assertTrue(sales_invoice_name) + self.assertEqual( + frappe.db.get_value("Sales Invoice", sales_invoice_name, "company"), appointment.company + ) + self.assertEqual( + frappe.db.get_value("Sales Invoice", sales_invoice_name, "patient"), appointment.patient + ) + self.assertEqual( + frappe.db.get_value("Sales Invoice", sales_invoice_name, "paid_amount"), appointment.paid_amount + ) + + def test_auto_invoicing_based_on_department(self): + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 0) + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + appointment_type = create_appointment_type({"medical_department": medical_department}) + + appointment = create_appointment( + patient, + practitioner, + add_days(nowdate(), 2), + invoice=1, + appointment_type=appointment_type.name, + department=medical_department, + ) + appointment.reload() + + self.assertEqual(appointment.invoiced, 1) + self.assertEqual(appointment.billing_item, "HLC-SI-001") + self.assertEqual(appointment.paid_amount, 200) + + sales_invoice_name = frappe.db.get_value( + "Sales Invoice Item", {"reference_dn": appointment.name}, "parent" + ) + self.assertTrue(sales_invoice_name) + self.assertEqual( + frappe.db.get_value("Sales Invoice", sales_invoice_name, "paid_amount"), appointment.paid_amount + ) + + def test_auto_invoicing_according_to_appointment_type_charge(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 0) + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + + item = create_healthcare_service_items() + items = [{"op_consulting_charge_item": item, "op_consulting_charge": 300}] + appointment_type = create_appointment_type( + args={"name": "Generic Appointment Type charge", "items": items} + ) + + appointment = create_appointment( + patient, practitioner, add_days(nowdate(), 2), invoice=1, appointment_type=appointment_type.name + ) + appointment.reload() + + self.assertEqual(appointment.invoiced, 1) + self.assertEqual(appointment.billing_item, item) + self.assertEqual(appointment.paid_amount, 300) + + sales_invoice_name = frappe.db.get_value( + "Sales Invoice Item", {"reference_dn": appointment.name}, "parent" + ) + self.assertTrue(sales_invoice_name) + + def test_appointment_cancel(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 1) + appointment = create_appointment(patient, practitioner, nowdate()) + fee_validity = frappe.db.get_value( + "Fee Validity", {"patient": patient, "practitioner": practitioner} + ) + # fee validity created + self.assertTrue(fee_validity) + + # first follow up appointment + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1)) + self.assertEqual(frappe.db.get_value("Fee Validity", fee_validity, "visited"), 1) + + update_status(appointment.name, "Cancelled") + # check fee validity updated + self.assertEqual(frappe.db.get_value("Fee Validity", fee_validity, "visited"), 0) + + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 0) + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1), invoice=1) + update_status(appointment.name, "Cancelled") + # check invoice cancelled + sales_invoice_name = frappe.db.get_value( + "Sales Invoice Item", {"reference_dn": appointment.name}, "parent" + ) + self.assertEqual(frappe.db.get_value("Sales Invoice", sales_invoice_name, "status"), "Cancelled") + + def test_appointment_booking_for_admission_service_unit(self): + from healthcare.healthcare.doctype.inpatient_record.inpatient_record import ( + admit_patient, + discharge_patient, + schedule_discharge, + ) + from healthcare.healthcare.doctype.inpatient_record.test_inpatient_record import ( + create_inpatient, + get_healthcare_service_unit, + mark_invoiced_inpatient_occupancy, + ) + + frappe.db.sql("""delete from `tabInpatient Record`""") + patient, practitioner = create_healthcare_docs() + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions=True) + + # Admit + service_unit = get_healthcare_service_unit("_Test Service Unit Ip Occupancy") + admit_patient(ip_record, service_unit, now_datetime()) + + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) + self.assertEqual(appointment.service_unit, service_unit) + + # Discharge + schedule_discharge(frappe.as_json({"patient": patient})) + ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) + mark_invoiced_inpatient_occupancy(ip_record1) + discharge_patient(ip_record1) + + def test_invalid_healthcare_service_unit_validation(self): + from healthcare.healthcare.doctype.inpatient_record.inpatient_record import ( + admit_patient, + discharge_patient, + schedule_discharge, + ) + from healthcare.healthcare.doctype.inpatient_record.test_inpatient_record import ( + create_inpatient, + get_healthcare_service_unit, + mark_invoiced_inpatient_occupancy, + ) + + frappe.db.sql("""delete from `tabInpatient Record`""") + patient, practitioner = create_healthcare_docs() + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions=True) + + # Admit + service_unit = get_healthcare_service_unit("_Test Service Unit Ip Occupancy") + admit_patient(ip_record, service_unit, now_datetime()) + + appointment_service_unit = get_healthcare_service_unit( + "_Test Service Unit Ip Occupancy for Appointment" + ) + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0 + ) + self.assertRaises(frappe.exceptions.ValidationError, appointment.save) + + # Discharge + schedule_discharge(frappe.as_json({"patient": patient})) + ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) + mark_invoiced_inpatient_occupancy(ip_record1) + discharge_patient(ip_record1) + + def test_payment_should_be_mandatory_for_new_patient_appointment(self): + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 1) + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + frappe.db.set_value("Healthcare Settings", None, "max_visits", 3) + frappe.db.set_value("Healthcare Settings", None, "valid_days", 30) + + patient = create_patient() + assert check_is_new_patient(patient) + payment_required = check_payment_fields_reqd(patient) + assert payment_required is True + + def test_sales_invoice_should_be_generated_for_new_patient_appointment(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + invoice_count = frappe.db.count("Sales Invoice") + + assert check_is_new_patient(patient) + create_appointment(patient, practitioner, nowdate()) + new_invoice_count = frappe.db.count("Sales Invoice") + + assert new_invoice_count == invoice_count + 1 + + def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self): + patient, practitioner = create_healthcare_docs() + create_appointment(patient, practitioner, nowdate()) + + patient, new_practitioner = create_healthcare_docs(id=5) + create_appointment(patient, new_practitioner, nowdate()) + + roles = [{"doctype": "Has Role", "role": "Physician"}] + user = create_user(roles=roles) + new_practitioner = frappe.get_doc("Healthcare Practitioner", new_practitioner) + new_practitioner.user_id = user.email + new_practitioner.save() + + frappe.set_user(user.name) + appointments = frappe.get_list("Patient Appointment") + assert len(appointments) == 1 + + frappe.set_user("Administrator") + appointments = frappe.get_list("Patient Appointment") + assert len(appointments) == 2 + + def test_overlap_appointment(self): + from healthcare.healthcare.doctype.patient_appointment.patient_appointment import OverlapError + + patient, practitioner = create_healthcare_docs(id=1) + patient_1, practitioner_1 = create_healthcare_docs(id=2) + service_unit = create_service_unit(id=0) + service_unit_1 = create_service_unit(id=1) + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=service_unit + ) # valid + + # patient and practitioner cannot have overlapping appointments + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=service_unit, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=service_unit_1, save=0 + ) # diff service unit + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient, practitioner, nowdate(), save=0 + ) # with no service unit link + self.assertRaises(OverlapError, appointment.save) + + # patient cannot have overlapping appointments with other practitioners + appointment = create_appointment( + patient, practitioner_1, nowdate(), service_unit=service_unit, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner_1, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) + + # practitioner cannot have overlapping appointments with other patients + appointment = create_appointment( + patient_1, practitioner, nowdate(), service_unit=service_unit, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient_1, practitioner, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) + + def test_service_unit_capacity(self): + from healthcare.healthcare.doctype.patient_appointment.patient_appointment import ( + MaximumCapacityError, + OverlapError, + ) + + practitioner = create_practitioner() + capacity = 3 + overlap_service_unit_type = create_service_unit_type( + id=10, allow_appointments=1, overlap_appointments=1 + ) + overlap_service_unit = create_service_unit( + id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity + ) + + for i in range(0, capacity): + patient = create_patient(id=i) + create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0 + ) # overlap + self.assertRaises(OverlapError, appointment.save) + + patient = create_patient(id=capacity) + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0 + ) + self.assertRaises(MaximumCapacityError, appointment.save) + + +def create_healthcare_docs(id=0): + patient = create_patient(id) + practitioner = create_practitioner(id) + + return patient, practitioner + + +def create_patient( + id=0, patient_name=None, email=None, mobile=None, customer=None, create_user=False +): + if frappe.db.exists("Patient", {"firstname": f"_Test Patient {str(id)}"}): + patient = frappe.db.get_value("Patient", {"first_name": f"_Test Patient {str(id)}"}, ["name"]) + return patient + + patient = frappe.new_doc("Patient") + patient.first_name = patient_name if patient_name else f"_Test Patient {str(id)}" + patient.sex = "Female" + patient.mobile = mobile + patient.email = email + patient.customer = customer + patient.invite_user = create_user + patient.save(ignore_permissions=True) + + return patient.name + + +def create_medical_department(id=0): + if frappe.db.exists("Medical Department", f"_Test Medical Department {str(id)}"): + return f"_Test Medical Department {str(id)}" + + medical_department = frappe.new_doc("Medical Department") + medical_department.department = f"_Test Medical Department {str(id)}" + medical_department.save(ignore_permissions=True) + + return medical_department.name + + +def create_practitioner(id=0, medical_department=None): + if frappe.db.exists( + "Healthcare Practitioner", {"firstname": f"_Test Healthcare Practitioner {str(id)}"} + ): + practitioner = frappe.db.get_value( + "Healthcare Practitioner", {"firstname": f"_Test Healthcare Practitioner {str(id)}"}, ["name"] + ) + return practitioner + + practitioner = frappe.new_doc("Healthcare Practitioner") + practitioner.first_name = f"_Test Healthcare Practitioner {str(id)}" + practitioner.gender = "Female" + practitioner.department = medical_department or create_medical_department(id) + practitioner.op_consulting_charge = 500 + practitioner.inpatient_visit_charge = 500 + practitioner.save(ignore_permissions=True) + + return practitioner.name + + +def create_encounter(appointment): + if appointment: + encounter = frappe.new_doc("Patient Encounter") + encounter.appointment = appointment.name + encounter.patient = appointment.patient + encounter.practitioner = appointment.practitioner + encounter.encounter_date = appointment.appointment_date + encounter.encounter_time = appointment.appointment_time + encounter.company = appointment.company + encounter.save() + encounter.submit() + + return encounter + + +def create_appointment( + patient, + practitioner, + appointment_date, + invoice=0, + procedure_template=0, + service_unit=None, + appointment_type=None, + save=1, + department=None, +): + item = create_healthcare_service_items() + frappe.db.set_value("Healthcare Settings", None, "inpatient_visit_charge_item", item) + frappe.db.set_value("Healthcare Settings", None, "op_consulting_charge_item", item) + appointment = frappe.new_doc("Patient Appointment") + appointment.patient = patient + appointment.practitioner = practitioner + appointment.department = department or "_Test Medical Department" + appointment.appointment_date = appointment_date + appointment.company = "_Test Company" + appointment.duration = 15 + + if service_unit: + appointment.service_unit = service_unit + if invoice: + appointment.mode_of_payment = "Cash" + if appointment_type: + appointment.appointment_type = appointment_type + if procedure_template: + appointment.procedure_template = create_clinical_procedure_template().get("name") + if save: + appointment.save(ignore_permissions=True) + + return appointment + + +def create_healthcare_service_items(): + if frappe.db.exists("Item", "HLC-SI-001"): + return "HLC-SI-001" + + item = frappe.new_doc("Item") + item.item_code = "HLC-SI-001" + item.item_name = "Consulting Charges" + item.item_group = "Services" + item.is_stock_item = 0 + item.stock_uom = "Nos" + item.save() + + return item.name + + +def create_clinical_procedure_template(): + if frappe.db.exists("Clinical Procedure Template", "Knee Surgery and Rehab"): + return frappe.get_doc("Clinical Procedure Template", "Knee Surgery and Rehab") + + template = frappe.new_doc("Clinical Procedure Template") + template.template = "Knee Surgery and Rehab" + template.item_code = "Knee Surgery and Rehab" + template.item_group = "Services" + template.is_billable = 1 + template.description = "Knee Surgery and Rehab" + template.rate = 50000 + template.save() + + return template + + +def create_appointment_type(args=None): + if not args: + args = frappe.local.form_dict + + name = args.get("name") or "Test Appointment Type wise Charge" + + if frappe.db.exists("Appointment Type", name): + return frappe.get_doc("Appointment Type", name) + + else: + item = create_healthcare_service_items() + items = [ + { + "medical_department": args.get("medical_department") or "_Test Medical Department", + "op_consulting_charge_item": item, + "op_consulting_charge": 200, + } + ] + return frappe.get_doc( + { + "doctype": "Appointment Type", + "appointment_type": args.get("name") or "Test Appointment Type wise Charge", + "default_duration": args.get("default_duration") or 20, + "color": args.get("color") or "#7575ff", + "price_list": args.get("price_list") or frappe.db.get_value("Price List", {"selling": 1}), + "items": args.get("items") or items, + } + ).insert() + + +def create_user(email=None, roles=None): + if not email: + email = "{}@frappe.com".format(frappe.utils.random_string(10)) + user = frappe.db.exists("User", email) + if not user: + user = frappe.get_doc( + { + "doctype": "User", + "email": email, + "first_name": "test_user", + "password": "password", + "roles": roles, + } + ).insert() + return user + + +def create_service_unit_type(id=0, allow_appointments=1, overlap_appointments=0): + if frappe.db.exists("Healthcare Service Unit Type", f"_Test Service Unit Type {str(id)}"): + return f"_Test Service Unit Type {str(id)}" + + service_unit_type = frappe.new_doc("Healthcare Service Unit Type") + service_unit_type.service_unit_type = f"_Test Service Unit Type {str(id)}" + service_unit_type.allow_appointments = allow_appointments + service_unit_type.overlap_appointments = overlap_appointments + service_unit_type.save(ignore_permissions=True) + + return service_unit_type.name + + +def create_service_unit(id=0, service_unit_type=None, service_unit_capacity=0): + if frappe.db.exists("Healthcare Service Unit", f"_Test Service Unit {str(id)}"): + return f"_Test service_unit {str(id)}" + + service_unit = frappe.new_doc("Healthcare Service Unit") + service_unit.is_group = 0 + service_unit.healthcare_service_unit_name = f"_Test Service Unit {str(id)}" + service_unit.service_unit_type = service_unit_type or create_service_unit_type(id) + service_unit.service_unit_capacity = service_unit_capacity + service_unit.save(ignore_permissions=True) + + return service_unit.name diff --git a/healthcare/healthcare/doctype/patient_assessment/__init__.py b/healthcare/healthcare/doctype/patient_assessment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_assessment/patient_assessment.js b/healthcare/healthcare/doctype/patient_assessment/patient_assessment.js new file mode 100644 index 0000000..0e509f5 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment/patient_assessment.js @@ -0,0 +1,88 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Patient Assessment', { + refresh: function(frm) { + if (frm.doc.assessment_template) { + frm.trigger('set_score_range'); + } + + if (!frm.doc.__islocal) { + frm.trigger('show_patient_progress'); + } + }, + + assessment_template: function(frm) { + if (frm.doc.assessment_template) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Patient Assessment Template', + name: frm.doc.assessment_template + }, + callback: function(data) { + frm.doc.assessment_sheet = []; + $.each(data.message.parameters, function(_i, e) { + let entry = frm.add_child('assessment_sheet'); + entry.parameter = e.assessment_parameter; + }); + + frm.set_value('scale_min', data.message.scale_min); + frm.set_value('scale_max', data.message.scale_max); + frm.set_value('assessment_description', data.message.assessment_description); + frm.set_value('total_score', data.message.scale_max * data.message.parameters.length); + frm.trigger('set_score_range'); + refresh_field('assessment_sheet'); + } + }); + } + }, + + set_score_range: function(frm) { + let options = ['']; + for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) { + options.push(i); + } + frm.fields_dict.assessment_sheet.grid.update_docfield_property( + 'score', 'options', options + ); + }, + + calculate_total_score: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + let total_score = 0; + $.each(frm.doc.assessment_sheet || [], function(_i, item) { + if (item.score) { + total_score += parseInt(item.score); + } + }); + + frm.set_value('total_score_obtained', total_score); + }, + + show_patient_progress: function(frm) { + let bars = []; + let message = ''; + let added_min = false; + + let title = __('{0} out of {1}', [frm.doc.total_score_obtained, frm.doc.total_score]); + + bars.push({ + 'title': title, + 'width': (frm.doc.total_score_obtained / frm.doc.total_score * 100) + '%', + 'progress_class': 'progress-bar-success' + }); + if (bars[0].width == '0%') { + bars[0].width = '0.5%'; + added_min = 0.5; + } + message = title; + frm.dashboard.add_progress(__('Status'), bars, message); + }, +}); + +frappe.ui.form.on('Patient Assessment Sheet', { + score: function(frm, cdt, cdn) { + frm.events.calculate_total_score(frm, cdt, cdn); + } +}); diff --git a/healthcare/healthcare/doctype/patient_assessment/patient_assessment.json b/healthcare/healthcare/doctype/patient_assessment/patient_assessment.json new file mode 100644 index 0000000..eb0021f --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment/patient_assessment.json @@ -0,0 +1,181 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-04-19 22:45:12.356209", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "therapy_session", + "patient", + "assessment_template", + "column_break_4", + "company", + "healthcare_practitioner", + "assessment_datetime", + "assessment_description", + "section_break_7", + "assessment_sheet", + "section_break_9", + "total_score_obtained", + "column_break_11", + "total_score", + "scale_min", + "scale_max", + "amended_from" + ], + "fields": [ + { + "fetch_from": "therapy_session.patient", + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fieldname": "assessment_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Template", + "options": "Patient Assessment Template", + "reqd": 1 + }, + { + "fieldname": "therapy_session", + "fieldtype": "Link", + "label": "Therapy Session", + "options": "Therapy Session" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fetch_from": "therapy_session.practitioner", + "fieldname": "healthcare_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "assessment_datetime", + "fieldtype": "Datetime", + "label": "Assessment Datetime", + "reqd": 1 + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "assessment_sheet", + "fieldtype": "Table", + "label": "Assessment Sheet", + "options": "Patient Assessment Sheet" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_score", + "fieldtype": "Int", + "label": "Total Score", + "read_only": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_score_obtained", + "fieldtype": "Int", + "label": "Total Score Obtained", + "read_only": 1 + }, + { + "fieldname": "scale_min", + "fieldtype": "Int", + "hidden": 1, + "label": "Scale Min", + "read_only": 1 + }, + { + "fieldname": "scale_max", + "fieldtype": "Int", + "hidden": 1, + "label": "Scale Max", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "HLC-PA-.YYYY.-" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Patient Assessment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "assessment_description", + "fieldtype": "Small Text", + "label": "Assessment Description" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-06-25 00:25:13.208400", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_assessment/patient_assessment.py b/healthcare/healthcare/doctype/patient_assessment/patient_assessment.py new file mode 100644 index 0000000..581b400 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment/patient_assessment.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc + + +class PatientAssessment(Document): + def validate(self): + self.set_total_score() + + def set_total_score(self): + total_score = 0 + for entry in self.assessment_sheet: + total_score += int(entry.score) + self.total_score_obtained = total_score + + +@frappe.whitelist() +def create_patient_assessment(source_name, target_doc=None): + doc = get_mapped_doc( + "Therapy Session", + source_name, + { + "Therapy Session": { + "doctype": "Patient Assessment", + "field_map": [ + ["therapy_session", "name"], + ["patient", "patient"], + ["practitioner", "practitioner"], + ], + } + }, + target_doc, + ) + + return doc diff --git a/healthcare/healthcare/doctype/patient_assessment/test_patient_assessment.py b/healthcare/healthcare/doctype/patient_assessment/test_patient_assessment.py new file mode 100644 index 0000000..5cf10d0 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment/test_patient_assessment.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPatientAssessment(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/patient_assessment_detail/__init__.py b/healthcare/healthcare/doctype/patient_assessment_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json b/healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json new file mode 100644 index 0000000..179f441 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-04-19 19:33:00.115395", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_parameter" + ], + "fields": [ + { + "fieldname": "assessment_parameter", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Parameter", + "options": "Patient Assessment Parameter", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-19 19:33:00.115395", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py b/healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py new file mode 100644 index 0000000..4c0bec7 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientAssessmentDetail(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_assessment_parameter/__init__.py b/healthcare/healthcare/doctype/patient_assessment_parameter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js b/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js new file mode 100644 index 0000000..049a347 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Patient Assessment Parameter', { + // refresh: function(frm) { + + // } +}); diff --git a/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json b/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json new file mode 100644 index 0000000..098bdef --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "autoname": "field:assessment_parameter", + "creation": "2020-04-15 14:34:46.551042", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_parameter" + ], + "fields": [ + { + "fieldname": "assessment_parameter", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Assessment Parameter", + "reqd": 1, + "unique": 1 + } + ], + "links": [], + "modified": "2020-04-20 09:22:19.135196", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Parameter", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py b/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py new file mode 100644 index 0000000..3112fb4 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientAssessmentParameter(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py b/healthcare/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py new file mode 100644 index 0000000..034b874 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPatientAssessmentParameter(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/patient_assessment_sheet/__init__.py b/healthcare/healthcare/doctype/patient_assessment_sheet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json b/healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json new file mode 100644 index 0000000..64e4aef --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "creation": "2020-04-19 23:07:02.220244", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parameter", + "score", + "time", + "column_break_4", + "comments" + ], + "fields": [ + { + "fieldname": "parameter", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parameter", + "options": "Patient Assessment Parameter", + "reqd": 1 + }, + { + "fieldname": "score", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Score", + "reqd": 1 + }, + { + "fieldname": "time", + "fieldtype": "Time", + "label": "Time" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "comments", + "fieldtype": "Small Text", + "label": "Comments" + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-20 09:56:28.746619", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Sheet", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py b/healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py new file mode 100644 index 0000000..de52ac2 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientAssessmentSheet(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_assessment_template/__init__.py b/healthcare/healthcare/doctype/patient_assessment_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.js b/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.js new file mode 100644 index 0000000..072bd9a --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Patient Assessment Template', { + // refresh: function(frm) { + + // } +}); diff --git a/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.json b/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.json new file mode 100644 index 0000000..de006b1 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.json @@ -0,0 +1,109 @@ +{ + "actions": [], + "autoname": "field:assessment_name", + "creation": "2020-04-19 19:33:13.204707", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_name", + "section_break_2", + "parameters", + "assessment_scale_details_section", + "scale_min", + "scale_max", + "column_break_8", + "assessment_description" + ], + "fields": [ + { + "fieldname": "parameters", + "fieldtype": "Table", + "label": "Parameters", + "options": "Patient Assessment Detail" + }, + { + "fieldname": "assessment_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Assessment Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Assessment Parameters" + }, + { + "fieldname": "assessment_scale_details_section", + "fieldtype": "Section Break", + "label": "Assessment Scale" + }, + { + "fieldname": "scale_min", + "fieldtype": "Int", + "label": "Scale Minimum" + }, + { + "fieldname": "scale_max", + "fieldtype": "Int", + "label": "Scale Maximum" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "assessment_description", + "fieldtype": "Small Text", + "label": "Assessment Description" + } + ], + "links": [], + "modified": "2020-04-21 13:14:19.075167", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Template", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.py b/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.py new file mode 100644 index 0000000..237da9f --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_template/patient_assessment_template.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientAssessmentTemplate(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py b/healthcare/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py new file mode 100644 index 0000000..d064dc7 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPatientAssessmentTemplate(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/patient_encounter/__init__.py b/healthcare/healthcare/doctype/patient_encounter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_encounter/patient_encounter.js b/healthcare/healthcare/doctype/patient_encounter/patient_encounter.js new file mode 100644 index 0000000..2fbaea0 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter/patient_encounter.js @@ -0,0 +1,503 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Patient Encounter', { + onload: function(frm) { + if (!frm.doc.__islocal && frm.doc.docstatus === 1 && + frm.doc.inpatient_status == 'Admission Scheduled') { + frappe.db.get_value('Inpatient Record', frm.doc.inpatient_record, + ['admission_encounter', 'status']).then(r => { + if (r.message) { + if (r.message.admission_encounter == frm.doc.name && + r.message.status == 'Admission Scheduled') { + frm.add_custom_button(__('Cancel Admission'), function() { + cancel_ip_order(frm); + }); + } + if (r.message.status == 'Admitted') { + frm.add_custom_button(__('Schedule Discharge'), function() { + schedule_discharge(frm); + }); + } + } + }) + } + }, + + setup: function(frm) { + frm.get_field('therapies').grid.editable_fields = [ + {fieldname: 'therapy_type', columns: 8}, + {fieldname: 'no_of_sessions', columns: 2} + ]; + frm.get_field('drug_prescription').grid.editable_fields = [ + {fieldname: 'drug_code', columns: 2}, + {fieldname: 'drug_name', columns: 2}, + {fieldname: 'dosage', columns: 2}, + {fieldname: 'period', columns: 2}, + {fieldname: 'dosage_form', columns: 2} + ]; + frm.get_field('lab_test_prescription').grid.editable_fields = [ + {fieldname: 'lab_test_code', columns: 2}, + {fieldname: 'lab_test_name', columns: 4}, + {fieldname: 'lab_test_comment', columns: 4} + ]; + }, + + refresh: function(frm) { + refresh_field('drug_prescription'); + refresh_field('lab_test_prescription'); + + if (!frm.doc.__islocal) { + if (frm.doc.docstatus === 1) { + if(!['Discharge Scheduled', 'Admission Scheduled', 'Admitted'].includes(frm.doc.inpatient_status)) { + frm.add_custom_button(__('Schedule Admission'), function() { + schedule_inpatient(frm); + }); + } + } + + frm.add_custom_button(__('Patient History'), function() { + if (frm.doc.patient) { + frappe.route_options = {'patient': frm.doc.patient}; + frappe.set_route('patient_history'); + } else { + frappe.msgprint(__('Please select Patient')); + } + },__('View')); + + frm.add_custom_button(__('Vital Signs'), function() { + create_vital_signs(frm); + },__('Create')); + + frm.add_custom_button(__('Medical Record'), function() { + create_medical_record(frm); + },__('Create')); + + frm.add_custom_button(__('Clinical Procedure'), function() { + create_procedure(frm); + },__('Create')); + + if (frm.doc.drug_prescription && frm.doc.inpatient_record && frm.doc.inpatient_status === "Admitted") { + frm.add_custom_button(__('Inpatient Medication Order'), function() { + frappe.model.open_mapped_doc({ + method: 'healthcare.healthcare.doctype.patient_encounter.patient_encounter.make_ip_medication_order', + frm: frm + }); + },__('Create')); + } + + frm.add_custom_button(__('Nursing Tasks'), function() { + create_nursing_tasks(frm); + },__('Create')); + } + + frm.set_query('patient', function() { + return { + filters: {'status': 'Active'} + }; + }); + + frm.set_query('drug_code', 'drug_prescription', function() { + return { + filters: { + is_stock_item: 1 + } + }; + }); + + frm.set_query('lab_test_code', 'lab_test_prescription', function() { + return { + filters: { + is_billable: 1 + } + }; + }); + + frm.set_query('appointment', function() { + return { + filters: { + // Scheduled filter for demo ... + status:['in',['Open','Scheduled']] + } + }; + }); + + frm.set_df_property('patient', 'read_only', frm.doc.appointment ? 1 : 0); + }, + + appointment: function(frm) { + frm.events.set_appointment_fields(frm); + }, + + patient: function(frm) { + frm.events.set_patient_info(frm); + }, + + practitioner: function(frm) { + if (!frm.doc.practitioner) { + frm.set_value('practitioner_name', ''); + } + }, + set_appointment_fields: function(frm) { + if (frm.doc.appointment) { + frappe.call({ + method: 'frappe.client.get', + args: { + doctype: 'Patient Appointment', + name: frm.doc.appointment + }, + callback: function(data) { + let values = { + 'patient':data.message.patient, + 'type': data.message.appointment_type, + 'practitioner': data.message.practitioner, + 'invoiced': data.message.invoiced, + 'company': data.message.company + }; + frm.set_value(values); + frm.set_df_property('patient', 'read_only', 1); + } + }); + } + else { + let values = { + 'patient': '', + 'patient_name': '', + 'type': '', + 'practitioner': '', + 'invoiced': 0, + 'patient_sex': '', + 'patient_age': '', + 'inpatient_record': '', + 'inpatient_status': '' + }; + frm.set_value(values); + frm.set_df_property('patient', 'read_only', 0); + } + }, + + set_patient_info: function(frm) { + if (frm.doc.patient) { + frappe.call({ + method: 'healthcare.healthcare.doctype.patient.patient.get_patient_detail', + args: { + patient: frm.doc.patient + }, + callback: function(data) { + let age = ''; + if (data.message.dob) { + age = calculate_age(data.message.dob); + } + let values = { + 'patient_age': age, + 'patient_name':data.message.patient_name, + 'patient_sex': data.message.sex, + 'inpatient_record': data.message.inpatient_record, + 'inpatient_status': data.message.inpatient_status + }; + frm.set_value(values); + } + }); + } else { + let values = { + 'patient_age': '', + 'patient_name':'', + 'patient_sex': '', + 'inpatient_record': '', + 'inpatient_status': '' + }; + frm.set_value(values); + } + }, + + get_applicable_treatment_plans: function(frm) { + frappe.call({ + method: 'get_applicable_treatment_plans', + doc: frm.doc, + args: {'encounter': frm.doc}, + freeze: true, + freeze_message: __('Fetching Treatment Plans'), + callback: function() { + new frappe.ui.form.MultiSelectDialog({ + doctype: "Treatment Plan Template", + target: this.cur_frm, + setters: { + medical_department: "", + }, + action(selections) { + frappe.call({ + method: 'set_treatment_plans', + doc: frm.doc, + args: selections, + }).then(() => { + frm.refresh_field('drug_prescription'); + frm.refresh_field('procedure_prescription'); + frm.refresh_field('lab_test_prescription'); + frm.refresh_field('therapies'); + }); + cur_dialog.hide(); + } + }); + + + } + }); + }, + +}); + +var schedule_inpatient = function(frm) { + var dialog = new frappe.ui.Dialog({ + title: 'Patient Admission', + fields: [ + {fieldtype: 'Link', label: 'Medical Department', fieldname: 'medical_department', options: 'Medical Department', reqd: 1}, + {fieldtype: 'Link', label: 'Healthcare Practitioner (Primary)', fieldname: 'primary_practitioner', options: 'Healthcare Practitioner', reqd: 1}, + {fieldtype: 'Link', label: 'Healthcare Practitioner (Secondary)', fieldname: 'secondary_practitioner', options: 'Healthcare Practitioner'}, + {fieldtype: 'Link', label: 'Nursing Checklist Template', fieldname: 'admission_nursing_checklist_template', options: 'Nursing Checklist Template'}, + {fieldtype: 'Column Break'}, + {fieldtype: 'Date', label: 'Admission Ordered For', fieldname: 'admission_ordered_for', default: 'Today'}, + {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, + {fieldtype: 'Int', label: 'Expected Length of Stay', fieldname: 'expected_length_of_stay'}, + {fieldtype: 'Section Break'}, + {fieldtype: 'Long Text', label: 'Admission Instructions', fieldname: 'admission_instruction'} + ], + primary_action_label: __('Order Admission'), + primary_action : function() { + var args = { + patient: frm.doc.patient, + admission_encounter: frm.doc.name, + referring_practitioner: frm.doc.practitioner, + company: frm.doc.company, + medical_department: dialog.get_value('medical_department'), + primary_practitioner: dialog.get_value('primary_practitioner'), + secondary_practitioner: dialog.get_value('secondary_practitioner'), + admission_ordered_for: dialog.get_value('admission_ordered_for'), + admission_service_unit_type: dialog.get_value('service_unit_type'), + expected_length_of_stay: dialog.get_value('expected_length_of_stay'), + admission_instruction: dialog.get_value('admission_instruction'), + admission_nursing_checklist_template: dialog.get_value('admission_nursing_checklist_template') + } + frappe.call({ + method: 'healthcare.healthcare.doctype.inpatient_record.inpatient_record.schedule_inpatient', + args: { + args: args + }, + callback: function(data) { + if (!data.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('Scheduling Patient Admission') + }); + frm.refresh_fields(); + dialog.hide(); + } + }); + + dialog.set_values({ + 'medical_department': frm.doc.medical_department, + 'primary_practitioner': frm.doc.practitioner, + }); + + dialog.fields_dict['service_unit_type'].get_query = function() { + return { + filters: { + 'inpatient_occupancy': 1, + 'allow_appointments': 0 + } + }; + }; + + dialog.show(); + dialog.$wrapper.find('.modal-dialog').css('width', '800px'); +}; + +var schedule_discharge = function(frm) { + var dialog = new frappe.ui.Dialog ({ + title: 'Inpatient Discharge', + fields: [ + {fieldtype: 'Date', label: 'Discharge Ordered Date', fieldname: 'discharge_ordered_date', default: 'Today', read_only: 1}, + {fieldtype: 'Date', label: 'Followup Date', fieldname: 'followup_date'}, + {fieldtype: 'Link', label: 'Nursing Checklist Template', options: 'Nursing Checklist Template', fieldname: 'discharge_nursing_checklist_template'}, + {fieldtype: 'Column Break'}, + {fieldtype: 'Small Text', label: 'Discharge Instructions', fieldname: 'discharge_instructions'}, + {fieldtype: 'Section Break', label:'Discharge Summary'}, + {fieldtype: 'Long Text', label: 'Discharge Note', fieldname: 'discharge_note'} + ], + primary_action_label: __('Order Discharge'), + primary_action : function() { + var args = { + patient: frm.doc.patient, + discharge_encounter: frm.doc.name, + discharge_practitioner: frm.doc.practitioner, + discharge_ordered_date: dialog.get_value('discharge_ordered_date'), + followup_date: dialog.get_value('followup_date'), + discharge_instructions: dialog.get_value('discharge_instructions'), + discharge_note: dialog.get_value('discharge_note'), + discharge_nursing_checklist_template: dialog.get_value('discharge_nursing_checklist_template') + } + frappe.call ({ + method: 'healthcare.healthcare.doctype.inpatient_record.inpatient_record.schedule_discharge', + args: {args}, + callback: function(data) { + if(!data.exc){ + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: 'Scheduling Inpatient Discharge' + }); + frm.refresh_fields(); + dialog.hide(); + } + }); + + dialog.show(); + dialog.$wrapper.find('.modal-dialog').css('width', '800px'); +}; + +let create_medical_record = function(frm) { + if (!frm.doc.patient) { + frappe.throw(__('Please select patient')); + } + frappe.route_options = { + 'patient': frm.doc.patient, + 'status': 'Open', + 'reference_doctype': 'Patient Medical Record', + 'reference_owner': frm.doc.owner + }; + frappe.new_doc('Patient Medical Record'); +}; + +let create_vital_signs = function(frm) { + if (!frm.doc.patient) { + frappe.throw(__('Please select patient')); + } + frappe.route_options = { + 'patient': frm.doc.patient, + 'encounter': frm.doc.name, + 'company': frm.doc.company + }; + frappe.new_doc('Vital Signs'); +}; + +let create_procedure = function(frm) { + if (!frm.doc.patient) { + frappe.throw(__('Please select patient')); + } + frappe.route_options = { + 'patient': frm.doc.patient, + 'medical_department': frm.doc.medical_department, + 'company': frm.doc.company + }; + frappe.new_doc('Clinical Procedure'); +}; + +let create_nursing_tasks = function(frm) { + const d = new frappe.ui.Dialog({ + + title: __('Create Nursing Tasks'), + + fields: [ + { + label: __('Nursing Checklist Template'), + fieldtype: 'Link', + options: 'Nursing Checklist Template', + fieldname: 'template', + reqd: 1, + }, + { + label: __('Start Time'), + fieldtype: 'Datetime', + fieldname: 'start_time', + default: frappe.datetime.now_datetime(), + reqd: 1, + }, + ], + + primary_action_label: __('Create Nursing Tasks'), + + primary_action: () => { + + let values = d.get_values(); + frappe.call({ + method: 'healthcare.healthcare.doctype.nursing_task.nursing_task.create_nursing_tasks_from_template', + args: { + 'template': values.template, + 'doc': frm.doc, + 'start_time': values.start_time + }, + callback: (r) => { + if (r && !r.exc) { + frappe.show_alert({ + message: __('Nursing Tasks Created'), + indicator: 'success' + }); + } + } + }); + + d.hide(); + } + }); + + d.show(); +}; + +frappe.ui.form.on('Drug Prescription', { + dosage: function(frm, cdt, cdn){ + frappe.model.set_value(cdt, cdn, 'update_schedule', 1); + let child = locals[cdt][cdn]; + if (child.dosage) { + frappe.model.set_value(cdt, cdn, 'interval_uom', 'Day'); + frappe.model.set_value(cdt, cdn, 'interval', 1); + } + }, + period: function(frm, cdt, cdn) { + frappe.model.set_value(cdt, cdn, 'update_schedule', 1); + }, + interval_uom: function(frm, cdt, cdn) { + frappe.model.set_value(cdt, cdn, 'update_schedule', 1); + let child = locals[cdt][cdn]; + if (child.interval_uom == 'Hour') { + frappe.model.set_value(cdt, cdn, 'dosage', null); + } + } +}); + +let calculate_age = function(birth) { + let ageMS = Date.parse(Date()) - Date.parse(birth); + let age = new Date(); + age.setTime(ageMS); + let years = age.getFullYear() - 1970; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; +}; + +let cancel_ip_order = function(frm) { + frappe.prompt([ + { + fieldname: 'reason_for_cancellation', + label: __('Reason for Cancellation'), + fieldtype: 'Small Text', + reqd: 1 + } + ], + function(data) { + frappe.call({ + method: 'healthcare.healthcare.doctype.inpatient_record.inpatient_record.set_ip_order_cancelled', + async: false, + freeze: true, + args: { + inpatient_record: frm.doc.inpatient_record, + reason: data.reason_for_cancellation, + encounter: frm.doc.name + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + } + }); + }, __('Reason for Cancellation'), __('Submit')); +} diff --git a/healthcare/healthcare/doctype/patient_encounter/patient_encounter.json b/healthcare/healthcare/doctype/patient_encounter/patient_encounter.json new file mode 100644 index 0000000..994597d --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter/patient_encounter.json @@ -0,0 +1,368 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2016-04-21 10:53:44.637684", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "title", + "appointment", + "appointment_type", + "patient", + "patient_name", + "patient_sex", + "patient_age", + "inpatient_record", + "inpatient_status", + "column_break_6", + "company", + "encounter_date", + "encounter_time", + "practitioner", + "practitioner_name", + "medical_department", + "invoiced", + "sb_symptoms", + "symptoms", + "symptoms_in_print", + "get_applicable_treatment_plans", + "physical_examination", + "diagnosis", + "diagnosis_in_print", + "codification", + "codification_table", + "sb_drug_prescription", + "drug_prescription", + "sb_test_prescription", + "lab_test_prescription", + "sb_procedures", + "procedure_prescription", + "rehabilitation_section", + "therapy_plan", + "therapies", + "section_break_33", + "encounter_comment", + "sb_refs", + "amended_from" + ], + "fields": [ + { + "allow_on_submit": 1, + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "HLC-ENC-.YYYY.-", + "set_only_once": 1 + }, + { + "fieldname": "appointment", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Appointment", + "options": "Patient Appointment", + "search_index": 1, + "set_only_once": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "label": "Age", + "read_only": 1 + }, + { + "fieldname": "patient_sex", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "encounter_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Encounter Date", + "reqd": 1 + }, + { + "fieldname": "encounter_time", + "fieldtype": "Time", + "label": "Encounter Time", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "sb_symptoms", + "fieldtype": "Section Break", + "label": "Encounter Impression" + }, + { + "fieldname": "symptoms", + "fieldtype": "Table MultiSelect", + "ignore_xss_filter": 1, + "label": "Symptoms", + "no_copy": 1, + "options": "Patient Encounter Symptom" + }, + { + "default": "0", + "depends_on": "eval: doc.symptoms != ''", + "fieldname": "symptoms_in_print", + "fieldtype": "Check", + "label": "In print", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "physical_examination", + "fieldtype": "Column Break" + }, + { + "fieldname": "diagnosis", + "fieldtype": "Table MultiSelect", + "ignore_xss_filter": 1, + "label": "Diagnosis", + "no_copy": 1, + "options": "Patient Encounter Diagnosis" + }, + { + "default": "1", + "depends_on": "eval: doc.diagnosis != ''", + "fieldname": "diagnosis_in_print", + "fieldtype": "Check", + "label": "In print", + "no_copy": 1, + "print_hide": 1, + "report_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "codification", + "fieldtype": "Section Break", + "label": "Medical Coding" + }, + { + "fieldname": "codification_table", + "fieldtype": "Table", + "label": "Medical Codes", + "options": "Codification Table" + }, + { + "fieldname": "sb_drug_prescription", + "fieldtype": "Section Break", + "label": "Medications" + }, + { + "fieldname": "drug_prescription", + "fieldtype": "Table", + "label": "Drug Prescription", + "options": "Drug Prescription" + }, + { + "fieldname": "sb_test_prescription", + "fieldtype": "Section Break", + "label": "Investigations" + }, + { + "fieldname": "lab_test_prescription", + "fieldtype": "Table", + "label": "Lab Tests", + "options": "Lab Prescription" + }, + { + "fieldname": "sb_procedures", + "fieldtype": "Section Break", + "label": "Procedures" + }, + { + "fieldname": "procedure_prescription", + "fieldtype": "Table", + "label": "Clinical Procedures", + "no_copy": 1, + "options": "Procedure Prescription" + }, + { + "fieldname": "encounter_comment", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Review Details", + "no_copy": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Patient Encounter", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "rehabilitation_section", + "fieldtype": "Section Break", + "label": "Rehabilitation" + }, + { + "fieldname": "therapies", + "fieldtype": "Table", + "label": "Therapies", + "options": "Therapy Plan Detail" + }, + { + "fieldname": "section_break_33", + "fieldtype": "Section Break" + }, + { + "fieldname": "therapy_plan", + "fieldtype": "Link", + "hidden": 1, + "label": "Therapy Plan", + "options": "Therapy Plan", + "read_only": 1 + }, + { + "fieldname": "appointment_type", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Appointment Type", + "no_copy": 1, + "options": "Appointment Type", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fetch_from": "practitioner.department", + "fieldname": "medical_department", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Medical Department", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "inpatient_status", + "fieldtype": "Data", + "label": "Inpatient Status", + "read_only": 1 + }, + { + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval:doc.patient", + "fieldname": "get_applicable_treatment_plans", + "fieldtype": "Button", + "label": "Get Applicable Treatment Plans" + } + ], + "is_submittable": 1, + "links": [], + "modified": "2021-07-27 11:39:12.347704", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Encounter", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient, practitioner, medical_department, encounter_date, encounter_time", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1, + "track_seen": 1 +} diff --git a/healthcare/healthcare/doctype/patient_encounter/patient_encounter.py b/healthcare/healthcare/doctype/patient_encounter/patient_encounter.py new file mode 100644 index 0000000..22cb7ee --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter/patient_encounter.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import add_days, getdate + + +class PatientEncounter(Document): + def validate(self): + self.set_title() + + def on_update(self): + if self.appointment: + frappe.db.set_value("Patient Appointment", self.appointment, "status", "Closed") + + def on_submit(self): + if self.therapies: + create_therapy_plan(self) + + def on_cancel(self): + if self.appointment: + frappe.db.set_value("Patient Appointment", self.appointment, "status", "Open") + + if self.inpatient_record and self.drug_prescription: + delete_ip_medication_order(self) + + def set_title(self): + self.title = _("{0} with {1}").format( + self.patient_name or self.patient, self.practitioner_name or self.practitioner + )[:100] + + @frappe.whitelist() + @staticmethod + def get_applicable_treatment_plans(encounter): + patient = frappe.get_doc("Patient", encounter["patient"]) + + plan_filters = {} + plan_filters["name"] = ["in", []] + + age = patient.age + if age: + plan_filters["patient_age_from"] = ["<=", age.years] + plan_filters["patient_age_to"] = [">=", age.years] + + gender = patient.sex + if gender: + plan_filters["gender"] = ["in", [gender, None]] + + diagnosis = encounter.get("diagnosis") + if diagnosis: + diagnosis = [_diagnosis["diagnosis"] for _diagnosis in encounter["diagnosis"]] + filters = [ + ["diagnosis", "in", diagnosis], + ["parenttype", "=", "Treatment Plan Template"], + ] + diagnosis = frappe.get_list("Patient Encounter Diagnosis", filters=filters, fields="*") + plan_names = [_diagnosis["parent"] for _diagnosis in diagnosis] + plan_filters["name"][1].extend(plan_names) + + symptoms = encounter.get("symptoms") + if symptoms: + symptoms = [symptom["complaint"] for symptom in encounter["symptoms"]] + filters = [ + ["complaint", "in", symptoms], + ["parenttype", "=", "Treatment Plan Template"], + ] + symptoms = frappe.get_list("Patient Encounter Symptom", filters=filters, fields="*") + plan_names = [symptom["parent"] for symptom in symptoms] + plan_filters["name"][1].extend(plan_names) + + if not plan_filters["name"][1]: + plan_filters.pop("name") + + plans = frappe.get_list("Treatment Plan Template", fields="*", filters=plan_filters) + + return plans + + @frappe.whitelist() + def set_treatment_plans(self, treatment_plans=None): + for treatment_plan in treatment_plans: + self.set_treatment_plan(treatment_plan) + + def set_treatment_plan(self, plan): + plan_items = frappe.get_list( + "Treatment Plan Template Item", filters={"parent": plan}, fields="*" + ) + for plan_item in plan_items: + self.set_treatment_plan_item(plan_item) + + drugs = frappe.get_list("Drug Prescription", filters={"parent": plan}, fields="*") + for drug in drugs: + self.append("drug_prescription", drug) + + self.save() + + def set_treatment_plan_item(self, plan_item): + if plan_item.type == "Clinical Procedure Template": + self.append("procedure_prescription", {"procedure": plan_item.template}) + + if plan_item.type == "Lab Test Template": + self.append("lab_test_prescription", {"lab_test_code": plan_item.template}) + + if plan_item.type == "Therapy Type": + self.append("therapies", {"therapy_type": plan_item.template}) + + +@frappe.whitelist() +def make_ip_medication_order(source_name, target_doc=None): + def set_missing_values(source, target): + target.start_date = source.encounter_date + for entry in source.drug_prescription: + if entry.drug_code: + dosage = frappe.get_doc("Prescription Dosage", entry.dosage) + dates = get_prescription_dates(entry.period, target.start_date) + for date in dates: + for dose in dosage.dosage_strength: + order = target.append("medication_orders") + order.drug = entry.drug_code + order.drug_name = entry.drug_name + order.dosage = dose.strength + order.instructions = entry.comment + order.dosage_form = entry.dosage_form + order.date = date + order.time = dose.strength_time + target.end_date = dates[-1] + + doc = get_mapped_doc( + "Patient Encounter", + source_name, + { + "Patient Encounter": { + "doctype": "Inpatient Medication Order", + "field_map": { + "name": "patient_encounter", + "patient": "patient", + "patient_name": "patient_name", + "patient_age": "patient_age", + "inpatient_record": "inpatient_record", + "practitioner": "practitioner", + "start_date": "encounter_date", + }, + } + }, + target_doc, + set_missing_values, + ) + + return doc + + +def get_prescription_dates(period, start_date): + prescription_duration = frappe.get_doc("Prescription Duration", period) + days = prescription_duration.get_days() + dates = [start_date] + for i in range(1, days): + dates.append(add_days(getdate(start_date), i)) + return dates + + +def create_therapy_plan(encounter): + if len(encounter.therapies): + doc = frappe.new_doc("Therapy Plan") + doc.patient = encounter.patient + doc.start_date = encounter.encounter_date + for entry in encounter.therapies: + doc.append( + "therapy_plan_details", + {"therapy_type": entry.therapy_type, "no_of_sessions": entry.no_of_sessions}, + ) + doc.save(ignore_permissions=True) + if doc.get("name"): + encounter.db_set("therapy_plan", doc.name) + frappe.msgprint( + _("Therapy Plan {0} created successfully.").format(frappe.bold(doc.name)), alert=True + ) + + +def delete_ip_medication_order(encounter): + record = frappe.db.exists("Inpatient Medication Order", {"patient_encounter": encounter.name}) + if record: + frappe.delete_doc("Inpatient Medication Order", record, force=1) diff --git a/healthcare/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py b/healthcare/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py new file mode 100644 index 0000000..8831e3a --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py @@ -0,0 +1,17 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "encounter", + "non_standard_fieldnames": { + "Patient Medical Record": "reference_name", + "Inpatient Medication Order": "patient_encounter", + "Nursing Task": "reference_name", + }, + "transactions": [ + {"label": _("Records"), "items": ["Vital Signs", "Patient Medical Record"]}, + {"label": _("Orders"), "items": ["Inpatient Medication Order", "Nursing Task"]}, + ], + "disable_create_buttons": ["Inpatient Medication Order"], + } diff --git a/healthcare/healthcare/doctype/patient_encounter/patient_encounter_list.js b/healthcare/healthcare/doctype/patient_encounter/patient_encounter_list.js new file mode 100644 index 0000000..d8f63bd --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter/patient_encounter_list.js @@ -0,0 +1,6 @@ +/* +(c) ESS 2015-16 +*/ +frappe.listview_settings['Patient Encounter'] = { + filters:[["docstatus","!=","2"]] +}; diff --git a/healthcare/healthcare/doctype/patient_encounter/test_patient_encounter.py b/healthcare/healthcare/doctype/patient_encounter/test_patient_encounter.py new file mode 100644 index 0000000..dbd0eba --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter/test_patient_encounter.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# See license.txt + + +import frappe +from frappe.tests.utils import FrappeTestCase + +from healthcare.healthcare.doctype.patient_encounter.patient_encounter import PatientEncounter + + +class TestPatientEncounter(FrappeTestCase): + def setUp(self): + try: + gender_m = frappe.get_doc({"doctype": "Gender", "gender": "MALE"}).insert() + gender_f = frappe.get_doc({"doctype": "Gender", "gender": "FEMALE"}).insert() + except frappe.exceptions.DuplicateEntryError: + gender_m = frappe.get_doc({"doctype": "Gender", "gender": "MALE"}) + gender_f = frappe.get_doc({"doctype": "Gender", "gender": "FEMALE"}) + + self.patient_male = frappe.get_doc( + { + "doctype": "Patient", + "first_name": "John", + "sex": gender_m.gender, + } + ).insert() + self.patient_female = frappe.get_doc( + { + "doctype": "Patient", + "first_name": "Curie", + "sex": gender_f.gender, + } + ).insert() + self.practitioner = frappe.get_doc( + { + "doctype": "Healthcare Practitioner", + "first_name": "Doc", + "sex": "MALE", + } + ).insert() + try: + self.care_plan_male = frappe.get_doc( + { + "doctype": "Treatment Plan Template", + "template_name": "test plan - m", + "gender": gender_m.gender, + } + ).insert() + self.care_plan_female = frappe.get_doc( + { + "doctype": "Treatment Plan Template", + "template_name": "test plan - f", + "gender": gender_f.gender, + } + ).insert() + except frappe.exceptions.DuplicateEntryError: + self.care_plan_male = frappe.get_doc( + { + "doctype": "Treatment Plan Template", + "template_name": "test plan - m", + "gender": gender_m.gender, + } + ) + self.care_plan_female = frappe.get_doc( + { + "doctype": "Treatment Plan Template", + "template_name": "test plan - f", + "gender": gender_f.gender, + } + ) + + def test_treatment_plan_template_filter(self): + encounter = frappe.get_doc( + { + "doctype": "Patient Encounter", + "patient": self.patient_male.name, + "practitioner": self.practitioner.name, + } + ).insert() + plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict()) + self.assertEqual(plans[0]["name"], self.care_plan_male.template_name) + + encounter = frappe.get_doc( + { + "doctype": "Patient Encounter", + "patient": self.patient_female.name, + "practitioner": self.practitioner.name, + } + ).insert() + plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict()) + self.assertEqual(plans[0]["name"], self.care_plan_female.template_name) diff --git a/healthcare/healthcare/doctype/patient_encounter_diagnosis/__init__.py b/healthcare/healthcare/doctype/patient_encounter_diagnosis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json b/healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json new file mode 100644 index 0000000..00ca309 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json @@ -0,0 +1,33 @@ +{ + "actions": [], + "beta": 1, + "creation": "2020-02-26 16:48:16.835105", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "diagnosis" + ], + "fields": [ + { + "fieldname": "diagnosis", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Diagnosis", + "options": "Diagnosis", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-02-26 16:58:16.480583", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Encounter Diagnosis", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py b/healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py new file mode 100644 index 0000000..e2c4454 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientEncounterDiagnosis(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_encounter_symptom/__init__.py b/healthcare/healthcare/doctype/patient_encounter_symptom/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json b/healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json new file mode 100644 index 0000000..bc92145 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json @@ -0,0 +1,33 @@ +{ + "actions": [], + "beta": 1, + "creation": "2020-02-26 16:47:00.525657", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "complaint" + ], + "fields": [ + { + "fieldname": "complaint", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Complaint", + "options": "Complaint", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-02-26 16:57:37.997481", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Encounter Symptom", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py b/healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py new file mode 100644 index 0000000..9e52d50 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientEncounterSymptom(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_history_custom_document_type/__init__.py b/healthcare/healthcare/doctype/patient_history_custom_document_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json b/healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json new file mode 100644 index 0000000..3025c7b --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "creation": "2020-11-25 13:40:23.054469", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "date_fieldname", + "add_edit_fields", + "selected_fields" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "selected_fields", + "fieldtype": "Code", + "label": "Selected Fields", + "read_only": 1 + }, + { + "fieldname": "add_edit_fields", + "fieldtype": "Button", + "in_list_view": 1, + "label": "Add / Edit Fields" + }, + { + "fieldname": "date_fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Date Fieldname", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-11-30 13:54:37.474671", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient History Custom Document Type", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py b/healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py new file mode 100644 index 0000000..3ed52ee --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientHistoryCustomDocumentType(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_history_settings/__init__.py b/healthcare/healthcare/doctype/patient_history_settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.js b/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.js new file mode 100644 index 0000000..f751933 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -0,0 +1,133 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Patient History Settings', { + refresh: function(frm) { + frm.set_query('document_type', 'custom_doctypes', () => { + return { + filters: { + custom: 1, + is_submittable: 1, + module: 'Healthcare', + } + }; + }); + }, + + field_selector: function(frm, doc, standard=1) { + let document_fields = []; + if (doc.selected_fields) + document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname); + + frm.call({ + method: 'get_doctype_fields', + doc: frm.doc, + args: { + document_type: doc.document_type, + fields: document_fields + }, + freeze: true, + callback: function(r) { + if (r.message) { + let doctype = 'Patient History Custom Document Type'; + if (standard) + doctype = 'Patient History Standard Document Type'; + + frm.events.show_field_selector_dialog(frm, doc, doctype, r.message); + } + } + }); + }, + + show_field_selector_dialog: function(frm, doc, doctype, doc_fields) { + let d = new frappe.ui.Dialog({ + title: __('{0} Fields', [__(doc.document_type)]), + fields: [ + { + label: __('Select Fields'), + fieldtype: 'MultiCheck', + fieldname: 'fields', + options: doc_fields, + columns: 2 + } + ] + }); + + d.$body.prepend(` + ` + ); + + frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area'); + + d.set_primary_action(__('Save'), () => { + let values = d.get_values().fields; + + let selected_fields = []; + + frappe.model.with_doctype(doc.document_type, function() { + for (let idx in values) { + let value = values[idx]; + + let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0]; + if (field) { + selected_fields.push({ + label: field.label, + fieldname: field.fieldname, + fieldtype: field.fieldtype + }); + } + } + + d.refresh(); + frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); + }); + + d.hide(); + }); + + d.show(); + }, + + get_date_field_for_dt: function(frm, row) { + frm.call({ + method: 'get_date_field_for_dt', + doc: frm.doc, + args: { + document_type: row.document_type + }, + callback: function(data) { + if (data.message) { + frappe.model.set_value('Patient History Custom Document Type', + row.name, 'date_fieldname', data.message); + } + } + }); + } +}); + +frappe.ui.form.on('Patient History Custom Document Type', { + document_type: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.document_type) { + frm.events.get_date_field_for_dt(frm, row); + } + }, + + add_edit_fields: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.document_type) { + frm.events.field_selector(frm, row, 0); + } + } +}); + +frappe.ui.form.on('Patient History Standard Document Type', { + add_edit_fields: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.document_type) { + frm.events.field_selector(frm, row); + } + } +}); diff --git a/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.json b/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.json new file mode 100644 index 0000000..143e2c9 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "creation": "2020-11-25 13:41:37.675518", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "standard_doctypes", + "section_break_2", + "custom_doctypes" + ], + "fields": [ + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "custom_doctypes", + "fieldtype": "Table", + "label": "Custom Document Types", + "options": "Patient History Custom Document Type" + }, + { + "fieldname": "standard_doctypes", + "fieldtype": "Table", + "label": "Standard Document Types", + "options": "Patient History Standard Document Type", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2020-11-25 13:43:38.511771", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient History Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.py b/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.py new file mode 100644 index 0000000..2eeb730 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cint, cstr + +from healthcare.healthcare.page.patient_history.patient_history import get_patient_history_doctypes + + +class PatientHistorySettings(Document): + def validate(self): + self.validate_submittable_doctypes() + self.validate_date_fieldnames() + + def validate_submittable_doctypes(self): + for entry in self.custom_doctypes: + if not cint(frappe.db.get_value("DocType", entry.document_type, "is_submittable")): + msg = _("Row #{0}: Document Type {1} is not submittable.").format( + entry.idx, frappe.bold(entry.document_type) + ) + msg += _("Patient Medical Record can only be created for submittable document types.") + frappe.throw(msg) + + def validate_date_fieldnames(self): + for entry in self.custom_doctypes: + field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname) + if not field: + frappe.throw( + _("Row #{0}: No such Field named {1} found in the Document Type {2}.").format( + entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type) + ) + ) + + if field.fieldtype not in ["Date", "Datetime"]: + frappe.throw( + _("Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.").format( + entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type) + ) + ) + + @frappe.whitelist() + def get_doctype_fields(self, document_type, fields): + multicheck_fields = [] + doc_fields = frappe.get_meta(document_type).fields + + for field in doc_fields: + if ( + field.fieldtype not in frappe.model.no_value_fields + or field.fieldtype in frappe.model.table_fields + and not field.hidden + ): + multicheck_fields.append( + { + "label": field.label, + "value": field.fieldname, + "checked": 1 if field.fieldname in fields else 0, + } + ) + + return multicheck_fields + + @frappe.whitelist() + def get_date_field_for_dt(self, document_type): + meta = frappe.get_meta(document_type) + date_fields = meta.get("fields", {"fieldtype": ["in", ["Date", "Datetime"]]}) + + if date_fields: + return date_fields[0].get("fieldname") + + +def create_medical_record(doc, method=None): + medical_record_required = validate_medical_record_required(doc) + if not medical_record_required: + return + + if frappe.db.exists("Patient Medical Record", {"reference_name": doc.name}): + return + + subject = set_subject_field(doc) + date_field = get_date_field(doc.doctype) + medical_record = frappe.new_doc("Patient Medical Record") + medical_record.patient = doc.patient + medical_record.subject = subject + medical_record.status = "Open" + medical_record.communication_date = doc.get(date_field) + medical_record.reference_doctype = doc.doctype + medical_record.reference_name = doc.name + medical_record.reference_owner = doc.owner + medical_record.save(ignore_permissions=True) + + +def update_medical_record(doc, method=None): + medical_record_required = validate_medical_record_required(doc) + if not medical_record_required: + return + + medical_record_id = frappe.db.exists("Patient Medical Record", {"reference_name": doc.name}) + + if medical_record_id: + subject = set_subject_field(doc) + frappe.db.set_value("Patient Medical Record", medical_record_id[0][0], "subject", subject) + else: + create_medical_record(doc) + + +def delete_medical_record(doc, method=None): + medical_record_required = validate_medical_record_required(doc) + if not medical_record_required: + return + + record = frappe.db.exists("Patient Medical Record", {"reference_name": doc.name}) + if record: + frappe.delete_doc("Patient Medical Record", record, force=1) + + +def set_subject_field(doc): + from frappe.utils.formatters import format_value + + meta = frappe.get_meta(doc.doctype) + subject = "" + patient_history_fields = get_patient_history_fields(doc) + + for entry in patient_history_fields: + fieldname = entry.get("fieldname") + if entry.get("fieldtype") == "Table" and doc.get(fieldname): + formatted_value = get_formatted_value_for_table_field( + doc.get(fieldname), meta.get_field(fieldname) + ) + subject += frappe.bold(_(entry.get("label")) + ":") + "
" + cstr(formatted_value) + "
" + + else: + if doc.get(fieldname): + formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc) + subject += frappe.bold(_(entry.get("label")) + ":") + cstr(formatted_value) + "
" + + return subject + + +def get_date_field(doctype): + dt = get_patient_history_config_dt(doctype) + + return frappe.db.get_value(dt, {"document_type": doctype}, "date_fieldname") + + +def get_patient_history_fields(doc): + dt = get_patient_history_config_dt(doc.doctype) + patient_history_fields = frappe.db.get_value( + dt, {"document_type": doc.doctype}, "selected_fields" + ) + + if patient_history_fields: + return json.loads(patient_history_fields) + + +def get_formatted_value_for_table_field(items, df): + child_meta = frappe.get_meta(df.options) + + table_head = "" + table_row = "" + html = "" + create_head = True + for item in items: + table_row += "" + for cdf in child_meta.fields: + if cdf.in_list_view: + if create_head: + table_head += "" + cdf.label + "" + if item.get(cdf.fieldname): + table_row += "" + str(item.get(cdf.fieldname)) + "" + else: + table_row += "" + create_head = False + table_row += "" + + html += ( + "" + table_head + table_row + "
" + ) + + return html + + +def get_patient_history_config_dt(doctype): + if frappe.db.get_value("DocType", doctype, "custom"): + return "Patient History Custom Document Type" + else: + return "Patient History Standard Document Type" + + +def validate_medical_record_required(doc): + if ( + frappe.flags.in_patch + or frappe.flags.in_install + or frappe.flags.in_setup_wizard + or get_module(doc) != "Healthcare" + ): + return False + + if doc.doctype not in get_patient_history_doctypes(): + return False + + return True + + +def get_module(doc): + module = doc.meta.module + if not module: + module = frappe.db.get_value("DocType", doc.doctype, "module") + + return module diff --git a/healthcare/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/healthcare/healthcare/doctype/patient_history_settings/test_patient_history_settings.py new file mode 100644 index 0000000..164ee36 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_settings/test_patient_history_settings.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +import json +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import getdate, strip_html + +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_patient, +) + + +class TestPatientHistorySettings(FrappeTestCase): + def setUp(self): + dt = create_custom_doctype() + settings = frappe.get_single("Patient History Settings") + settings.append( + "custom_doctypes", + { + "document_type": dt.name, + "date_fieldname": "date", + "selected_fields": json.dumps( + [ + {"label": "Date", "fieldname": "date", "fieldtype": "Date"}, + {"label": "Rating", "fieldname": "rating", "fieldtype": "Rating"}, + {"label": "Feedback", "fieldname": "feedback", "fieldtype": "Small Text"}, + ] + ), + }, + ) + settings.save() + + def test_custom_doctype_medical_record(self): + # tests for medical record creation of standard doctypes in test_patient_medical_record.py + patient = create_patient() + doc = create_doc(patient) + # check for medical record + medical_rec = frappe.db.exists( + "Patient Medical Record", {"status": "Open", "reference_name": doc.name} + ) + self.assertTrue(medical_rec) + + medical_rec = frappe.get_doc("Patient Medical Record", medical_rec) + expected_subject = "Date:{0}Rating:3Feedback:Test Patient History Settings".format( + frappe.utils.format_date(getdate()) + ) + self.assertEqual(strip_html(medical_rec.subject), expected_subject) + self.assertEqual(medical_rec.patient, patient) + self.assertEqual(medical_rec.communication_date, getdate()) + + +def create_custom_doctype(): + if not frappe.db.exists("DocType", "Test Patient Feedback"): + doc = frappe.get_doc( + { + "doctype": "DocType", + "module": "Healthcare", + "custom": 1, + "is_submittable": 1, + "fields": [ + {"label": "Date", "fieldname": "date", "fieldtype": "Date"}, + {"label": "Patient", "fieldname": "patient", "fieldtype": "Link", "options": "Patient"}, + {"label": "Rating", "fieldname": "rating", "fieldtype": "Rating"}, + {"label": "Feedback", "fieldname": "feedback", "fieldtype": "Small Text"}, + ], + "permissions": [{"role": "System Manager", "read": 1}], + "name": "Test Patient Feedback", + } + ) + doc.insert() + return doc + else: + return frappe.get_doc("DocType", "Test Patient Feedback") + + +def create_doc(patient): + doc = frappe.get_doc( + { + "doctype": "Test Patient Feedback", + "patient": patient, + "date": getdate(), + "rating": 3, + "feedback": "Test Patient History Settings", + } + ).insert() + doc.submit() + + return doc diff --git a/healthcare/healthcare/doctype/patient_history_standard_document_type/__init__.py b/healthcare/healthcare/doctype/patient_history_standard_document_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json b/healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json new file mode 100644 index 0000000..b43099c --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "creation": "2020-11-25 13:39:36.014814", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "date_fieldname", + "add_edit_fields", + "selected_fields" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "selected_fields", + "fieldtype": "Code", + "label": "Selected Fields", + "read_only": 1 + }, + { + "fieldname": "add_edit_fields", + "fieldtype": "Button", + "in_list_view": 1, + "label": "Add / Edit Fields" + }, + { + "fieldname": "date_fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Date Fieldname", + "read_only": 1, + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-11-30 13:54:56.773325", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient History Standard Document Type", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py b/healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py new file mode 100644 index 0000000..ab59bc8 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class PatientHistoryStandardDocumentType(Document): + pass diff --git a/healthcare/healthcare/doctype/patient_medical_record/__init__.py b/healthcare/healthcare/doctype/patient_medical_record/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.js b/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.js new file mode 100644 index 0000000..93ff70e --- /dev/null +++ b/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.js @@ -0,0 +1,5 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Patient Medical Record', { +}); diff --git a/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.json b/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.json new file mode 100644 index 0000000..ed82355 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.json @@ -0,0 +1,155 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2016-06-09 11:30:44.972056", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "patient", + "status", + "column_break_2", + "attach", + "section_break_4", + "subject", + "section_break_8", + "communication_date", + "reference_doctype", + "reference_name", + "column_break_9", + "reference_owner", + "user" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-PMR-.YYYY.-", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "search_index": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "attach", + "fieldtype": "Attach", + "label": "Attach Medical Record" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "subject", + "fieldtype": "Text Editor", + "ignore_xss_filter": 1, + "label": "Subject" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Open\nClose", + "read_only": 1 + }, + { + "default": "Today", + "fieldname": "communication_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Datetime", + "read_only": 1 + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reference DocType", + "options": "DocType", + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "options": "reference_doctype", + "read_only": 1, + "search_index": 1 + }, + { + "fetch_from": "reference_name.owner", + "fieldname": "reference_owner", + "fieldtype": "Data", + "label": "Reference Owner", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "default": "__user", + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" + } + ], + "in_create": 1, + "links": [], + "modified": "2020-04-29 12:26:57.679402", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Medical Record", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient, subject, communication_date, reference_doctype, reference_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.py b/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.py new file mode 100644 index 0000000..a73b544 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_medical_record/patient_medical_record.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document + + +class PatientMedicalRecord(Document): + def after_insert(self): + if self.reference_doctype == "Patient Medical Record": + frappe.db.set_value("Patient Medical Record", self.name, "reference_name", self.name) diff --git a/healthcare/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/healthcare/healthcare/doctype/patient_medical_record/test_patient_medical_record.py new file mode 100644 index 0000000..541b212 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_medical_record/test_patient_medical_record.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import add_days, nowdate + +from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_appointment, + create_encounter, + create_healthcare_docs, + create_medical_department, +) + + +class TestPatientMedicalRecord(FrappeTestCase): + def setUp(self): + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 0) + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + make_pos_profile() + + def test_medical_record(self): + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() + appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) + encounter = create_encounter(appointment) + + # check for encounter + medical_rec = frappe.db.exists( + "Patient Medical Record", {"status": "Open", "reference_name": encounter.name} + ) + self.assertTrue(medical_rec) + + vital_signs = create_vital_signs(appointment) + # check for vital signs + medical_rec = frappe.db.exists( + "Patient Medical Record", {"status": "Open", "reference_name": vital_signs.name} + ) + self.assertTrue(medical_rec) + + appointment = create_appointment( + patient, practitioner, add_days(nowdate(), 1), invoice=1, procedure_template=1 + ) + procedure = create_procedure(appointment) + procedure.start_procedure() + procedure.complete_procedure() + # check for clinical procedure + medical_rec = frappe.db.exists( + "Patient Medical Record", {"status": "Open", "reference_name": procedure.name} + ) + self.assertTrue(medical_rec) + + template = create_lab_test_template(medical_department) + lab_test = create_lab_test(template.name, patient) + # check for lab test + medical_rec = frappe.db.exists( + "Patient Medical Record", {"status": "Open", "reference_name": lab_test.name} + ) + self.assertTrue(medical_rec) + + +def create_procedure(appointment): + if appointment: + procedure = frappe.new_doc("Clinical Procedure") + procedure.procedure_template = appointment.procedure_template + procedure.appointment = appointment.name + procedure.patient = appointment.patient + procedure.practitioner = appointment.practitioner + procedure.medical_department = appointment.department + procedure.start_dt = appointment.appointment_date + procedure.start_time = appointment.appointment_time + procedure.save() + procedure.submit() + return procedure + + +def create_vital_signs(appointment): + vital_signs = frappe.new_doc("Vital Signs") + vital_signs.patient = appointment.patient + vital_signs.signs_date = appointment.appointment_date + vital_signs.signs_time = appointment.appointment_time + vital_signs.temperature = 38.5 + vital_signs.save() + vital_signs.submit() + return vital_signs + + +def create_lab_test_template(medical_department): + if frappe.db.exists("Lab Test Template", "Blood Test"): + return frappe.get_doc("Lab Test Template", "Blood Test") + + template = frappe.new_doc("Lab Test Template") + template.lab_test_name = "Blood Test" + template.lab_test_code = "Blood Test" + template.lab_test_group = "Services" + template.department = medical_department + template.is_billable = 1 + template.lab_test_rate = 2000 + template.save() + return template + + +def create_lab_test(template, patient): + lab_test = frappe.new_doc("Lab Test") + lab_test.patient = patient + lab_test.patient_sex = frappe.db.get_value("Patient", patient, "sex") + lab_test.template = template + lab_test.save() + lab_test.submit() + return lab_test diff --git a/healthcare/healthcare/doctype/patient_relation/__init__.py b/healthcare/healthcare/doctype/patient_relation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/patient_relation/patient_relation.json b/healthcare/healthcare/doctype/patient_relation/patient_relation.json new file mode 100644 index 0000000..376f7f7 --- /dev/null +++ b/healthcare/healthcare/doctype/patient_relation/patient_relation.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2017-04-26 15:40:11.561855", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "patient", + "relation", + "description" + ], + "fields": [ + { + "fieldname": "relation", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Relation", + "options": "\nFather\nMother\nSpouse\nSiblings\nFamily\nOther", + "search_index": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Description" + } + ], + "istable": 1, + "links": [], + "modified": "2020-01-29 12:45:40.081899", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Relation", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/patient_relation/patient_relation.py b/healthcare/healthcare/doctype/patient_relation/patient_relation.py new file mode 100644 index 0000000..1377aac --- /dev/null +++ b/healthcare/healthcare/doctype/patient_relation/patient_relation.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, ESS LLP and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class PatientRelation(Document): + pass diff --git a/healthcare/healthcare/doctype/practitioner_schedule/__init__.py b/healthcare/healthcare/doctype/practitioner_schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.js b/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.js new file mode 100644 index 0000000..2e96a49 --- /dev/null +++ b/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.js @@ -0,0 +1,117 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Practitioner Schedule', { + refresh: function(frm) { + cur_frm.fields_dict["time_slots"].grid.wrapper.find('.grid-add-row').hide(); + cur_frm.fields_dict["time_slots"].grid.add_custom_button(__('Add Time Slots'), () => { + let d = new frappe.ui.Dialog({ + fields: [ + {fieldname: 'days', label: __('Select Days'), fieldtype: 'MultiSelect', + options:[ + {value:'Sunday', label:__('Sunday')}, + {value:'Monday', label:__('Monday')}, + {value:'Tuesday', label:__('Tuesday')}, + {value:'Wednesday', label:__('Wednesday')}, + {value:'Thursday', label:__('Thursday')}, + {value:'Friday', label:__('Friday')}, + {value:'Saturday', label:__('Saturday')}, + ], reqd: 1}, + {fieldname: 'from_time', label: __('From'), fieldtype: 'Time', + 'default': '09:00:00', reqd: 1}, + {fieldname: 'to_time', label: __('To'), fieldtype: 'Time', + 'default': '12:00:00', reqd: 1}, + {fieldname: 'duration', label: __('Appointment Duration (mins)'), + fieldtype:'Int', 'default': 15, reqd: 1}, + ], + primary_action_label: __('Add Timeslots'), + primary_action: () => { + let values = d.get_values(); + if (values) { + let slot_added = false; + values.days.split(',').forEach(function(day){ + day = $.trim(day); + if (['Sunday', 'Monday', 'Tuesday', 'Wednesday', + 'Thursday', 'Friday', 'Saturday'].includes(day)){ + add_slots(day); + } + }); + + function check_overlap_or_add_slot(week_day, cur_time, end_time, add_slots_to_child){ + let overlap = false; + while (cur_time < end_time) { + let add_to_child = true; + let to_time = cur_time.clone().add(values.duration, 'minutes'); + if (to_time <= end_time) { + if (frm.doc.time_slots){ + frm.doc.time_slots.forEach(function(slot) { + if (slot.day == week_day){ + let slot_from_moment = moment(slot.from_time, 'HH:mm:ss'); + let slot_to_moment = moment(slot.to_time, 'HH:mm:ss'); + if (cur_time.isSame(slot_from_moment) || cur_time.isBetween(slot_from_moment, slot_to_moment) || + to_time.isSame(slot_to_moment) || to_time.isBetween(slot_from_moment, slot_to_moment)) { + overlap = true; + if (add_slots_to_child) { + frappe.show_alert({ + message:__('Time slot skiped, the slot {0} to {1} overlap exisiting slot {2} to {3}', + [cur_time.format('HH:mm:ss'), to_time.format('HH:mm:ss'), slot.from_time, slot.to_time]), + indicator:'orange' + }); + add_to_child = false; + } + } + } + }); + } + // add a new timeslot + if (add_to_child && add_slots_to_child) { + frm.add_child('time_slots', { + from_time: cur_time.format('HH:mm:ss'), + to_time: to_time.format('HH:mm:ss'), + day: week_day + }); + slot_added = true; + } + } + cur_time = to_time; + } + return overlap; + } + + function add_slots(week_day) { + let cur_time = moment(values.from_time, 'HH:mm:ss'); + let end_time = moment(values.to_time, 'HH:mm:ss'); + if (check_overlap_or_add_slot(week_day, cur_time, end_time, false)) { + frappe.confirm(__('Schedules for {0} overlaps, do you want to proceed after skipping overlapped slots ?', [week_day]), + function() { + // if Yes + check_overlap_or_add_slot(week_day, cur_time, end_time, true); + }, + function() { + // if No + frappe.show_alert({ + message: __('Slots for {0} are not added to the schedule', [week_day]), + indicator: 'red' + }); + } + ); + } else { + check_overlap_or_add_slot(week_day, cur_time, end_time, true); + } + } + + frm.refresh_field('time_slots'); + + if (slot_added) { + frappe.show_alert({ + message: __('Time slots added'), + indicator: 'green' + }); + } + } + }, + }); + d.show(); + }); + } +}); diff --git a/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.json b/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.json new file mode 100644 index 0000000..a21825e --- /dev/null +++ b/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.json @@ -0,0 +1,71 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:schedule_name", + "beta": 1, + "creation": "2017-05-03 17:28:03.926787", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "disabled", + "schedule_details_section", + "schedule_name", + "time_slots" + ], + "fields": [ + { + "fieldname": "schedule_name", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Schedule Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "time_slots", + "fieldtype": "Table", + "label": "Time Slots", + "options": "Healthcare Schedule Time Slot" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled", + "print_hide": 1 + }, + { + "fieldname": "schedule_details_section", + "fieldtype": "Section Break", + "label": "Schedule Details" + } + ], + "links": [], + "modified": "2020-09-18 17:26:09.703215", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Practitioner Schedule", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.py b/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.py new file mode 100644 index 0000000..bba96bc --- /dev/null +++ b/healthcare/healthcare/doctype/practitioner_schedule/practitioner_schedule.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class PractitionerSchedule(Document): + def autoname(self): + self.name = self.schedule_name diff --git a/healthcare/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py b/healthcare/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py new file mode 100644 index 0000000..6322695 --- /dev/null +++ b/healthcare/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestPractitionerSchedule(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/practitioner_service_unit_schedule/__init__.py b/healthcare/healthcare/doctype/practitioner_service_unit_schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json b/healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json new file mode 100644 index 0000000..4c283aa --- /dev/null +++ b/healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json @@ -0,0 +1,110 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2017-11-16 12:19:17.163786", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "schedule", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Schedule", + "length": 0, + "no_copy": 0, + "options": "Practitioner Schedule", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "service_unit", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Service Unit", + "length": 0, + "no_copy": 0, + "options": "Healthcare Service Unit", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:33:07.936958", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Practitioner Service Unit Schedule", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py b/healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py new file mode 100644 index 0000000..068b14b --- /dev/null +++ b/healthcare/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class PractitionerServiceUnitSchedule(Document): + pass diff --git a/healthcare/healthcare/doctype/prescription_dosage/__init__.py b/healthcare/healthcare/doctype/prescription_dosage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.js b/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.js new file mode 100644 index 0000000..471ac8f --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Prescription Dosage', { +}); diff --git a/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.json b/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.json new file mode 100644 index 0000000..9fb0dbc --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.json @@ -0,0 +1,145 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:dosage", + "beta": 1, + "creation": "2016-09-16 15:49:25.327610", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "dosage", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Dosage", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "dosage_strength", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "options": "Dosage Strength", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-05 11:20:47.558464", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Prescription Dosage", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "dosage", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.py b/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.py new file mode 100644 index 0000000..a8f82f4 --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_dosage/prescription_dosage.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class PrescriptionDosage(Document): + pass diff --git a/healthcare/healthcare/doctype/prescription_dosage/test_prescription_dosage.py b/healthcare/healthcare/doctype/prescription_dosage/test_prescription_dosage.py new file mode 100644 index 0000000..3acaec2 --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_dosage/test_prescription_dosage.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestPrescriptionDosage(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/prescription_duration/__init__.py b/healthcare/healthcare/doctype/prescription_duration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/prescription_duration/prescription_duration.js b/healthcare/healthcare/doctype/prescription_duration/prescription_duration.js new file mode 100644 index 0000000..22152b5 --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_duration/prescription_duration.js @@ -0,0 +1,5 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Prescription Duration', { +}); diff --git a/healthcare/healthcare/doctype/prescription_duration/prescription_duration.json b/healthcare/healthcare/doctype/prescription_duration/prescription_duration.json new file mode 100644 index 0000000..c4f6c5f --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_duration/prescription_duration.json @@ -0,0 +1,145 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "", + "beta": 1, + "creation": "2016-09-16 15:50:28.895789", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "number", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Number", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "period", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Period", + "length": 0, + "no_copy": 0, + "options": "Hour\nDay\nWeek\nMonth", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-08-31 13:42:51.325725", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Prescription Duration", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "", + "show_name_in_global_search": 0, + "sort_field": "", + "sort_order": "ASC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/prescription_duration/prescription_duration.py b/healthcare/healthcare/doctype/prescription_duration/prescription_duration.py new file mode 100644 index 0000000..8b8785d --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_duration/prescription_duration.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document +from frappe.utils import cstr + + +class PrescriptionDuration(Document): + def autoname(self): + self.name = " ".join(filter(None, [cstr(self.get(f)).strip() for f in ["number", "period"]])) + + def get_days(self): + days = 0 + duration = self + if duration.period == "Day": + days = duration.number + if duration.period == "Hour": + days = (duration.number) / 24 + if duration.period == "Week": + days = duration.number * 7 + if duration.period == "Month": + days = duration.number * 30 + return days + + def get_weeks(self): + weeks = 0 + duration = self + if duration.period == "Day": + weeks = (duration.number) / 7 + # if(duration.period == 'Hour'): + # weeks = (duration.number)/x + if duration.period == "Week": + weeks = duration.number + if duration.period == "Month": + weeks = duration.number * 4 + return weeks + + def get_months(self): + months = 0 + duration = self + if duration.period == "Day": + months = (duration.number) / 30 + # if(duration.period == 'Hour'): + # months = (duration.number)/x + if duration.period == "Week": + months = (duration.number) / 4 + if duration.period == "Month": + months = duration.number + return months + + def get_hours(self): + hours = 0 + duration = self + if duration.period == "Day": + hours = duration.number * 24 + if duration.period == "Hour": + hours = duration.number + if duration.period == "Week": + hours = (duration.number * 24) * 7 + if duration.period == "Month": + hours = (duration.number * 24) * 30 + return hours + + def get_minutes(self): + minutes = 0 + duration = self + if duration.period == "Day": + minutes = duration.number * 1440 + if duration.period == "Hour": + minutes = duration.number * 60 + if duration.period == "Week": + minutes = duration.number * 10080 + if duration.period == "Month": + minutes = duration.number * 43800 + return minutes diff --git a/healthcare/healthcare/doctype/prescription_duration/test_prescription_duration.py b/healthcare/healthcare/doctype/prescription_duration/test_prescription_duration.py new file mode 100644 index 0000000..c6e7389 --- /dev/null +++ b/healthcare/healthcare/doctype/prescription_duration/test_prescription_duration.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + + +class TestPrescriptionDuration(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/procedure_prescription/__init__.py b/healthcare/healthcare/doctype/procedure_prescription/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.json b/healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.json new file mode 100644 index 0000000..e4c01d7 --- /dev/null +++ b/healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.json @@ -0,0 +1,99 @@ +{ + "actions": [], + "allow_copy": 1, + "beta": 1, + "creation": "2017-11-17 15:52:48.324157", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "procedure", + "procedure_name", + "department", + "practitioner", + "date", + "comments", + "appointment_booked", + "procedure_created", + "invoiced" + ], + "fields": [ + { + "fieldname": "procedure", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Clinical Procedure", + "options": "Clinical Procedure Template", + "reqd": 1 + }, + { + "fetch_from": "procedure.template", + "fieldname": "procedure_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Procedure Name" + }, + { + "fetch_from": "procedure.medical_department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Medical Department" + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Referring Practitioner", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date" + }, + { + "fieldname": "comments", + "fieldtype": "Data", + "label": "Comments" + }, + { + "default": "0", + "fieldname": "appointment_booked", + "fieldtype": "Check", + "hidden": 1, + "label": "Appointment Booked", + "search_index": 1 + }, + { + "default": "0", + "fieldname": "procedure_created", + "fieldtype": "Check", + "hidden": 1, + "label": "Procedure Created", + "no_copy": 1, + "search_index": 1 + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 1, + "search_index": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-02-26 15:42:33.988081", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Procedure Prescription", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.py b/healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.py new file mode 100644 index 0000000..15474d2 --- /dev/null +++ b/healthcare/healthcare/doctype/procedure_prescription/procedure_prescription.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ProcedurePrescription(Document): + pass diff --git a/healthcare/healthcare/doctype/sample_collection/__init__.py b/healthcare/healthcare/doctype/sample_collection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/sample_collection/sample_collection.js b/healthcare/healthcare/doctype/sample_collection/sample_collection.js new file mode 100644 index 0000000..1bf2fc2 --- /dev/null +++ b/healthcare/healthcare/doctype/sample_collection/sample_collection.js @@ -0,0 +1,40 @@ +// Copyright (c) 2016, ESS and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Sample Collection', { + refresh: function(frm) { + if (frappe.defaults.get_default('create_sample_collection_for_lab_test')) { + frm.add_custom_button(__('View Lab Tests'), function() { + frappe.route_options = {'sample': frm.doc.name}; + frappe.set_route('List', 'Lab Test'); + }); + } + } +}); + +frappe.ui.form.on('Sample Collection', 'patient', function(frm) { + if(frm.doc.patient){ + frappe.call({ + 'method': 'healthcare.healthcare.doctype.patient.patient.get_patient_detail', + args: { + patient: frm.doc.patient + }, + callback: function (data) { + var age = null; + if (data.message.dob){ + age = calculate_age(data.message.dob); + } + frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age); + frappe.model.set_value(frm.doctype,frm.docname, 'patient_sex', data.message.sex); + } + }); + } +}); + +var calculate_age = function(birth) { + var ageMS = Date.parse(Date()) - Date.parse(birth); + var age = new Date(); + age.setTime(ageMS); + var years = age.getFullYear() - 1970; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; +}; diff --git a/healthcare/healthcare/doctype/sample_collection/sample_collection.json b/healthcare/healthcare/doctype/sample_collection/sample_collection.json new file mode 100644 index 0000000..83383e3 --- /dev/null +++ b/healthcare/healthcare/doctype/sample_collection/sample_collection.json @@ -0,0 +1,256 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2016-04-05 15:58:18.076977", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "patient_details_section", + "naming_series", + "patient", + "patient_name", + "patient_age", + "patient_sex", + "column_break_4", + "inpatient_record", + "company", + "invoiced", + "section_break_6", + "sample", + "sample_uom", + "sample_qty", + "column_break_10", + "collected_by", + "collected_time", + "num_print", + "section_break_15", + "sample_details", + "amended_from" + ], + "fields": [ + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "bold": 1, + "fieldname": "naming_series", + "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, + "label": "Series", + "no_copy": 1, + "options": "HLC-SC-.YYYY.-", + "print_hide": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, + "label": "Invoiced", + "no_copy": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fetch_from": "inpatient_record.patient", + "fieldname": "patient", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, + "label": "Age", + "read_only": 1 + }, + { + "fetch_from": "patient.sex", + "fieldname": "patient_sex", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, + "label": "Sample Details" + }, + { + "fieldname": "sample", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "ignore_user_permissions": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Sample", + "options": "Lab Test Sample", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "sample.sample_uom", + "fieldname": "sample_uom", + "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, + "in_list_view": 1, + "label": "UOM", + "read_only": 1 + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 + }, + { + "fieldname": "collected_by", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "ignore_user_permissions": 1, + "label": "Collected By", + "options": "User" + }, + { + "fieldname": "collected_time", + "fieldtype": "Datetime", + "hide_days": 1, + "hide_seconds": 1, + "label": "Collected On" + }, + { + "allow_on_submit": 1, + "default": "1", + "description": "Number of prints required for labelling the samples", + "fieldname": "num_print", + "fieldtype": "Int", + "hide_days": 1, + "hide_seconds": 1, + "label": "No. of prints", + "print_hide": 1, + "report_hide": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Sample Collection", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 + }, + { + "default": "0", + "fieldname": "sample_qty", + "fieldtype": "Float", + "hide_days": 1, + "hide_seconds": 1, + "in_list_view": 1, + "label": "Quantity" + }, + { + "fieldname": "sample_details", + "fieldtype": "Long Text", + "hide_days": 1, + "hide_seconds": 1, + "ignore_xss_filter": 1, + "label": "Collection Details" + }, + { + "fieldname": "patient_details_section", + "fieldtype": "Section Break", + "label": "Patient Details" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-07-30 16:53:13.076104", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Sample Collection", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient, sample", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/sample_collection/sample_collection.py b/healthcare/healthcare/doctype/sample_collection/sample_collection.py new file mode 100644 index 0000000..6ee34e2 --- /dev/null +++ b/healthcare/healthcare/doctype/sample_collection/sample_collection.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt + + +class SampleCollection(Document): + def validate(self): + if flt(self.sample_qty) <= 0: + frappe.throw(_("Sample Quantity cannot be negative or 0"), title=_("Invalid Quantity")) diff --git a/healthcare/healthcare/doctype/sample_collection/test_sample_collection.py b/healthcare/healthcare/doctype/sample_collection/test_sample_collection.py new file mode 100644 index 0000000..2d991d1 --- /dev/null +++ b/healthcare/healthcare/doctype/sample_collection/test_sample_collection.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Sample Collection') + + +class TestSampleCollection(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/sensitivity/__init__.py b/healthcare/healthcare/doctype/sensitivity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/sensitivity/sensitivity.js b/healthcare/healthcare/doctype/sensitivity/sensitivity.js new file mode 100644 index 0000000..f9c9002 --- /dev/null +++ b/healthcare/healthcare/doctype/sensitivity/sensitivity.js @@ -0,0 +1,5 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Sensitivity', { +}); diff --git a/healthcare/healthcare/doctype/sensitivity/sensitivity.json b/healthcare/healthcare/doctype/sensitivity/sensitivity.json new file mode 100644 index 0000000..eddfda9 --- /dev/null +++ b/healthcare/healthcare/doctype/sensitivity/sensitivity.json @@ -0,0 +1,115 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:sensitivity", + "beta": 1, + "creation": "2016-02-23 11:12:54.623249", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sensitivity", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Sensitivity", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-05 11:19:12.110308", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Sensitivity", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Laboratory User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "search_fields": "sensitivity", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "sensitivity", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/sensitivity/sensitivity.py b/healthcare/healthcare/doctype/sensitivity/sensitivity.py new file mode 100644 index 0000000..9db634a --- /dev/null +++ b/healthcare/healthcare/doctype/sensitivity/sensitivity.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class Sensitivity(Document): + pass diff --git a/healthcare/healthcare/doctype/sensitivity/test_sensitivity.py b/healthcare/healthcare/doctype/sensitivity/test_sensitivity.py new file mode 100644 index 0000000..3fa7722 --- /dev/null +++ b/healthcare/healthcare/doctype/sensitivity/test_sensitivity.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Sensitivity') + + +class TestSensitivity(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/sensitivity_test_result/__init__.py b/healthcare/healthcare/doctype/sensitivity_test_result/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json b/healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json new file mode 100644 index 0000000..768c177 --- /dev/null +++ b/healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json @@ -0,0 +1,103 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 1, + "creation": "2016-02-22 15:18:01.769903", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "antibiotic", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Antibiotic", + "length": 0, + "no_copy": 0, + "options": "Antibiotic", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "antibiotic_sensitivity", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Sensitivity", + "length": 0, + "no_copy": 0, + "options": "Sensitivity", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-10-05 11:08:06.327972", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Sensitivity Test Result", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Healthcare", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py b/healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py new file mode 100644 index 0000000..ffddc81 --- /dev/null +++ b/healthcare/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS and contributors +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class SensitivityTestResult(Document): + pass diff --git a/healthcare/healthcare/doctype/therapy_plan/__init__.py b/healthcare/healthcare/doctype/therapy_plan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/therapy_plan/test_therapy_plan.py b/healthcare/healthcare/doctype/therapy_plan/test_therapy_plan.py new file mode 100644 index 0000000..f881eba --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import flt, getdate, nowdate + +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_appointment, + create_healthcare_docs, + create_medical_department, + create_patient, +) +from healthcare.healthcare.doctype.therapy_plan.therapy_plan import ( + make_sales_invoice, + make_therapy_session, +) +from healthcare.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type + + +class TestTherapyPlan(FrappeTestCase): + def test_creation_on_encounter_submission(self): + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() + encounter = create_encounter(patient, medical_department, practitioner) + self.assertTrue(frappe.db.exists("Therapy Plan", encounter.therapy_plan)) + + def test_status(self): + plan = create_therapy_plan() + self.assertEqual(plan.status, "Not Started") + + session = make_therapy_session(plan.name, plan.patient, "Basic Rehab", "_Test Company") + frappe.get_doc(session).submit() + self.assertEqual(frappe.db.get_value("Therapy Plan", plan.name, "status"), "In Progress") + + session = make_therapy_session(plan.name, plan.patient, "Basic Rehab", "_Test Company") + frappe.get_doc(session).submit() + self.assertEqual(frappe.db.get_value("Therapy Plan", plan.name, "status"), "Completed") + + patient, practitioner = create_healthcare_docs() + appointment = create_appointment(patient, practitioner, nowdate()) + + session = make_therapy_session( + plan.name, plan.patient, "Basic Rehab", "_Test Company", appointment.name + ) + session = frappe.get_doc(session) + session.submit() + self.assertEqual( + frappe.db.get_value("Patient Appointment", appointment.name, "status"), "Closed" + ) + session.cancel() + self.assertEqual(frappe.db.get_value("Patient Appointment", appointment.name, "status"), "Open") + + def test_therapy_plan_from_template(self): + patient = create_patient() + template = create_therapy_plan_template() + # check linked item + self.assertTrue(frappe.db.exists("Therapy Plan Template", {"linked_item": "Complete Rehab"})) + + plan = create_therapy_plan(template) + # invoice + si = make_sales_invoice(plan.name, patient, "_Test Company", template) + si.save() + + therapy_plan_template_amt = frappe.db.get_value( + "Therapy Plan Template", template, "total_amount" + ) + self.assertEqual(si.items[0].amount, therapy_plan_template_amt) + + +def create_therapy_plan(template=None, patient=None): + if not patient: + patient = create_patient() + therapy_type = create_therapy_type() + plan = frappe.new_doc("Therapy Plan") + plan.patient = patient + plan.start_date = getdate() + + if template: + plan.therapy_plan_template = template + plan = plan.set_therapy_details_from_template() + else: + plan.append("therapy_plan_details", {"therapy_type": therapy_type.name, "no_of_sessions": 2}) + + plan.save() + return plan + + +def create_encounter(patient, medical_department, practitioner): + encounter = frappe.new_doc("Patient Encounter") + encounter.patient = patient + encounter.practitioner = practitioner + encounter.medical_department = medical_department + therapy_type = create_therapy_type() + encounter.append("therapies", {"therapy_type": therapy_type.name, "no_of_sessions": 2}) + encounter.save() + encounter.submit() + return encounter + + +def create_therapy_plan_template(): + template_name = frappe.db.exists("Therapy Plan Template", "Complete Rehab") + if not template_name: + therapy_type = create_therapy_type() + template = frappe.new_doc("Therapy Plan Template") + template.plan_name = template.item_code = template.item_name = "Complete Rehab" + template.item_group = "Services" + rate = frappe.db.get_value("Therapy Type", therapy_type.name, "rate") + template.append( + "therapy_types", + {"therapy_type": therapy_type.name, "no_of_sessions": 2, "rate": rate, "amount": 2 * flt(rate)}, + ) + template.save() + template_name = template.name + + return template_name diff --git a/healthcare/healthcare/doctype/therapy_plan/therapy_plan.js b/healthcare/healthcare/doctype/therapy_plan/therapy_plan.js new file mode 100644 index 0000000..f36e77f --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan/therapy_plan.js @@ -0,0 +1,133 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Therapy Plan', { + setup: function(frm) { + frm.get_field('therapy_plan_details').grid.editable_fields = [ + {fieldname: 'therapy_type', columns: 6}, + {fieldname: 'no_of_sessions', columns: 2}, + {fieldname: 'sessions_completed', columns: 2} + ]; + }, + + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.trigger('show_progress_for_therapies'); + if (frm.doc.status != 'Completed') { + let therapy_types = (frm.doc.therapy_plan_details || []).map(function(d){ return d.therapy_type; }); + const fields = [{ + fieldtype: 'Link', + label: __('Therapy Type'), + fieldname: 'therapy_type', + options: 'Therapy Type', + reqd: 1, + get_query: function() { + return { + filters: { 'therapy_type': ['in', therapy_types]} + }; + } + }]; + + frm.add_custom_button(__('Therapy Session'), function() { + frappe.prompt(fields, data => { + frappe.call({ + method: 'healthcare.healthcare.doctype.therapy_plan.therapy_plan.make_therapy_session', + args: { + therapy_plan: frm.doc.name, + patient: frm.doc.patient, + therapy_type: data.therapy_type, + company: frm.doc.company + }, + freeze: true, + callback: function(r) { + if (r.message) { + frappe.model.sync(r.message); + frappe.set_route('Form', r.message.doctype, r.message.name); + } + } + }); + }, __('Select Therapy Type'), __('Create')); + }, __('Create')); + } + + if (frm.doc.therapy_plan_template && !frm.doc.invoiced) { + frm.add_custom_button(__('Sales Invoice'), function() { + frm.trigger('make_sales_invoice'); + }, __('Create')); + } + } + + if (frm.doc.therapy_plan_template) { + frm.fields_dict.therapy_plan_details.grid.update_docfield_property( + 'therapy_type', 'read_only', 1 + ); + frm.fields_dict.therapy_plan_details.grid.update_docfield_property( + 'no_of_sessions', 'read_only', 1 + ); + } + }, + + make_sales_invoice: function(frm) { + frappe.call({ + args: { + 'reference_name': frm.doc.name, + 'patient': frm.doc.patient, + 'company': frm.doc.company, + 'therapy_plan_template': frm.doc.therapy_plan_template + }, + method: 'healthcare.healthcare.doctype.therapy_plan.therapy_plan.make_sales_invoice', + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route('Form', doclist[0].doctype, doclist[0].name); + } + }); + }, + + therapy_plan_template: function(frm) { + if (frm.doc.therapy_plan_template) { + frappe.call({ + method: 'set_therapy_details_from_template', + doc: frm.doc, + freeze: true, + freeze_message: __('Fetching Template Details'), + callback: function() { + refresh_field('therapy_plan_details'); + } + }); + } + }, + + show_progress_for_therapies: function(frm) { + let bars = []; + let message = ''; + + // completed sessions + let title = __('{0} sessions completed', [frm.doc.total_sessions_completed]); + if (frm.doc.total_sessions_completed === 1) { + title = __('{0} session completed', [frm.doc.total_sessions_completed]); + } + title += __(' out of {0}', [frm.doc.total_sessions]); + + bars.push({ + 'title': title, + 'width': (frm.doc.total_sessions_completed / frm.doc.total_sessions * 100) + '%', + 'progress_class': 'progress-bar-success' + }); + if (bars[0].width == '0%') { + bars[0].width = '0.5%'; + } + message = title; + frm.dashboard.add_progress(__('Status'), bars, message); + }, +}); + +frappe.ui.form.on('Therapy Plan Detail', { + no_of_sessions: function(frm) { + let total = 0; + $.each(frm.doc.therapy_plan_details, function(_i, e) { + total += e.no_of_sessions; + }); + frm.set_value('total_sessions', total); + refresh_field('total_sessions'); + } +}); diff --git a/healthcare/healthcare/doctype/therapy_plan/therapy_plan.json b/healthcare/healthcare/doctype/therapy_plan/therapy_plan.json new file mode 100644 index 0000000..c03e9de --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan/therapy_plan.json @@ -0,0 +1,179 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-03-29 20:56:49.758602", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "patient", + "patient_name", + "invoiced", + "column_break_4", + "company", + "status", + "start_date", + "section_break_3", + "therapy_plan_template", + "therapy_plan_details", + "title", + "section_break_9", + "total_sessions", + "column_break_11", + "total_sessions_completed" + ], + "fields": [ + { + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "reqd": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "therapy_plan_details", + "fieldtype": "Table", + "label": "Therapy Plan Details", + "options": "Therapy Plan Detail", + "read_only_depends_on": "therapy_plan_template", + "reqd": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "HLC-THP-.YYYY.-" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "default": "{patient_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_sessions", + "fieldtype": "Int", + "label": "Total Sessions", + "read_only": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_sessions_completed", + "fieldtype": "Int", + "label": "Total Sessions Completed", + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Not Started\nIn Progress\nCompleted\nCancelled", + "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "therapy_plan_template", + "fieldtype": "Link", + "label": "Therapy Plan Template", + "options": "Therapy Plan Template", + "set_only_once": 1 + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } + ], + "links": [], + "modified": "2020-11-04 18:13:13.564999", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Plan", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "search_fields": "patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/therapy_plan/therapy_plan.py b/healthcare/healthcare/doctype/therapy_plan/therapy_plan.py new file mode 100644 index 0000000..1d5ba8c --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan/therapy_plan.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document +from frappe.utils import flt, today +from healthcare.healthcare.doctype.nursing_task.nursing_task import NursingTask +from healthcare.healthcare.utils import validate_nursing_tasks + + +class TherapyPlan(Document): + def validate(self): + self.set_totals() + self.set_status() + + def on_submit(self): + validate_nursing_tasks(self) + + def set_status(self): + if not self.total_sessions_completed: + self.status = "Not Started" + else: + if self.total_sessions_completed < self.total_sessions: + self.status = "In Progress" + elif self.total_sessions_completed == self.total_sessions: + self.status = "Completed" + + def set_totals(self): + total_sessions = 0 + total_sessions_completed = 0 + for entry in self.therapy_plan_details: + if entry.no_of_sessions: + total_sessions += entry.no_of_sessions + if entry.sessions_completed: + total_sessions_completed += entry.sessions_completed + + self.db_set("total_sessions", total_sessions) + self.db_set("total_sessions_completed", total_sessions_completed) + + @frappe.whitelist() + def set_therapy_details_from_template(self): + # Add therapy types in the child table + self.set("therapy_plan_details", []) + therapy_plan_template = frappe.get_doc("Therapy Plan Template", self.therapy_plan_template) + + for data in therapy_plan_template.therapy_types: + self.append( + "therapy_plan_details", + {"therapy_type": data.therapy_type, "no_of_sessions": data.no_of_sessions}, + ) + return self + + +@frappe.whitelist() +def make_therapy_session(therapy_plan, patient, therapy_type, company, appointment=None): + therapy_type = frappe.get_doc("Therapy Type", therapy_type) + + therapy_session = frappe.new_doc("Therapy Session") + therapy_session.therapy_plan = therapy_plan + therapy_session.company = company + therapy_session.patient = patient + therapy_session.therapy_type = therapy_type.name + therapy_session.duration = therapy_type.default_duration + therapy_session.rate = therapy_type.rate + therapy_session.exercises = therapy_type.exercises + therapy_session.appointment = appointment + + if frappe.flags.in_test: + therapy_session.start_date = today() + return therapy_session.as_dict() + + +@frappe.whitelist() +def make_sales_invoice(reference_name, patient, company, therapy_plan_template): + from erpnext.stock.get_item_details import get_item_details + + si = frappe.new_doc("Sales Invoice") + si.company = company + si.patient = patient + si.customer = frappe.db.get_value("Patient", patient, "customer") + + item = frappe.db.get_value("Therapy Plan Template", therapy_plan_template, "linked_item") + price_list, price_list_currency = frappe.db.get_values( + "Price List", {"selling": 1}, ["name", "currency"] + )[0] + args = { + "doctype": "Sales Invoice", + "item_code": item, + "company": company, + "customer": si.customer, + "selling_price_list": price_list, + "price_list_currency": price_list_currency, + "plc_conversion_rate": 1.0, + "conversion_rate": 1.0, + } + + item_line = si.append("items", {}) + item_details = get_item_details(args) + item_line.item_code = item + item_line.qty = 1 + item_line.rate = item_details.price_list_rate + item_line.amount = flt(item_line.rate) * flt(item_line.qty) + item_line.reference_dt = "Therapy Plan" + item_line.reference_dn = reference_name + item_line.description = item_details.description + + si.set_missing_values(for_validate=True) + return si diff --git a/healthcare/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py b/healthcare/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py new file mode 100644 index 0000000..65eb787 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py @@ -0,0 +1,13 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "therapy_plan", + "non_standard_fieldnames": {"Sales Invoice": "reference_dn"}, + "transactions": [ + {"label": _("Therapy Sessions"), "items": ["Therapy Session"]}, + {"label": _("Billing"), "items": ["Sales Invoice"]}, + ], + "disable_create_buttons": ["Sales Invoice"], + } diff --git a/healthcare/healthcare/doctype/therapy_plan/therapy_plan_list.js b/healthcare/healthcare/doctype/therapy_plan/therapy_plan_list.js new file mode 100644 index 0000000..63967af --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan/therapy_plan_list.js @@ -0,0 +1,11 @@ +frappe.listview_settings['Therapy Plan'] = { + get_indicator: function(doc) { + var colors = { + 'Completed': 'green', + 'In Progress': 'orange', + 'Not Started': 'red', + 'Cancelled': 'grey' + }; + return [__(doc.status), colors[doc.status], 'status,=,' + doc.status]; + } +}; diff --git a/healthcare/healthcare/doctype/therapy_plan_detail/__init__.py b/healthcare/healthcare/doctype/therapy_plan_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json b/healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json new file mode 100644 index 0000000..77f08af --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2020-03-29 20:52:57.068731", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "therapy_type", + "no_of_sessions", + "sessions_completed" + ], + "fields": [ + { + "fieldname": "therapy_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Therapy Type", + "options": "Therapy Type", + "reqd": 1 + }, + { + "fieldname": "no_of_sessions", + "fieldtype": "Int", + "in_list_view": 1, + "label": "No of Sessions" + }, + { + "default": "0", + "depends_on": "eval:doc.parenttype=='Therapy Plan';", + "fieldname": "sessions_completed", + "fieldtype": "Int", + "label": "Sessions Completed", + "no_copy": 1, + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-11-04 18:15:52.173450", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Plan Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py b/healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py new file mode 100644 index 0000000..46aaa6f --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class TherapyPlanDetail(Document): + pass diff --git a/healthcare/healthcare/doctype/therapy_plan_template/__init__.py b/healthcare/healthcare/doctype/therapy_plan_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py b/healthcare/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py new file mode 100644 index 0000000..70c0d5b --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestTherapyPlanTemplate(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.js b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.js new file mode 100644 index 0000000..90c4b61 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.js @@ -0,0 +1,87 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Therapy Plan Template', { + refresh: function(frm) { + frm.set_query('therapy_type', 'therapy_types', () => { + return { + filters: { + 'is_billable': 1 + } + }; + }); + + frm.set_query('linked_item', function() { + return { + filters: { + 'disabled': false, + 'is_stock_item': false + } + }; + }); + }, + + set_totals: function (frm) { + let total_sessions = 0; + let total_amount = 0.0; + frm.doc.therapy_types.forEach((d) => { + if (d.no_of_sessions) total_sessions += cint(d.no_of_sessions); + if (d.amount) total_amount += flt(d.amount); + }); + frm.set_value('total_sessions', total_sessions); + frm.set_value('total_amount', total_amount); + frm.refresh_fields(); + }, + + link_existing_item: function (frm) { + if (frm.doc.link_existing_item) { + frm.set_value('item_code', ''); + } else { + frm.set_value('linked_item', ''); + } + }, + + linked_item: function (frm) { + if (frm.doc.linked_item) { + frappe.db.get_value('Item', frm.doc.linked_item, ['item_group', 'description', 'item_name']) + .then(r => { + frm.set_value({ + 'item_group': r.message.item_group, + 'description': r.message.description, + 'item_name': r.message.item_name + }); + }) + } + } +}); + +frappe.ui.form.on('Therapy Plan Template Detail', { + therapy_type: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + frappe.call('frappe.client.get', { + doctype: 'Therapy Type', + name: row.therapy_type + }).then((res) => { + row.rate = res.message.rate; + if (!row.no_of_sessions) + row.no_of_sessions = 1; + row.amount = flt(row.rate) * cint(row.no_of_sessions); + frm.refresh_field('therapy_types'); + frm.trigger('set_totals'); + }); + }, + + no_of_sessions: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + row.amount = flt(row.rate) * cint(row.no_of_sessions); + frm.refresh_field('therapy_types'); + frm.trigger('set_totals'); + }, + + rate: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + row.amount = flt(row.rate) * cint(row.no_of_sessions); + frm.refresh_field('therapy_types'); + frm.trigger('set_totals'); + } +}); diff --git a/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.json b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.json new file mode 100644 index 0000000..9c46be3 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.json @@ -0,0 +1,149 @@ +{ + "actions": [], + "autoname": "field:plan_name", + "creation": "2022-01-21 19:58:31.906990", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "plan_name", + "linked_item_details_section", + "link_existing_item", + "linked_item", + "item_code", + "item_name", + "item_group", + "column_break_6", + "description", + "therapy_types_section", + "therapy_types", + "section_break_11", + "total_sessions", + "column_break_13", + "total_amount" + ], + "fields": [ + { + "fieldname": "plan_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Plan Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "therapy_types_section", + "fieldtype": "Section Break", + "label": "Therapy Types" + }, + { + "fieldname": "therapy_types", + "fieldtype": "Table", + "label": "Therapy Types", + "options": "Therapy Plan Template Detail", + "reqd": 1 + }, + { + "depends_on": "eval: !doc.__islocal || doc.link_existing_item", + "fieldname": "linked_item", + "fieldtype": "Link", + "label": "Item", + "mandatory_depends_on": "eval:doc.link_existing_item", + "options": "Item", + "read_only_depends_on": "eval: !doc.__islocal" + }, + { + "fieldname": "linked_item_details_section", + "fieldtype": "Section Break", + "label": "Linked Item Details" + }, + { + "depends_on": "eval:!doc.link_existing_item || !doc.__islocal", + "fieldname": "item_code", + "fieldtype": "Data", + "label": "Item Code", + "mandatory_depends_on": "eval:!doc.link_existing_item", + "read_only_depends_on": "eval: !doc.__islocal", + "set_only_once": 1 + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only_depends_on": "eval:doc.link_existing_item", + "reqd": 1 + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "read_only_depends_on": "eval:doc.link_existing_item", + "reqd": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Item Description", + "read_only_depends_on": "eval:doc.link_existing_item" + }, + { + "fieldname": "total_amount", + "fieldtype": "Currency", + "label": "Total Amount", + "read_only": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_sessions", + "fieldtype": "Int", + "label": "Total Sessions", + "read_only": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval: doc.__islocal", + "fieldname": "link_existing_item", + "fieldtype": "Check", + "label": "Link existing Item" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2022-05-31 16:37:04.658515", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Plan Template", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.py b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.py new file mode 100644 index 0000000..f2f1d71 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document +from frappe.utils import cint, flt + +from healthcare.healthcare.doctype.therapy_type.therapy_type import make_item_price + + +class TherapyPlanTemplate(Document): + def after_insert(self): + if not self.link_existing_item: + self.create_item_from_template() + elif self.linked_item and self.total_amount: + make_item_price(self.linked_item, self.total_amount) + + def validate(self): + self.set_totals() + + def on_update(self): + doc_before_save = self.get_doc_before_save() + if not doc_before_save: + return + if ( + doc_before_save.item_name != self.item_name + or doc_before_save.item_group != self.item_group + or doc_before_save.description != self.description + ): + self.update_item() + + if doc_before_save.therapy_types != self.therapy_types: + self.update_item_price() + + def set_totals(self): + total_sessions = 0 + total_amount = 0 + + for entry in self.therapy_types: + total_sessions += cint(entry.no_of_sessions) + total_amount += flt(entry.amount) + + self.total_sessions = total_sessions + self.total_amount = total_amount + + def create_item_from_template(self): + uom = frappe.db.exists("UOM", "Nos") or frappe.db.get_single_value("Stock Settings", "stock_uom") + + item = frappe.get_doc( + { + "doctype": "Item", + "item_code": self.item_code, + "item_name": self.item_name, + "item_group": self.item_group, + "description": self.description, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "stock_uom": uom, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + make_item_price(item.name, self.total_amount) + self.db_set("linked_item", item.name) + + def update_item(self): + item_doc = frappe.get_doc("Item", {"item_code": self.linked_item}) + item_doc.item_name = self.item_name + item_doc.item_group = self.item_group + item_doc.description = self.description + item_doc.ignore_mandatory = True + item_doc.save(ignore_permissions=True) + + def update_item_price(self): + item_price = frappe.get_doc("Item Price", {"item_code": self.linked_item}) + item_price.item_name = self.item_name + item_price.price_list_rate = self.total_amount + item_price.ignore_mandatory = True + item_price.save(ignore_permissions=True) diff --git a/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py new file mode 100644 index 0000000..7c23ae8 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py @@ -0,0 +1,8 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "therapy_plan_template", + "transactions": [{"label": _("Therapy Plans"), "items": ["Therapy Plan"]}], + } diff --git a/healthcare/healthcare/doctype/therapy_plan_template_detail/__init__.py b/healthcare/healthcare/doctype/therapy_plan_template_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json b/healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json new file mode 100644 index 0000000..5553a11 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json @@ -0,0 +1,54 @@ +{ + "actions": [], + "creation": "2020-10-07 23:04:44.373381", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "therapy_type", + "no_of_sessions", + "rate", + "amount" + ], + "fields": [ + { + "fieldname": "therapy_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Therapy Type", + "options": "Therapy Type", + "reqd": 1 + }, + { + "fieldname": "no_of_sessions", + "fieldtype": "Int", + "in_list_view": 1, + "label": "No of Sessions" + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-10-07 23:46:54.296322", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Plan Template Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py b/healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py new file mode 100644 index 0000000..2225a2c --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +# import frappe +from frappe.model.document import Document + + +class TherapyPlanTemplateDetail(Document): + pass diff --git a/healthcare/healthcare/doctype/therapy_session/__init__.py b/healthcare/healthcare/doctype/therapy_session/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/therapy_session/test_therapy_session.py b/healthcare/healthcare/doctype/therapy_session/test_therapy_session.py new file mode 100644 index 0000000..caa29cc --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_session/test_therapy_session.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import nowdate + + +class TestTherapySession(FrappeTestCase): + pass + + +def create_therapy_session(patient, therapy_type, therapy_plan, duration=0, start_date=None): + if not start_date: + start_date = nowdate() + therapy_session = frappe.new_doc("Therapy Session") + therapy_session.patient = patient + therapy_session.therapy_type = therapy_type + therapy_session.therapy_plan = therapy_plan + therapy_session.duration = duration + therapy_session.start_date = start_date + therapy_session.save() + + return therapy_session diff --git a/healthcare/healthcare/doctype/therapy_session/therapy_session.js b/healthcare/healthcare/doctype/therapy_session/therapy_session.js new file mode 100644 index 0000000..6338749 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_session/therapy_session.js @@ -0,0 +1,171 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Therapy Session', { + setup: function(frm) { + frm.get_field('exercises').grid.editable_fields = [ + {fieldname: 'exercise_type', columns: 7}, + {fieldname: 'counts_target', columns: 1}, + {fieldname: 'counts_completed', columns: 1}, + {fieldname: 'assistance_level', columns: 1} + ]; + + frm.set_query('service_unit', function() { + return { + filters: { + 'is_group': false, + 'allow_appointments': true, + 'company': frm.doc.company + } + }; + }); + + frm.set_query('appointment', function() { + + return { + filters: { + 'status': ['in', ['Open', 'Scheduled']] + } + }; + }); + }, + + refresh: function(frm) { + if (frm.doc.therapy_plan) { + frm.trigger('filter_therapy_types'); + } + + if (!frm.doc.__islocal) { + frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue'); + frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]), + (frm.doc.total_counts_completed < frm.doc.total_counts_targeted) ? 'orange' : 'green'); + } + + if (frm.doc.docstatus === 1) { + frm.add_custom_button(__('Patient Assessment'), function() { + frappe.model.open_mapped_doc({ + method: 'healthcare.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment', + frm: frm, + }) + }, 'Create'); + + frappe.db.get_value('Therapy Plan', {'name': frm.doc.therapy_plan}, 'therapy_plan_template', (r) => { + if (r && !r.therapy_plan_template) { + frm.add_custom_button(__('Sales Invoice'), function() { + frappe.model.open_mapped_doc({ + method: 'healthcare.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session', + frm: frm, + }); + }, 'Create'); + } + }); + } + }, + + therapy_plan: function(frm) { + if (frm.doc.therapy_plan) { + frm.trigger('filter_therapy_types'); + } + }, + + filter_therapy_types: function(frm) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Therapy Plan', + name: frm.doc.therapy_plan + }, + callback: function(data) { + let therapy_types = (data.message.therapy_plan_details || []).map(function(d){ return d.therapy_type; }); + frm.set_query('therapy_type', function() { + return { + filters: { 'therapy_type': ['in', therapy_types]} + }; + }); + } + }); + }, + + patient: function(frm) { + if (frm.doc.patient) { + frappe.call({ + 'method': 'healthcare.healthcare.doctype.patient.patient.get_patient_detail', + args: { + patient: frm.doc.patient + }, + callback: function (data) { + let age = ''; + if (data.message.dob) { + age = calculate_age(data.message.dob); + } else if (data.message.age) { + age = data.message.age; + if (data.message.age_as_on) { + age = __('{0} as on {1}', [age, data.message.age_as_on]); + } + } + frm.set_value('patient_age', age); + frm.set_value('gender', data.message.sex); + frm.set_value('patient_name', data.message.patient_name); + } + }); + } else { + frm.set_value('patient_age', ''); + frm.set_value('gender', ''); + frm.set_value('patient_name', ''); + } + }, + + appointment: function(frm) { + if (frm.doc.appointment) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Patient Appointment', + name: frm.doc.appointment + }, + callback: function(data) { + let values = { + 'patient':data.message.patient, + 'therapy_type': data.message.therapy_type, + 'therapy_plan': data.message.therapy_plan, + 'practitioner': data.message.practitioner, + 'department': data.message.department, + 'start_date': data.message.appointment_date, + 'start_time': data.message.appointment_time, + 'service_unit': data.message.service_unit, + 'company': data.message.company, + 'duration': data.message.duration + }; + frm.set_value(values); + } + }); + } + }, + + therapy_type: function(frm) { + if (frm.doc.therapy_type) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Therapy Type', + name: frm.doc.therapy_type + }, + callback: function(data) { + frm.set_value('duration', data.message.default_duration); + frm.set_value('rate', data.message.rate); + frm.set_value('service_unit', data.message.healthcare_service_unit); + frm.set_value('department', data.message.medical_department); + frm.doc.exercises = []; + $.each(data.message.exercises, function(_i, e) { + let exercise = frm.add_child('exercises'); + exercise.exercise_type = e.exercise_type; + exercise.difficulty_level = e.difficulty_level; + exercise.counts_target = e.counts_target; + exercise.assistance_level = e.assistance_level; + }); + refresh_field('exercises'); + } + }); + } + } +}); diff --git a/healthcare/healthcare/doctype/therapy_session/therapy_session.json b/healthcare/healthcare/doctype/therapy_session/therapy_session.json new file mode 100644 index 0000000..c57b9bf --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_session/therapy_session.json @@ -0,0 +1,270 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-03-11 08:57:40.669857", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "appointment", + "patient", + "patient_name", + "patient_age", + "gender", + "column_break_5", + "company", + "therapy_plan", + "therapy_type", + "practitioner", + "department", + "details_section", + "medical_code", + "duration", + "rate", + "location", + "column_break_12", + "service_unit", + "start_date", + "start_time", + "invoiced", + "exercises_section", + "exercises", + "section_break_23", + "total_counts_targeted", + "column_break_25", + "total_counts_completed", + "amended_from" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-THP-.YYYY.-" + }, + { + "fieldname": "appointment", + "fieldtype": "Link", + "label": "Appointment", + "options": "Patient Appointment", + "set_only_once": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "department", + "fieldtype": "Link", + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fetch_from": "therapy_template.default_duration", + "fieldname": "duration", + "fieldtype": "Int", + "label": "Duration", + "reqd": 1 + }, + { + "fieldname": "location", + "fieldtype": "Select", + "label": "Location", + "options": "\nCenter\nHome\nTele" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fetch_from": "therapy_template.rate", + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate" + }, + { + "fieldname": "exercises_section", + "fieldtype": "Section Break", + "label": "Exercises" + }, + { + "fieldname": "exercises", + "fieldtype": "Table", + "label": "Exercises", + "options": "Exercise" + }, + { + "depends_on": "eval: doc.therapy_plan", + "fieldname": "therapy_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Therapy Type", + "options": "Therapy Type", + "reqd": 1 + }, + { + "fieldname": "therapy_plan", + "fieldtype": "Link", + "label": "Therapy Plan", + "options": "Therapy Plan", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Therapy Session", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "service_unit", + "fieldtype": "Link", + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date", + "reqd": 1 + }, + { + "fieldname": "start_time", + "fieldtype": "Time", + "label": "Start Time" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "label": "Patient Age", + "read_only": 1 + }, + { + "fieldname": "total_counts_targeted", + "fieldtype": "Int", + "label": "Total Counts Targeted", + "read_only": 1 + }, + { + "fieldname": "total_counts_completed", + "fieldtype": "Int", + "label": "Total Counts Completed", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fetch_from": "therapy_type.medical_code", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code", + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [ + { + "link_doctype": "Nursing Task", + "link_fieldname": "reference_name" + } + ], + "modified": "2022-01-17 15:11:07.822895", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Session", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 1, + "search_fields": "patient,appointment,therapy_plan,therapy_type", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/therapy_session/therapy_session.py b/healthcare/healthcare/doctype/therapy_session/therapy_session.py new file mode 100644 index 0000000..1da96da --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_session/therapy_session.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import datetime + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import flt, get_link_to_form, get_time, getdate + +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import ( + get_income_account, + get_receivable_account, +) +from healthcare.healthcare.doctype.nursing_task.nursing_task import NursingTask +from healthcare.healthcare.utils import validate_nursing_tasks + + +class TherapySession(Document): + def validate(self): + self.validate_duplicate() + self.set_total_counts() + + def validate_duplicate(self): + end_time = datetime.datetime.combine( + getdate(self.start_date), get_time(self.start_time) + ) + datetime.timedelta(minutes=flt(self.duration)) + + overlaps = frappe.db.sql( + """ + select + name + from + `tabTherapy Session` + where + start_date=%s and name!=%s and docstatus!=2 + and (practitioner=%s or patient=%s) and + ((start_time<%s and start_time + INTERVAL duration MINUTE>%s) or + (start_time>%s and start_time<%s) or + (start_time=%s)) + """, + ( + self.start_date, + self.name, + self.practitioner, + self.patient, + self.start_time, + end_time.time(), + self.start_time, + end_time.time(), + self.start_time, + ), + ) + + if overlaps: + overlapping_details = _("Therapy Session overlaps with {0}").format( + get_link_to_form("Therapy Session", overlaps[0][0]) + ) + frappe.throw(overlapping_details, title=_("Therapy Sessions Overlapping")) + + def on_submit(self): + validate_nursing_tasks(self) + self.update_sessions_count_in_therapy_plan() + + def on_update(self): + if self.appointment: + frappe.db.set_value("Patient Appointment", self.appointment, "status", "Closed") + + def on_cancel(self): + if self.appointment: + frappe.db.set_value("Patient Appointment", self.appointment, "status", "Open") + + self.update_sessions_count_in_therapy_plan(on_cancel=True) + + def after_insert(self): + self.create_nursing_tasks(post_event=False) + + def create_nursing_tasks(self, post_event=True): + template = frappe.db.get_value("Therapy Type", self.therapy_type, "nursing_checklist_template") + if template: + NursingTask.create_nursing_tasks_from_template( + template, + self, + start_time=frappe.utils.get_datetime(f"{self.start_date} {self.start_time}"), + post_event=post_event, + ) + + def update_sessions_count_in_therapy_plan(self, on_cancel=False): + therapy_plan = frappe.get_doc("Therapy Plan", self.therapy_plan) + for entry in therapy_plan.therapy_plan_details: + if entry.therapy_type == self.therapy_type: + if on_cancel: + entry.sessions_completed -= 1 + else: + entry.sessions_completed += 1 + therapy_plan.save() + + def set_total_counts(self): + target_total = 0 + counts_completed = 0 + for entry in self.exercises: + if entry.counts_target: + target_total += entry.counts_target + if entry.counts_completed: + counts_completed += entry.counts_completed + + self.db_set("total_counts_targeted", target_total) + self.db_set("total_counts_completed", counts_completed) + + +@frappe.whitelist() +def create_therapy_session(source_name, target_doc=None): + def set_missing_values(source, target): + therapy_type = frappe.get_doc("Therapy Type", source.therapy_type) + target.exercises = therapy_type.exercises + + doc = get_mapped_doc( + "Patient Appointment", + source_name, + { + "Patient Appointment": { + "doctype": "Therapy Session", + "field_map": [ + ["appointment", "name"], + ["patient", "patient"], + ["patient_age", "patient_age"], + ["gender", "patient_sex"], + ["therapy_type", "therapy_type"], + ["therapy_plan", "therapy_plan"], + ["practitioner", "practitioner"], + ["department", "department"], + ["start_date", "appointment_date"], + ["start_time", "appointment_time"], + ["service_unit", "service_unit"], + ["company", "company"], + ["invoiced", "invoiced"], + ], + } + }, + target_doc, + set_missing_values, + ) + + return doc + + +@frappe.whitelist() +def invoice_therapy_session(source_name, target_doc=None): + def set_missing_values(source, target): + target.customer = frappe.db.get_value("Patient", source.patient, "customer") + target.due_date = getdate() + target.debit_to = get_receivable_account(source.company) + item = target.append("items", {}) + item = get_therapy_item(source, item) + target.set_missing_values(for_validate=True) + + doc = get_mapped_doc( + "Therapy Session", + source_name, + { + "Therapy Session": { + "doctype": "Sales Invoice", + "field_map": [ + ["patient", "patient"], + ["referring_practitioner", "practitioner"], + ["company", "company"], + ["due_date", "start_date"], + ], + } + }, + target_doc, + set_missing_values, + ) + + return doc + + +def get_therapy_item(therapy, item): + item.item_code = frappe.db.get_value("Therapy Type", therapy.therapy_type, "item") + item.description = _("Therapy Session Charges: {0}").format(therapy.practitioner) + item.income_account = get_income_account(therapy.practitioner, therapy.company) + item.cost_center = frappe.get_cached_value("Company", therapy.company, "cost_center") + item.rate = therapy.rate + item.amount = therapy.rate + item.qty = 1 + item.reference_dt = "Therapy Session" + item.reference_dn = therapy.name + return item diff --git a/healthcare/healthcare/doctype/therapy_session/therapy_session_dashboard.py b/healthcare/healthcare/doctype/therapy_session/therapy_session_dashboard.py new file mode 100644 index 0000000..9ceb0d7 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_session/therapy_session_dashboard.py @@ -0,0 +1,8 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "therapy_session", + "transactions": [{"label": _("Assessments"), "items": ["Patient Assessment"]}], + } diff --git a/healthcare/healthcare/doctype/therapy_type/__init__.py b/healthcare/healthcare/doctype/therapy_type/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/therapy_type/test_therapy_type.py b/healthcare/healthcare/doctype/therapy_type/test_therapy_type.py new file mode 100644 index 0000000..8619eab --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_type/test_therapy_type.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +import frappe + + +class TestTherapyType(FrappeTestCase): + def test_therapy_type_item(self): + therapy_type = create_therapy_type() + self.assertTrue(frappe.db.exists("Item", therapy_type.item)) + + therapy_type.disabled = 1 + therapy_type.save() + self.assertEqual(frappe.db.get_value("Item", therapy_type.item, "disabled"), 1) + + +def create_therapy_type(): + exercise = create_exercise_type() + therapy_type = frappe.db.exists("Therapy Type", "Basic Rehab") + if not therapy_type: + therapy_type = frappe.new_doc("Therapy Type") + therapy_type.therapy_type = "Basic Rehab" + therapy_type.default_duration = 30 + therapy_type.is_billable = 1 + therapy_type.rate = 5000 + therapy_type.item_code = "Basic Rehab" + therapy_type.item_name = "Basic Rehab" + therapy_type.item_group = "Services" + therapy_type.append( + "exercises", + {"exercise_type": exercise.name, "counts_target": 10, "assistance_level": "Passive"}, + ) + therapy_type.save() + else: + therapy_type = frappe.get_doc("Therapy Type", therapy_type) + + return therapy_type + + +def create_exercise_type(): + exercise_type = frappe.db.exists("Exercise Type", "Sit to Stand") + if not exercise_type: + exercise_type = frappe.new_doc("Exercise Type") + exercise_type.exercise_name = "Sit to Stand" + exercise_type.append("steps_table", {"title": "Step 1", "description": "Squat and Rise"}) + exercise_type.save() + else: + exercise_type = frappe.get_doc("Exercise Type", exercise_type) + + return exercise_type diff --git a/healthcare/healthcare/doctype/therapy_type/therapy_type.js b/healthcare/healthcare/doctype/therapy_type/therapy_type.js new file mode 100644 index 0000000..ae76343 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_type/therapy_type.js @@ -0,0 +1,103 @@ +// Copyright (c) 2020, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Therapy Type', { + setup: function(frm) { + frm.get_field('exercises').grid.editable_fields = [ + {fieldname: 'exercise_type', columns: 7}, + {fieldname: 'difficulty_level', columns: 1}, + {fieldname: 'counts_target', columns: 1}, + {fieldname: 'assistance_level', columns: 1} + ]; + }, + + refresh: function(frm) { + if (!frm.doc.__islocal) { + cur_frm.add_custom_button(__('Change Item Code'), function() { + change_template_code(frm.doc); + }); + } + }, + + therapy_type: function(frm) { + if (!frm.doc.item_code) + frm.set_value('item_code', frm.doc.therapy_type); + if (!frm.doc.description) + frm.set_value('description', frm.doc.therapy_type); + mark_change_in_item(frm); + }, + + rate: function(frm) { + mark_change_in_item(frm); + }, + + is_billable: function (frm) { + mark_change_in_item(frm); + }, + + item_group: function(frm) { + mark_change_in_item(frm); + }, + + description: function(frm) { + mark_change_in_item(frm); + }, + + medical_department: function(frm) { + mark_change_in_item(frm); + }, + + medical_code: function(frm) { + frm.set_query("medical_code", function() { + return { + filters: { + medical_code_standard: frm.doc.medical_code_standard + } + }; + }); + } +}); + +let mark_change_in_item = function(frm) { + if (!frm.doc.__islocal) { + frm.doc.change_in_item = 1; + } +}; + +let change_template_code = function(doc) { + let d = new frappe.ui.Dialog({ + title:__('Change Item Code'), + fields:[ + { + 'fieldtype': 'Data', + 'label': 'Item Code', + 'fieldname': 'item_code', + reqd: 1 + } + ], + primary_action: function() { + let values = d.get_values(); + + if (values) { + frappe.call({ + 'method': 'healthcare.healthcare.doctype.therapy_type.therapy_type.change_item_code_from_therapy', + 'args': {item_code: values.item_code, doc: doc}, + callback: function () { + cur_frm.reload_doc(); + frappe.show_alert({ + message: 'Item Code renamed successfully', + indicator: 'green' + }); + } + }); + } + d.hide(); + }, + primary_action_label: __('Change Item Code') + }); + d.show(); + + d.set_values({ + 'item_code': doc.item_code + }); +}; diff --git a/healthcare/healthcare/doctype/therapy_type/therapy_type.json b/healthcare/healthcare/doctype/therapy_type/therapy_type.json new file mode 100644 index 0000000..32b4a18 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_type/therapy_type.json @@ -0,0 +1,242 @@ +{ + "actions": [], + "autoname": "field:therapy_type", + "creation": "2020-03-29 20:48:31.715063", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "disabled", + "section_break_2", + "therapy_type", + "default_duration", + "medical_department", + "nursing_checklist_template", + "column_break_3", + "is_billable", + "rate", + "healthcare_service_unit", + "item_details_section", + "item", + "item_code", + "item_name", + "item_group", + "column_break_12", + "description", + "medical_coding_section", + "medical_code_standard", + "medical_code", + "section_break_18", + "therapy_for", + "add_exercises", + "section_break_6", + "exercises", + "change_in_item" + ], + "fields": [ + { + "fieldname": "therapy_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Therapy Type", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_billable", + "fieldtype": "Check", + "label": "Is Billable" + }, + { + "depends_on": "eval:doc.is_billable;", + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "mandatory_depends_on": "eval:doc.is_billable;" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Exercises" + }, + { + "fieldname": "exercises", + "fieldtype": "Table", + "label": "Exercises", + "options": "Exercise" + }, + { + "fieldname": "default_duration", + "fieldtype": "Int", + "label": "Default Duration (In Minutes)" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "fieldname": "item_details_section", + "fieldtype": "Section Break", + "label": "Item Details" + }, + { + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Data", + "label": "Item Code", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 1 + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "reqd": 1 + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "label": "Medical Department", + "options": "Medical Department" + }, + { + "default": "0", + "fieldname": "change_in_item", + "fieldtype": "Check", + "hidden": 1, + "label": "Change In Item", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "therapy_for", + "fieldtype": "Table MultiSelect", + "label": "Therapy For", + "options": "Body Part Link" + }, + { + "fieldname": "healthcare_service_unit", + "fieldtype": "Link", + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit" + }, + { + "depends_on": "eval: doc.therapy_for", + "fieldname": "add_exercises", + "fieldtype": "Button", + "label": "Add Exercises", + "options": "add_exercises" + }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "medical_coding_section", + "fieldtype": "Section Break", + "label": "Medical Coding", + "options": "Medical Coding" + }, + { + "fieldname": "medical_code_standard", + "fieldtype": "Link", + "label": "Medical Code Standard", + "options": "Medical Code Standard" + }, + { + "depends_on": "medical_code_standard", + "fieldname": "medical_code", + "fieldtype": "Link", + "label": "Medical Code", + "options": "Medical Code" + }, + { + "fieldname": "nursing_checklist_template", + "fieldtype": "Link", + "label": "Nursing Checklist Template", + "options": "Nursing Checklist Template" + } + ], + "links": [], + "modified": "2022-01-17 14:45:27.792677", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Type", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/therapy_type/therapy_type.py b/healthcare/healthcare/doctype/therapy_type/therapy_type.py new file mode 100644 index 0000000..f55d872 --- /dev/null +++ b/healthcare/healthcare/doctype/therapy_type/therapy_type.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc +from frappe.utils import cint + + +class TherapyType(Document): + def validate(self): + self.enable_disable_item() + + def after_insert(self): + create_item_from_therapy(self) + + def on_update(self): + if self.change_in_item: + self.update_item_and_item_price() + + def enable_disable_item(self): + if self.is_billable: + if self.disabled: + frappe.db.set_value("Item", self.item, "disabled", 1) + else: + frappe.db.set_value("Item", self.item, "disabled", 0) + + def update_item_and_item_price(self): + if self.is_billable and self.item: + item_doc = frappe.get_doc("Item", {"item_code": self.item}) + item_doc.item_name = self.item_name + item_doc.item_group = self.item_group + item_doc.description = self.description + item_doc.disabled = 0 + item_doc.ignore_mandatory = True + item_doc.save(ignore_permissions=True) + + if self.rate: + item_price = frappe.get_doc("Item Price", {"item_code": self.item}) + item_price.item_name = self.item_name + item_price.price_list_rate = self.rate + item_price.ignore_mandatory = True + item_price.save() + + elif not self.is_billable and self.item: + frappe.db.set_value("Item", self.item, "disabled", 1) + + self.db_set("change_in_item", 0) + + @frappe.whitelist() + def add_exercises(self): + exercises = self.get_exercises_for_body_parts() + last_idx = max( + [cint(d.idx) for d in self.get("exercises")] + or [ + 0, + ] + ) + for i, d in enumerate(exercises): + ch = self.append("exercises", {}) + ch.exercise_type = d.parent + ch.idx = last_idx + i + 1 + + def get_exercises_for_body_parts(self): + body_parts = [entry.body_part for entry in self.therapy_for] + + exercises = frappe.db.sql( + """ + SELECT DISTINCT + b.parent, e.name, e.difficulty_level + FROM + `tabExercise Type` e, `tabBody Part Link` b + WHERE + b.body_part IN %(body_parts)s AND b.parent=e.name + """, + {"body_parts": body_parts}, + as_dict=1, + ) + + return exercises + + +def create_item_from_therapy(doc): + disabled = doc.disabled + if doc.is_billable and not doc.disabled: + disabled = 0 + + uom = frappe.db.exists("UOM", "Unit") or frappe.db.get_single_value("Stock Settings", "stock_uom") + + item = frappe.get_doc( + { + "doctype": "Item", + "item_code": doc.item_code, + "item_name": doc.item_name, + "item_group": doc.item_group, + "description": doc.description, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "disabled": disabled, + "stock_uom": uom, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + make_item_price(item.name, doc.rate) + doc.db_set("item", item.name) + + +def make_item_price(item, item_price): + price_list_name = frappe.db.get_value("Price List", {"selling": 1}) + frappe.get_doc( + { + "doctype": "Item Price", + "price_list": price_list_name, + "item_code": item, + "price_list_rate": item_price, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + +@frappe.whitelist() +def change_item_code_from_therapy(item_code, doc): + doc = frappe._dict(json.loads(doc)) + + if frappe.db.exists("Item", {"item_code": item_code}): + frappe.throw(_("Item with Item Code {0} already exists").format(item_code)) + else: + rename_doc("Item", doc.item, item_code, ignore_permissions=True) + frappe.db.set_value("Therapy Type", doc.name, "item_code", item_code) + return diff --git a/healthcare/healthcare/doctype/treatment_plan_template/__init__.py b/healthcare/healthcare/doctype/treatment_plan_template/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/treatment_plan_template/test_records.json b/healthcare/healthcare/doctype/treatment_plan_template/test_records.json new file mode 100644 index 0000000..d661b43 --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template/test_records.json @@ -0,0 +1,7 @@ +[ + { + "doctype": "Treatment Plan Template", + "template_name": "Chemo", + "patient_age_from": 21 + } +] diff --git a/healthcare/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py b/healthcare/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py new file mode 100644 index 0000000..12ed8d5 --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, InfluxERP +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestTreatmentPlanTemplate(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.js b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.js new file mode 100644 index 0000000..e8de9ab --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.js @@ -0,0 +1,14 @@ +// Copyright (c) 2021, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Treatment Plan Template', { + refresh: function (frm) { + frm.set_query('type', 'items', function () { + return { + filters: { + 'name': ['in', ['Lab Test Template', 'Clinical Procedure Template', 'Therapy Type']], + } + }; + }); + }, +}); diff --git a/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.json b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.json new file mode 100644 index 0000000..85a312f --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.json @@ -0,0 +1,189 @@ +{ + "actions": [], + "autoname": "field:template_name", + "creation": "2021-06-10 10:14:17.901273", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "template_name", + "description", + "practitioners", + "disabled", + "column_break_1", + "medical_department", + "goal", + "order_group", + "section_break_8", + "patient_age_from", + "complaints", + "gender", + "column_break_12", + "patient_age_to", + "diagnosis", + "plan_items_section", + "items", + "drugs" + ], + "fields": [ + { + "fieldname": "section_break_1", + "fieldtype": "Section Break", + "label": "Plan Details" + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "goal", + "fieldtype": "Small Text", + "label": "Goal" + }, + { + "fieldname": "practitioners", + "fieldtype": "Table MultiSelect", + "label": "Practitioners", + "options": "Treatment Plan Template Practitioner" + }, + { + "fieldname": "order_group", + "fieldtype": "Link", + "label": "Order Group", + "options": "Patient Encounter", + "read_only": 1 + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Plan Conditions" + }, + { + "fieldname": "template_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Template Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "patient_age_from", + "fieldtype": "Int", + "label": "Patient Age From", + "non_negative": 1 + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "patient_age_to", + "fieldtype": "Int", + "label": "Patient Age To", + "non_negative": 1 + }, + { + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender" + }, + { + "fieldname": "complaints", + "fieldtype": "Table MultiSelect", + "label": "Complaints", + "options": "Patient Encounter Symptom" + }, + { + "fieldname": "diagnosis", + "fieldtype": "Table MultiSelect", + "label": "Diagnosis", + "options": "Patient Encounter Diagnosis" + }, + { + "fieldname": "plan_items_section", + "fieldtype": "Section Break", + "label": "Plan Items" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Treatment Plan Template Item" + }, + { + "fieldname": "drugs", + "fieldtype": "Table", + "label": "Drugs", + "options": "Drug Prescription" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-08-18 02:41:58.354296", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Treatment Plan Template", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "template_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.py b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.py new file mode 100644 index 0000000..093568e --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021, InfluxERP +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class TreatmentPlanTemplate(Document): + def validate(self): + self.validate_age() + + def validate_age(self): + if self.patient_age_from and self.patient_age_from < 0: + frappe.throw(_("Patient Age From cannot be less than 0")) + if self.patient_age_to and self.patient_age_to < 0: + frappe.throw(_("Patient Age To cannot be less than 0")) + if self.patient_age_to and self.patient_age_from and self.patient_age_to < self.patient_age_from: + frappe.throw(_("Patient Age To cannot be less than Patient Age From")) diff --git a/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js new file mode 100644 index 0000000..7ab31df --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js @@ -0,0 +1,10 @@ +frappe.listview_settings['Treatment Plan Template'] = { + get_indicator: function(doc) { + var colors = { + 1: 'gray', + 0: 'blue', + }; + let label = doc.disabled == 1 ? 'Disabled' : 'Enabled'; + return [__(label), colors[doc.disabled], 'disable,=,' + doc.disabled]; + } +}; diff --git a/healthcare/healthcare/doctype/treatment_plan_template_item/__init__.py b/healthcare/healthcare/doctype/treatment_plan_template_item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json b/healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json new file mode 100644 index 0000000..20a9d67 --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "creation": "2021-06-10 11:47:29.194795", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "type", + "template", + "qty", + "instructions" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "template", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Template", + "options": "type", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "qty", + "fieldtype": "Int", + "label": "Qty" + }, + { + "fieldname": "instructions", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Instructions" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-17 11:19:03.515441", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Treatment Plan Template Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py b/healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py new file mode 100644 index 0000000..03ccfc9 --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, InfluxERP +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TreatmentPlanTemplateItem(Document): + pass diff --git a/healthcare/healthcare/doctype/treatment_plan_template_practitioner/__init__.py b/healthcare/healthcare/doctype/treatment_plan_template_practitioner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json b/healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json new file mode 100644 index 0000000..04da387 --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2021-06-10 10:37:56.669416", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "practitioner" + ], + "fields": [ + { + "fieldname": "practitioner", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Practitioner", + "options": "Healthcare Practitioner", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-11 16:05:06.733299", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Treatment Plan Template Practitioner", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py b/healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py new file mode 100644 index 0000000..1269d0f --- /dev/null +++ b/healthcare/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, InfluxERP +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TreatmentPlanTemplatePractitioner(Document): + pass diff --git a/healthcare/healthcare/doctype/vital_signs/__init__.py b/healthcare/healthcare/doctype/vital_signs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/doctype/vital_signs/test_vital_signs.py b/healthcare/healthcare/doctype/vital_signs/test_vital_signs.py new file mode 100644 index 0000000..b9cb358 --- /dev/null +++ b/healthcare/healthcare/doctype/vital_signs/test_vital_signs.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and Contributors +# See license.txt + + +from frappe.tests.utils import FrappeTestCase + +# test_records = frappe.get_test_records('Vital Signs') + + +class TestVitalSigns(FrappeTestCase): + pass diff --git a/healthcare/healthcare/doctype/vital_signs/vital_signs.js b/healthcare/healthcare/doctype/vital_signs/vital_signs.js new file mode 100644 index 0000000..b7a7d63 --- /dev/null +++ b/healthcare/healthcare/doctype/vital_signs/vital_signs.js @@ -0,0 +1,52 @@ +// Copyright (c) 2016, ESS LLP and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Vital Signs', { + height: function(frm) { + if (frm.doc.height && frm.doc.weight) { + calculate_bmi(frm); + } + }, + + weight: function(frm) { + if (frm.doc.height && frm.doc.weight) { + calculate_bmi(frm); + } + }, + + bp_systolic: function(frm) { + if (frm.doc.bp_systolic && frm.doc.bp_diastolic) { + set_bp(frm); + } + }, + + bp_diastolic: function(frm) { + if (frm.doc.bp_systolic && frm.doc.bp_diastolic) { + set_bp(frm); + } + } +}); + +let calculate_bmi = function(frm){ + // Reference https://en.wikipedia.org/wiki/Body_mass_index + // bmi = weight (in Kg) / height * height (in Meter) + let bmi = (frm.doc.weight / (frm.doc.height * frm.doc.height)).toFixed(2); + let bmi_note = null; + + if (bmi<18.5) { + bmi_note = __('Underweight'); + } else if (bmi>=18.5 && bmi<25) { + bmi_note = __('Normal'); + } else if (bmi>=25 && bmi<30) { + bmi_note = __('Overweight'); + } else if (bmi>=30) { + bmi_note = __('Obese'); + } + frappe.model.set_value(frm.doctype,frm.docname, 'bmi', bmi); + frappe.model.set_value(frm.doctype,frm.docname, 'nutrition_note', bmi_note); +}; + +let set_bp = function(frm){ + let bp = frm.doc.bp_systolic+ '/' + frm.doc.bp_diastolic + ' mmHg'; + frappe.model.set_value(frm.doctype,frm.docname, 'bp', bp); +}; diff --git a/healthcare/healthcare/doctype/vital_signs/vital_signs.json b/healthcare/healthcare/doctype/vital_signs/vital_signs.json new file mode 100644 index 0000000..15ab504 --- /dev/null +++ b/healthcare/healthcare/doctype/vital_signs/vital_signs.json @@ -0,0 +1,305 @@ +{ + "actions": [], + "allow_copy": 1, + "allow_import": 1, + "autoname": "naming_series:", + "beta": 1, + "creation": "2017-02-02 11:00:24.853005", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "title", + "patient", + "patient_name", + "inpatient_record", + "appointment", + "encounter", + "column_break_2", + "company", + "signs_date", + "signs_time", + "sb_vs", + "temperature", + "pulse", + "respiratory_rate", + "tongue", + "abdomen", + "column_break_8", + "reflexes", + "bp_systolic", + "bp_diastolic", + "bp", + "vital_signs_note", + "sb_nutrition_values", + "height", + "weight", + "bmi", + "column_break_14", + "nutrition_note", + "sb_references", + "amended_from" + ], + "fields": [ + { + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "read_only": 1 + }, + { + "fetch_from": "inpatient_record.patient", + "fieldname": "patient", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fieldname": "appointment", + "fieldtype": "Link", + "in_filter": 1, + "label": "Patient Appointment", + "no_copy": 1, + "options": "Patient Appointment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "encounter", + "fieldtype": "Link", + "in_filter": 1, + "label": "Patient Encounter", + "no_copy": 1, + "options": "Patient Encounter", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "signs_date", + "fieldtype": "Date", + "label": "Date", + "reqd": 1 + }, + { + "fieldname": "signs_time", + "fieldtype": "Time", + "label": "Time", + "reqd": 1 + }, + { + "fieldname": "sb_vs", + "fieldtype": "Section Break", + "label": "Vital Signs" + }, + { + "description": "Presence of a fever (temp > 38.5 \u00b0C/101.3 \u00b0F or sustained temp > 38 \u00b0C/100.4 \u00b0F)", + "fieldname": "temperature", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Body Temperature" + }, + { + "description": "Adults' pulse rate is anywhere between 50 and 80 beats per minute.", + "fieldname": "pulse", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Heart Rate / Pulse" + }, + { + "description": "Normal reference range for an adult is 16\u201320 breaths/minute (RCP 2012)", + "fieldname": "respiratory_rate", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Respiratory rate" + }, + { + "fieldname": "tongue", + "fieldtype": "Select", + "label": "Tongue", + "options": "\nCoated\nVery Coated\nNormal\nFurry\nCuts" + }, + { + "fieldname": "abdomen", + "fieldtype": "Select", + "label": "Abdomen", + "options": "\nNormal\nBloated\nFull\nFluid\nConstipated" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "reflexes", + "fieldtype": "Select", + "label": "Reflexes", + "options": "\nNormal\nHyper\nVery Hyper\nOne Sided" + }, + { + "fieldname": "bp_systolic", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Blood Pressure (systolic)" + }, + { + "fieldname": "bp_diastolic", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "in_list_view": 1, + "label": "Blood Pressure (diastolic)" + }, + { + "description": "Normal resting blood pressure in an adult is approximately 120 mmHg systolic, and 80 mmHg diastolic, abbreviated \"120/80 mmHg\"", + "fieldname": "bp", + "fieldtype": "Data", + "label": "Blood Pressure", + "read_only": 1 + }, + { + "fieldname": "vital_signs_note", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Notes" + }, + { + "fieldname": "sb_nutrition_values", + "fieldtype": "Section Break", + "label": "Nutrition Values" + }, + { + "fieldname": "height", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Height (In Meter)" + }, + { + "fieldname": "weight", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Weight (In Kilogram)" + }, + { + "default": "0.00", + "fieldname": "bmi", + "fieldtype": "Float", + "in_list_view": 1, + "label": "BMI", + "read_only": 1 + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "nutrition_note", + "fieldtype": "Small Text", + "ignore_xss_filter": 1, + "label": "Notes" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Vital Signs", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_references", + "fieldtype": "Section Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-VTS-.YYYY.-", + "reqd": 1 + }, + { + "allow_on_submit": 1, + "columns": 5, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-05-17 22:23:24.632286", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Vital Signs", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient, signs_date", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/vital_signs/vital_signs.py b/healthcare/healthcare/doctype/vital_signs/vital_signs.py new file mode 100644 index 0000000..41f3d2a --- /dev/null +++ b/healthcare/healthcare/doctype/vital_signs/vital_signs.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, ESS LLP and contributors +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class VitalSigns(Document): + def validate(self): + self.set_title() + + def set_title(self): + self.title = _("{0} on {1}").format( + self.patient_name or self.patient, frappe.utils.format_date(self.signs_date) + )[:100] diff --git a/healthcare/healthcare/healthcare_dashboard/healthcare/healthcare.json b/healthcare/healthcare/healthcare_dashboard/healthcare/healthcare.json new file mode 100644 index 0000000..2fea668 --- /dev/null +++ b/healthcare/healthcare/healthcare_dashboard/healthcare/healthcare.json @@ -0,0 +1,62 @@ +{ + "cards": [ + { + "card": "Total Patients" + }, + { + "card": "Total Patients Admitted" + }, + { + "card": "Open Appointments" + }, + { + "card": "Appointments to Bill" + } + ], + "charts": [ + { + "chart": "Patient Appointments", + "width": "Full" + }, + { + "chart": "In-Patient Status", + "width": "Half" + }, + { + "chart": "Clinical Procedures Status", + "width": "Half" + }, + { + "chart": "Lab Tests", + "width": "Half" + }, + { + "chart": "Clinical Procedures", + "width": "Half" + }, + { + "chart": "Symptoms", + "width": "Half" + }, + { + "chart": "Diagnoses", + "width": "Half" + }, + { + "chart": "Department wise Patient Appointments", + "width": "Full" + } + ], + "creation": "2020-07-14 18:17:54.823311", + "dashboard_name": "Healthcare", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2020-07-22 15:36:34.220387", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare", + "owner": "Administrator" +} \ No newline at end of file diff --git a/healthcare/healthcare/module_onboarding/healthcare/healthcare.json b/healthcare/healthcare/module_onboarding/healthcare/healthcare.json new file mode 100644 index 0000000..e3b120a --- /dev/null +++ b/healthcare/healthcare/module_onboarding/healthcare/healthcare.json @@ -0,0 +1,41 @@ +{ + "allow_roles": [ + { + "role": "Healthcare Administrator" + } + ], + "creation": "2020-05-19 10:32:43.025852", + "docstatus": 0, + "doctype": "Module Onboarding", + "documentation_url": "https://docs.influxerp.com/docs/user/manual/en/healthcare", + "idx": 0, + "is_complete": 0, + "modified": "2021-01-30 19:22:20.273766", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare", + "owner": "Administrator", + "steps": [ + { + "step": "Create Patient" + }, + { + "step": "Create Practitioner Schedule" + }, + { + "step": "Introduction to Healthcare Practitioner" + }, + { + "step": "Create Healthcare Practitioner" + }, + { + "step": "Explore Healthcare Settings" + }, + { + "step": "Explore Clinical Procedure Templates" + } + ], + "subtitle": "Patients, Practitioner Schedules, Settings, and more.", + "success_message": "The Healthcare Module is all set up!", + "title": "Let's Set Up the Healthcare Module." +} \ No newline at end of file diff --git a/healthcare/healthcare/number_card/appointments_to_bill/appointments_to_bill.json b/healthcare/healthcare/number_card/appointments_to_bill/appointments_to_bill.json new file mode 100644 index 0000000..3e4d4e2 --- /dev/null +++ b/healthcare/healthcare/number_card/appointments_to_bill/appointments_to_bill.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-14 18:17:54.792773", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient Appointment", + "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Patient Appointment\",\"invoiced\",\"=\",0,false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Appointments To Bill", + "modified": "2020-07-22 13:27:58.038577", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Appointments to Bill", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/healthcare/healthcare/number_card/open_appointments/open_appointments.json b/healthcare/healthcare/number_card/open_appointments/open_appointments.json new file mode 100644 index 0000000..8d121cc --- /dev/null +++ b/healthcare/healthcare/number_card/open_appointments/open_appointments.json @@ -0,0 +1,21 @@ +{ + "creation": "2020-07-14 18:17:54.771092", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient Appointment", + "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", + "filters_json": "[[\"Patient Appointment\",\"status\",\"=\",\"Open\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Open Appointments", + "modified": "2020-07-22 13:27:09.542122", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Open Appointments", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/healthcare/healthcare/number_card/total_patients/total_patients.json b/healthcare/healthcare/number_card/total_patients/total_patients.json new file mode 100644 index 0000000..75441a6 --- /dev/null +++ b/healthcare/healthcare/number_card/total_patients/total_patients.json @@ -0,0 +1,20 @@ +{ + "creation": "2020-07-14 18:17:54.727946", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient", + "filters_json": "[[\"Patient\",\"status\",\"=\",\"Active\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Patients", + "modified": "2020-07-22 13:26:02.643534", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Total Patients", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/healthcare/healthcare/number_card/total_patients_admitted/total_patients_admitted.json b/healthcare/healthcare/number_card/total_patients_admitted/total_patients_admitted.json new file mode 100644 index 0000000..69a967d --- /dev/null +++ b/healthcare/healthcare/number_card/total_patients_admitted/total_patients_admitted.json @@ -0,0 +1,20 @@ +{ + "creation": "2020-07-14 18:17:54.749754", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Patient", + "filters_json": "[[\"Patient\",\"inpatient_status\",\"=\",\"Admitted\",false]]", + "function": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "label": "Total Patients Admitted", + "modified": "2020-07-22 13:26:20.027788", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Total Patients Admitted", + "owner": "Administrator", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/healthcare/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json b/healthcare/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json new file mode 100644 index 0000000..3f25a9d --- /dev/null +++ b/healthcare/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-19 10:39:55.728058", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-01-30 12:02:22.849260", + "modified_by": "Administrator", + "name": "Create Healthcare Practitioner", + "owner": "Administrator", + "reference_document": "Healthcare Practitioner", + "show_form_tour": 0, + "show_full_form": 1, + "title": "Create Healthcare Practitioner", + "validate_action": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/onboarding_step/create_patient/create_patient.json b/healthcare/healthcare/onboarding_step/create_patient/create_patient.json new file mode 100644 index 0000000..b46bb15 --- /dev/null +++ b/healthcare/healthcare/onboarding_step/create_patient/create_patient.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-19 10:32:27.648902", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-01-30 00:09:28.786428", + "modified_by": "ruchamahabal2@gmail.com", + "name": "Create Patient", + "owner": "Administrator", + "reference_document": "Patient", + "show_form_tour": 0, + "show_full_form": 1, + "title": "Create Patient", + "validate_action": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json b/healthcare/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json new file mode 100644 index 0000000..7ce122d --- /dev/null +++ b/healthcare/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-19 10:41:19.065753", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-01-30 00:09:28.794602", + "modified_by": "ruchamahabal2@gmail.com", + "name": "Create Practitioner Schedule", + "owner": "Administrator", + "reference_document": "Practitioner Schedule", + "show_form_tour": 0, + "show_full_form": 1, + "title": "Create Practitioner Schedule", + "validate_action": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/healthcare/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json new file mode 100644 index 0000000..dfe9f71 --- /dev/null +++ b/healthcare/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json @@ -0,0 +1,19 @@ +{ + "action": "Show Form Tour", + "creation": "2020-05-19 11:40:51.963741", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-01-30 19:22:08.257160", + "modified_by": "Administrator", + "name": "Explore Clinical Procedure Templates", + "owner": "Administrator", + "reference_document": "Clinical Procedure Template", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Explore Clinical Procedure Templates", + "validate_action": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/healthcare/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json new file mode 100644 index 0000000..2d952f3 --- /dev/null +++ b/healthcare/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json @@ -0,0 +1,19 @@ +{ + "action": "Show Form Tour", + "creation": "2020-05-19 11:14:33.044989", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 1, + "is_skipped": 0, + "modified": "2021-01-30 19:22:07.275735", + "modified_by": "Administrator", + "name": "Explore Healthcare Settings", + "owner": "Administrator", + "reference_document": "Healthcare Settings", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Explore Healthcare Settings", + "validate_action": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json b/healthcare/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json new file mode 100644 index 0000000..baa8358 --- /dev/null +++ b/healthcare/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json @@ -0,0 +1,20 @@ +{ + "action": "Show Form Tour", + "creation": "2020-05-19 10:43:56.231679", + "docstatus": 0, + "doctype": "Onboarding Step", + "field": "schedule", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-01-30 00:09:28.807129", + "modified_by": "ruchamahabal2@gmail.com", + "name": "Introduction to Healthcare Practitioner", + "owner": "Administrator", + "reference_document": "Healthcare Practitioner", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Introduction to Healthcare Practitioner", + "validate_action": 0 +} \ No newline at end of file diff --git a/healthcare/healthcare/page/__init__.py b/healthcare/healthcare/page/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/healthcare/healthcare/page/__init__.py @@ -0,0 +1 @@ + diff --git a/healthcare/healthcare/page/patient_history/__init__.py b/healthcare/healthcare/page/patient_history/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/page/patient_history/patient_history.css b/healthcare/healthcare/page/patient_history/patient_history.css new file mode 100644 index 0000000..74b5e7e --- /dev/null +++ b/healthcare/healthcare/page/patient_history/patient_history.css @@ -0,0 +1,151 @@ +#page-medical_record .label { + display: inline-block; + margin-right: 7px; +} + +#page-medical_record .list-row { + border: none; + padding: 0px; + cursor: pointer; +} + +.patient-image-container { + margin-top: 17px; + } + +.patient-image { + display: inline-block; + width: 100%; + height: 0; + padding: 50% 0px; + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + border-radius: 4px; +} + +.patient-name { + font-size: 20px; + margin-top: 25px; +} + +.medical_record-label { + max-width: 100px; + margin-bottom: -4px; +} + +.medical_record-row > * { + z-index: -999; +} + +.date-indicator { + background:none; + font-size:12px; + vertical-align:middle; + font-weight:bold; + color:#6c7680; +} +.date-indicator::after { + margin:0 -4px 0 12px; + content:''; + display:inline-block; + height:8px; + width:8px; + border-radius:8px; + background: #d1d8dd; +} + +.date-indicator.blue { + color: #5e64ff; +} + +.div-bg-color { + background: #fafbfc; +} + +.bg-color-white { + background: #FFFFFF; +} + +.d-flex { + display: flex; +} + +.width-full { + width: 100%; +} + +.p-3 { + padding: 16px; +} + +.mt-2 { + margin-top: 8px; +} + +.mr-3 { + margin-right: 16px; +} + +.Box { + background-color: #fff; + border: 1px solid #d1d5da; + border-radius: 3px; +} + +.flex-column { + flex-direction: column; +} + +.avatar { + display: inline-block; + overflow: hidden; + line-height: 1; + vertical-align: middle; + border-radius: 3px; +} + +.py-3 { + padding-top: 16px; + padding-bottom: 16px; +} + +.border-bottom { + border-bottom: 1px #e1e4e8 solid; +} + +.date-indicator.blue::after { + background: #5e64ff; +} + +.medical_record-message { + border-left: 1px solid #d1d8dd; + padding: 15px; + padding-right: 30px; +} + +.medical_record-date { + padding: 15px; + padding-right: 0px; +} + +.patient-history-filter { + margin-left: 35px; + width: 25%; +} + +#page-medical_record .plot-wrapper { + padding: 20px 15px; + border-bottom: 1px solid #d1d8dd; + text-align: center; +} + +#page-medical_record .plot { + height: 140px ; + width: 97% ; + margin: auto; +} + +#page-medical_record .list-filters { + display: none ; +} diff --git a/healthcare/healthcare/page/patient_history/patient_history.html b/healthcare/healthcare/page/patient_history/patient_history.html new file mode 100644 index 0000000..d16b386 --- /dev/null +++ b/healthcare/healthcare/page/patient_history/patient_history.html @@ -0,0 +1,18 @@ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
diff --git a/healthcare/healthcare/page/patient_history/patient_history.js b/healthcare/healthcare/page/patient_history/patient_history.js new file mode 100644 index 0000000..8924d58 --- /dev/null +++ b/healthcare/healthcare/page/patient_history/patient_history.js @@ -0,0 +1,455 @@ +frappe.provide('frappe.patient_history'); +frappe.pages['patient_history'].on_page_load = function(wrapper) { + frappe.ui.make_app_page({ + parent: wrapper, + title: __('Patient History') + }); + + let patient_history = new PatientHistory(wrapper); + $(wrapper).bind('show', ()=> { + patient_history.show(); + }); +}; + +class PatientHistory { + constructor(wrapper) { + this.wrapper = $(wrapper); + this.page = wrapper.page; + this.sidebar = this.wrapper.find('.layout-side-section'); + this.main_section = this.wrapper.find('.layout-main-section'); + this.start = 0; + } + + show() { + frappe.breadcrumbs.add('Healthcare'); + this.sidebar.empty(); + + let me = this; + let patient = frappe.ui.form.make_control({ + parent: me.sidebar, + df: { + fieldtype: 'Link', + options: 'Patient', + fieldname: 'patient', + placeholder: __('Select Patient'), + only_select: true, + change: () => { + me.patient_id = ''; + if (me.patient_id != patient.get_value() && patient.get_value()) { + me.start = 0; + me.patient_id = patient.get_value(); + me.make_patient_profile(); + } + } + } + }); + patient.refresh(); + + if (frappe.route_options && !this.patient_id) { + patient.set_value(frappe.route_options.patient); + this.patient_id = frappe.route_options.patient; + } + + this.sidebar.find('[data-fieldname="patient"]').append('
'); + } + + make_patient_profile() { + this.page.set_title(__('Patient History')); + this.main_section.empty().append(frappe.render_template('patient_history')); + this.setup_filters(); + this.setup_documents(); + this.show_patient_info(); + this.setup_buttons(); + this.show_patient_vital_charts('bp', 'mmHg', 'Blood Pressure'); + } + + setup_filters() { + $('.doctype-filter').empty(); + let me = this; + + frappe.xcall( + 'healthcare.healthcare.page.patient_history.patient_history.get_patient_history_doctypes' + ).then(document_types => { + let doctype_filter = frappe.ui.form.make_control({ + parent: $('.doctype-filter'), + df: { + fieldtype: 'MultiSelectList', + fieldname: 'document_type', + placeholder: __('Select Document Type'), + change: () => { + me.start = 0; + me.page.main.find('.patient_documents_list').html(''); + this.setup_documents(doctype_filter.get_value(), date_range_field.get_value()); + }, + get_data: () => { + return document_types.map(document_type => { + return { + description: document_type, + value: document_type + }; + }); + }, + } + }); + doctype_filter.refresh(); + + $('.date-filter').empty(); + let date_range_field = frappe.ui.form.make_control({ + df: { + fieldtype: 'DateRange', + fieldname: 'date_range', + placeholder: __('Date Range'), + input_class: 'input-xs', + change: () => { + let selected_date_range = date_range_field.get_value(); + if (selected_date_range && selected_date_range.length === 2) { + me.start = 0; + me.page.main.find('.patient_documents_list').html(''); + this.setup_documents(doctype_filter.get_value(), date_range_field.get_value()); + } + } + }, + parent: $('.date-filter') + }); + date_range_field.refresh(); + }); + } + + setup_documents(document_types="", selected_date_range="") { + let filters = { + name: this.patient_id, + start: this.start, + page_length: 20 + }; + if (document_types) + filters['document_types'] = document_types; + if (selected_date_range) + filters['date_range'] = selected_date_range; + + let me = this; + frappe.call({ + 'method': 'healthcare.healthcare.page.patient_history.patient_history.get_feed', + args: filters, + callback: function(r) { + let data = r.message; + if (data.length) { + me.add_to_records(data); + } else { + me.page.main.find('.patient_documents_list').append(` +
+

${__('No more records..')}

+
`); + me.page.main.find('.btn-get-records').hide(); + } + } + }); + } + + add_to_records(data) { + let details = ""; + let i; + for (i=0; i + ${data[i].reference_name} + `; + + details += ` +
+
`; + + if (data[i].imgsrc) { + details += ` + + `; + } else { + details += ` +
+ ${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'} +
+
`; + } + + details += `
+
+ `+time_line_heading+` + + ${data[i].date_sep} + +
+
+ ${label} +
+
+ + +
+
+ + +
+
+
+
`; + } + } + + this.page.main.find('.patient_documents_list').append(details); + this.start += data.length; + + if (data.length === 20) { + this.page.main.find(".btn-get-records").show(); + } else { + this.page.main.find(".btn-get-records").hide(); + this.page.main.find(".patient_documents_list").append(` +
+

${__('No more records..')}

+
`); + } + } + + add_date_separator(data) { + let date = frappe.datetime.str_to_obj(data.communication_date); + let pdate = ''; + let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), + frappe.datetime.obj_to_str(date)); + + if (diff < 1) { + pdate = __('Today'); + } else if (diff < 2) { + pdate = __('Yesterday'); + } else { + pdate = __('on {0}', [frappe.datetime.global_date_format(date)]); + } + data.date_sep = pdate; + return data; + } + + show_patient_info() { + this.get_patient_info().then(() => { + $('.patient-info').empty().append(frappe.render_template('patient_history_sidebar', { + patient_image: this.patient.image, + patient_name: this.patient.patient_name, + patient_gender: this.patient.sex, + patient_mobile: this.patient.mobile + })); + this.show_patient_details(); + }); + } + + show_patient_details() { + let me = this; + frappe.call({ + 'method': 'healthcare.healthcare.doctype.patient.patient.get_patient_detail', + args: { + patient: me.patient_id + }, + callback: function(r) { + let data = r.message; + let details = ``; + + if (data.occupation) details += `

${__('Occupation')} : ${data.occupation}`; + if (data.blood_group) details += `
${__('Blood Group')} : ${data.blood_group}`; + if (data.allergies) details += `

${__('Allerigies')} : ${data.allergies.replace(/\n/g, ", ")}`; + if (data.medication) details += `
${__('Medication')} : ${data.medication.replace(/\n/g, ", ")}`; + if (data.alcohol_current_use) details += `

${__('Alcohol use')} : ${data.alcohol_current_use}`; + if (data.alcohol_past_use) details += `
${__('Alcohol past use')} : ${data.alcohol_past_use}`; + if (data.tobacco_current_use) details += `
${__('Tobacco use')} : ${data.tobacco_current_use}`; + if (data.tobacco_past_use) details += `
${__('Tobacco past use')} : ${data.tobacco_past_use}`; + if (data.medical_history) details += `

${__('Medical history')} : ${data.medical_history.replace(/\n/g, ", ")}`; + if (data.surgical_history) details += `
${__('Surgical history')} : ${data.surgical_history.replace(/\n/g, ", ")}`; + if (data.surrounding_factors) details += `

${__('Occupational hazards')} : ${data.surrounding_factors.replace(/\n/g, ", ")}`; + if (data.other_risk_factors) details += `
${__('Other risk factors')} : ${data.other_risk_factors.replace(/\n/g, ", ")}`; + if (data.patient_details) details += `

${__('More info')} : ${data.patient_details.replace(/\n/g, ", ")}`; + + if (details) { + details = `
` + details + `
`; + } + + me.sidebar.find('.patient-details').html(details); + } + }); + } + + get_patient_info() { + return frappe.xcall('frappe.client.get', { + doctype: 'Patient', + name: this.patient_id, + }).then((patient) => { + if (patient) { + this.patient = patient; + } + }); + } + + setup_buttons() { + let me = this; + this.page.main.on("click", ".btn-show-chart", function() { + let btn_id = $(this).attr("data-show-chart-id"), scale_unit = $(this).attr("data-pts"); + let title = $(this).attr("data-title"); + me.show_patient_vital_charts(btn_id, scale_unit, title); + }); + + this.page.main.on('click', '.btn-more', function() { + let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname'); + if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') { + me.page.main.find('.'+docname).hide(); + me.page.main.find('.'+docname).parent().find('.document-html').show(); + } else { + if (doctype && docname) { + let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date', 'naming_series']; + frappe.call({ + method: 'healthcare.healthcare.utils.render_doc_as_html', + args: { + doctype: doctype, + docname: docname, + exclude_fields: exclude + }, + freeze: true, + callback: function(r) { + if (r.message) { + me.page.main.find('.' + docname).hide(); + + me.page.main.find('.' + docname).parent().find('.document-html').html( + `${r.message.html} +
+
+ + +
+ `); + + me.page.main.find('.' + docname).parent().find('.document-html').attr('hidden', false); + me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1'); + } + } + }); + } + } + }); + + this.page.main.on('click', '.btn-less', function() { + let docname = $(this).attr('data-docname'); + me.page.main.find('.' + docname).parent().find('.document-id').show(); + me.page.main.find('.' + docname).parent().find('.document-html').hide(); + }); + + me.page.main.on('click', '.btn-get-records', function() { + this.setup_documents(); + }); + } + + show_patient_vital_charts(btn_id, scale_unit, title) { + let me = this; + + frappe.call({ + method: 'healthcare.healthcare.utils.get_patient_vitals', + args: { + patient: me.patient_id + }, + callback: function(r) { + if (r.message) { + let show_chart_btns_html = ` + `; + + me.page.main.find('.show_chart_btns').html(show_chart_btns_html); + let data = r.message; + let labels = [], datasets = []; + let bp_systolic = [], bp_diastolic = [], temperature = []; + let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = []; + + for (let i=0; i (d + '').toUpperCase(), + formatTooltipY: d => d + ' ' + scale_unit, + } + }); + me.page.main.find('.header-separator').show(); + } else { + me.page.main.find('.patient_vital_charts').html(''); + me.page.main.find('.show_chart_btns').html(''); + me.page.main.find('.header-separator').hide(); + } + } + }); + } +} diff --git a/healthcare/healthcare/page/patient_history/patient_history.json b/healthcare/healthcare/page/patient_history/patient_history.json new file mode 100644 index 0000000..b3892a4 --- /dev/null +++ b/healthcare/healthcare/page/patient_history/patient_history.json @@ -0,0 +1,28 @@ +{ + "content": null, + "creation": "2018-08-08 17:09:13.816199", + "docstatus": 0, + "doctype": "Page", + "icon": "", + "idx": 0, + "modified": "2018-08-08 17:09:55.969424", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "patient_history", + "owner": "Administrator", + "page_name": "patient_history", + "restrict_to_domain": "Healthcare", + "roles": [ + { + "role": "Healthcare Administrator" + }, + { + "role": "Physician" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Patient History" +} \ No newline at end of file diff --git a/healthcare/healthcare/page/patient_history/patient_history.py b/healthcare/healthcare/page/patient_history/patient_history.py new file mode 100644 index 0000000..a7304da --- /dev/null +++ b/healthcare/healthcare/page/patient_history/patient_history.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, ESS LLP and contributors +# For license information, please see license.txt + + +import json + +import frappe +from frappe.utils import cint + + +@frappe.whitelist() +def get_feed(name, document_types=None, date_range=None, start=0, page_length=20): + """get feed""" + filters = get_filters(name, document_types, date_range) + + result = frappe.db.get_all( + "Patient Medical Record", + fields=["name", "owner", "communication_date", "reference_doctype", "reference_name", "subject"], + filters=filters, + order_by="communication_date DESC", + limit=cint(page_length), + start=cint(start), + ) + + return result + + +def get_filters(name, document_types=None, date_range=None): + filters = {"patient": name} + if document_types: + document_types = json.loads(document_types) + if len(document_types): + filters["reference_doctype"] = ["IN", document_types] + + if date_range: + try: + date_range = json.loads(date_range) + if date_range: + filters["communication_date"] = ["between", [date_range[0], date_range[1]]] + except json.decoder.JSONDecodeError: + pass + + return filters + + +@frappe.whitelist() +def get_feed_for_dt(doctype, docname): + """get feed""" + result = frappe.db.get_all( + "Patient Medical Record", + fields=["name", "owner", "communication_date", "reference_doctype", "reference_name", "subject"], + filters={"reference_doctype": doctype, "reference_name": docname}, + order_by="communication_date DESC", + ) + + return result + + +@frappe.whitelist() +def get_patient_history_doctypes(): + document_types = [] + settings = frappe.get_single("Patient History Settings") + + for entry in settings.standard_doctypes: + document_types.append(entry.document_type) + + for entry in settings.custom_doctypes: + document_types.append(entry.document_type) + + return document_types diff --git a/healthcare/healthcare/page/patient_history/patient_history_sidebar.html b/healthcare/healthcare/page/patient_history/patient_history_sidebar.html new file mode 100644 index 0000000..fc7eab0 --- /dev/null +++ b/healthcare/healthcare/page/patient_history/patient_history_sidebar.html @@ -0,0 +1,20 @@ +
+
+ {% if patient_image %} +
+ {% endif %} +
+
+ {% if patient_name %} +

{{patient_name}}

+ {% endif %} + {% if patient_gender %} +

{%=__("Gender: ") %} {{patient_gender}}

+ {% endif %} + {% if patient_mobile %} +

{%=__("Contact: ") %} {{patient_mobile}}

+ {% endif %} +
+
+
+
diff --git a/healthcare/healthcare/page/patient_progress/__init__.py b/healthcare/healthcare/page/patient_progress/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/page/patient_progress/patient_progress.css b/healthcare/healthcare/page/patient_progress/patient_progress.css new file mode 100644 index 0000000..737b2e0 --- /dev/null +++ b/healthcare/healthcare/page/patient_progress/patient_progress.css @@ -0,0 +1,171 @@ +/* sidebar */ + +.layout-side-section .frappe-control[data-fieldname='patient'] { + max-width: 300px; +} + +.patient-image-container { + margin-top: 17px; +} + +.patient-image { + display: inline-block; + width: 100%; + height: 0; + padding: 50% 0px; + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + border-radius: 4px; +} + +.patient-details { + margin: -5px 5px; +} + +.important-links { + margin: 30px 5px; +} + +.patient-name { + font-size: 20px; + margin-top: 25px; +} + +/* heatmap */ + +.heatmap-container { + height: 170px; +} + +.patient-heatmap { + width: 80%; + display: inline-block; +} + +.patient-heatmap .chart-container { + margin-left: 30px; +} + +.patient-heatmap .frappe-chart { + margin-top: 5px; +} + +.patient-heatmap .frappe-chart .chart-legend { + display: none; +} + +.heatmap-container .chart-filter { + z-index: 1; + position: relative; + top: 5px; + margin-right: 10px; +} + +/* percentage chart */ + +.percentage-chart-container { + height: 130px; +} + +.percentage-chart-container .chart-filter { + position: relative; + top: 5px; + margin-right: 10px; +} + +.therapy-session-percentage-chart .frappe-chart { + position: absolute; + top: 5px; +} + +/* line charts */ + +.date-field .clearfix { + display: none; +} + +.date-field .help-box { + display: none; +} + +.date-field .frappe-control { + margin-bottom: 0px !important; +} + +.date-field .form-group { + margin-bottom: 0px !important; +} + +/* common */ + +text.title { + text-transform: uppercase; + font-size: 11px; + margin-left: 20px; + margin-top: 20px; + display: block; +} + +.chart-filter-search { + margin-left: 35px; + width: 25%; +} + +.chart-column-container { + margin: 5px 0; +} + +.progress-graphs .progress-container { + margin-bottom: var(--margin-xl); +} + +.line-chart-container .frappe-chart { + margin-top: -20px; +} + +.line-chart-container { + margin-bottom: 20px; +} + +.chart-control { + align-self: center; + display: flex; + flex-direction: row-reverse; + margin-top: -25px; +} + +.chart-control > * { + margin-right: 10px; +} + +/* mobile */ + +@media (max-width: 991px) { + .patient-progress-sidebar { + display: flex; + } + + .percentage-chart-container { + border-top: 1px solid #d1d8dd; + } + + .percentage-chart-container .chart-filter { + z-index: 1; + position: relative; + top: 12px; + margin-right: 10px; + } + + .patient-progress-sidebar .important-links { + margin: 0; + } + + .patient-progress-sidebar .patient-details { + width: 50%; + } + + .chart-filter-search { + width: 40%; + } +} diff --git a/healthcare/healthcare/page/patient_progress/patient_progress.html b/healthcare/healthcare/page/patient_progress/patient_progress.html new file mode 100644 index 0000000..ee60065 --- /dev/null +++ b/healthcare/healthcare/page/patient_progress/patient_progress.html @@ -0,0 +1,69 @@ +
+
+
+ + +
+
+
+ +
+
+ Therapy Progress +
+
+
+ +
+
+
+
+
+
+ +
+
+ Assessment Results +
+
+
+ +
+
+
+
+
+
+ +
+
+ Therapy Type and Assessment Correlation +
+
+
+ +
+
+
+
+
+
+ +
+
+ Assessment Parameter Wise Progress +
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/healthcare/healthcare/page/patient_progress/patient_progress.js b/healthcare/healthcare/page/patient_progress/patient_progress.js new file mode 100644 index 0000000..16ee0de --- /dev/null +++ b/healthcare/healthcare/page/patient_progress/patient_progress.js @@ -0,0 +1,536 @@ +frappe.pages['patient-progress'].on_page_load = function(wrapper) { + + frappe.ui.make_app_page({ + parent: wrapper, + title: __('Patient Progress') + }); + + let patient_progress = new PatientProgress(wrapper); + $(wrapper).bind('show', ()=> { + patient_progress.show(); + }); +}; + +class PatientProgress { + + constructor(wrapper) { + this.wrapper = $(wrapper); + this.page = wrapper.page; + this.sidebar = this.wrapper.find('.layout-side-section'); + this.main_section = this.wrapper.find('.layout-main-section'); + } + + show() { + frappe.breadcrumbs.add('Healthcare'); + this.sidebar.empty(); + + let me = this; + let patient = frappe.ui.form.make_control({ + parent: me.sidebar, + df: { + fieldtype: 'Link', + options: 'Patient', + fieldname: 'patient', + placeholder: __('Select Patient'), + only_select: true, + change: () => { + me.patient_id = ''; + if (me.patient_id != patient.get_value() && patient.get_value()) { + me.start = 0; + me.patient_id = patient.get_value(); + me.make_patient_profile(); + } + } + } + }); + patient.refresh(); + + if (frappe.route_options && !this.patient) { + patient.set_value(frappe.route_options.patient); + this.patient_id = frappe.route_options.patient; + } + + this.sidebar.find('[data-fieldname="patient"]').append('
'); + } + + make_patient_profile() { + this.page.set_title(__('Patient Progress')); + this.main_section.empty().append(frappe.render_template('patient_progress')); + this.render_patient_details(); + this.render_heatmap(); + this.render_percentage_chart('therapy_type', 'Therapy Type Distribution'); + this.create_percentage_chart_filters(); + this.show_therapy_progress(); + this.show_assessment_results(); + this.show_therapy_assessment_correlation(); + this.show_assessment_parameter_progress(); + } + + get_patient_info() { + return frappe.xcall('frappe.client.get', { + doctype: 'Patient', + name: this.patient_id + }).then((patient) => { + if (patient) { + this.patient = patient; + } + }); + } + + get_therapy_sessions_count() { + return frappe.xcall( + 'healthcare.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_count', { + patient: this.patient_id, + } + ).then(data => { + if (data) { + this.total_therapy_sessions = data.total_therapy_sessions; + this.therapy_sessions_this_month = data.therapy_sessions_this_month; + } + }); + } + + render_patient_details() { + this.get_patient_info().then(() => { + this.get_therapy_sessions_count().then(() => { + $('.patient-info').empty().append(frappe.render_template('patient_progress_sidebar', { + patient_image: this.patient.image, + patient_name: this.patient.patient_name, + patient_gender: this.patient.sex, + patient_mobile: this.patient.mobile, + total_therapy_sessions: this.total_therapy_sessions, + therapy_sessions_this_month: this.therapy_sessions_this_month + })); + + this.setup_patient_profile_links(); + }); + }); + } + + setup_patient_profile_links() { + this.wrapper.find('.patient-profile-link').on('click', () => { + frappe.set_route('Form', 'Patient', this.patient_id); + }); + + this.wrapper.find('.therapy-plan-link').on('click', () => { + frappe.route_options = { + 'patient': this.patient_id, + 'docstatus': 1 + }; + frappe.set_route('List', 'Therapy Plan'); + }); + + this.wrapper.find('.patient-history').on('click', () => { + frappe.route_options = { + 'patient': this.patient_id + }; + frappe.set_route('patient_history'); + }); + } + + render_heatmap() { + this.heatmap = new frappe.Chart('.patient-heatmap', { + type: 'heatmap', + countLabel: 'Interactions', + data: {}, + discreteDomains: 1, + radius: 3, + height: 150 + }); + + this.update_heatmap_data(); + this.create_heatmap_chart_filters(); + } + + update_heatmap_data(date_from) { + frappe.xcall('healthcare.healthcare.page.patient_progress.patient_progress.get_patient_heatmap_data', { + patient: this.patient_id, + date: date_from || frappe.datetime.year_start(), + }).then((data) => { + this.heatmap.update( {dataPoints: data} ); + }); + } + + create_heatmap_chart_filters() { + this.get_patient_info().then(() => { + let filters = [ + { + label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()), + options: frappe.dashboard_utils.get_years_since_creation(this.patient.creation), + action: (selected_item) => { + this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item)); + } + }, + ]; + frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-container'); + }); + } + + render_percentage_chart(field, title) { + // REDESIGN-TODO: chart seems to be broken. Enable this once fixed. + this.wrapper.find('.percentage-chart-container').hide(); + // frappe.xcall( + // 'healthcare.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_distribution_data', { + // patient: this.patient_id, + // field: field + // } + // ).then(chart => { + // if (chart.labels.length) { + // this.percentage_chart = new frappe.Chart('.therapy-session-percentage-chart', { + // title: title, + // type: 'percentage', + // data: { + // labels: chart.labels, + // datasets: chart.datasets + // }, + // truncateLegends: 1, + // barOptions: { + // height: 11, + // depth: 1 + // }, + // height: 160, + // maxSlices: 8, + // colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'], + // }); + // } else { + // this.wrapper.find('.percentage-chart-container').hide(); + // } + // }); + } + + create_percentage_chart_filters() { + let filters = [ + { + label: 'Therapy Type', + options: ['Therapy Type', 'Exercise Type'], + fieldnames: ['therapy_type', 'exercise_type'], + action: (selected_item, fieldname) => { + let title = selected_item + ' Distribution'; + this.render_percentage_chart(fieldname, title); + } + }, + ]; + frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-container'); + } + + create_time_span_filters(action_method, parent) { + let chart_control = $(parent).find('.chart-control'); + let filters = [ + { + label: 'Last Month', + options: ['Select Date Range', 'Last Week', 'Last Month', 'Last Quarter', 'Last Year'], + action: (selected_item) => { + if (selected_item === 'Select Date Range') { + this.render_date_range_fields(action_method, chart_control); + } else { + // hide date range field if visible + let date_field = $(parent).find('.date-field'); + if (date_field.is(':visible')) { + date_field.hide(); + } + this[action_method](selected_item); + } + } + } + ]; + frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', chart_control, 1); + } + + render_date_range_fields(action_method, parent) { + let date_field = $(parent).find('.date-field'); + + if (!date_field.length) { + let date_field_wrapper = $( + `
` + ).appendTo(parent); + + let date_range_field = frappe.ui.form.make_control({ + df: { + fieldtype: 'DateRange', + fieldname: 'from_date', + placeholder: 'Date Range', + input_class: 'input-xs', + reqd: 1, + change: () => { + let selected_date_range = date_range_field.get_value(); + if (selected_date_range && selected_date_range.length === 2) { + this[action_method](selected_date_range); + } + } + }, + parent: date_field_wrapper, + render_input: 1 + }); + } else if (!date_field.is(':visible')) { + date_field.show(); + } + } + + show_therapy_progress() { + let me = this; + let therapy_type = frappe.ui.form.make_control({ + parent: $('.therapy-type-search'), + df: { + fieldtype: 'Link', + options: 'Therapy Type', + fieldname: 'therapy_type', + placeholder: __('Select Therapy Type'), + only_select: true, + change: () => { + if (me.therapy_type != therapy_type.get_value() && therapy_type.get_value()) { + me.therapy_type = therapy_type.get_value(); + me.render_therapy_progress_chart(); + } + } + } + }); + therapy_type.refresh(); + this.create_time_span_filters('render_therapy_progress_chart', '.therapy-progress'); + } + + render_therapy_progress_chart(time_span='Last Month') { + if (!this.therapy_type) return; + + frappe.xcall( + 'healthcare.healthcare.page.patient_progress.patient_progress.get_therapy_progress_data', { + patient: this.patient_id, + therapy_type: this.therapy_type, + time_span: time_span + } + ).then(chart => { + let data = { + labels: chart.labels, + datasets: chart.datasets + } + let parent = '.therapy-progress-line-chart'; + if (!chart.labels.length) { + this.show_null_state(parent); + } else { + if (!this.therapy_line_chart) { + this.therapy_line_chart = new frappe.Chart(parent, { + type: 'axis-mixed', + height: 250, + data: data, + lineOptions: { + regionFill: 1 + }, + axisOptions: { + xIsSeries: 1 + } + }); + } else { + $(parent).find('.chart-container').show(); + $(parent).find('.chart-empty-state').hide(); + this.therapy_line_chart.update(data); + } + } + }); + } + + show_assessment_results() { + let me = this; + let assessment_template = frappe.ui.form.make_control({ + parent: $('.assessment-template-search'), + df: { + fieldtype: 'Link', + options: 'Patient Assessment Template', + fieldname: 'assessment_template', + placeholder: __('Select Assessment Template'), + only_select: true, + change: () => { + if (me.assessment_template != assessment_template.get_value() && assessment_template.get_value()) { + me.assessment_template = assessment_template.get_value(); + me.render_assessment_result_chart(); + } + } + } + }); + assessment_template.refresh(); + this.create_time_span_filters('render_assessment_result_chart', '.assessment-results'); + } + + render_assessment_result_chart(time_span='Last Month') { + if (!this.assessment_template) return; + + frappe.xcall( + 'healthcare.healthcare.page.patient_progress.patient_progress.get_patient_assessment_data', { + patient: this.patient_id, + assessment_template: this.assessment_template, + time_span: time_span + } + ).then(chart => { + let data = { + labels: chart.labels, + datasets: chart.datasets, + yMarkers: [ + { label: 'Max Score', value: chart.max_score } + ], + } + let parent = '.assessment-results-line-chart'; + if (!chart.labels.length) { + this.show_null_state(parent); + } else { + if (!this.assessment_line_chart) { + this.assessment_line_chart = new frappe.Chart(parent, { + type: 'axis-mixed', + height: 250, + data: data, + lineOptions: { + regionFill: 1 + }, + axisOptions: { + xIsSeries: 1 + }, + tooltipOptions: { + formatTooltipY: d => __('{0} out of {1}', [d, chart.max_score]) + } + }); + } else { + $(parent).find('.chart-container').show(); + $(parent).find('.chart-empty-state').hide(); + this.assessment_line_chart.update(data); + } + } + }); + } + + show_therapy_assessment_correlation() { + let me = this; + let assessment = frappe.ui.form.make_control({ + parent: $('.assessment-correlation-template-search'), + df: { + fieldtype: 'Link', + options: 'Patient Assessment Template', + fieldname: 'assessment', + placeholder: __('Select Assessment Template'), + only_select: true, + change: () => { + if (me.assessment != assessment.get_value() && assessment.get_value()) { + me.assessment = assessment.get_value(); + me.render_therapy_assessment_correlation_chart(); + } + } + } + }); + assessment.refresh(); + this.create_time_span_filters('render_therapy_assessment_correlation_chart', '.therapy-assessment-correlation'); + } + + render_therapy_assessment_correlation_chart(time_span='Last Month') { + if (!this.assessment) return; + + frappe.xcall( + 'healthcare.healthcare.page.patient_progress.patient_progress.get_therapy_assessment_correlation_data', { + patient: this.patient_id, + assessment_template: this.assessment, + time_span: time_span + } + ).then(chart => { + let data = { + labels: chart.labels, + datasets: chart.datasets, + yMarkers: [ + { label: 'Max Score', value: chart.max_score } + ], + } + let parent = '.therapy-assessment-correlation-chart'; + if (!chart.labels.length) { + this.show_null_state(parent); + } else { + if (!this.correlation_chart) { + this.correlation_chart = new frappe.Chart(parent, { + type: 'axis-mixed', + height: 300, + data: data, + axisOptions: { + xIsSeries: 1 + } + }); + } else { + $(parent).find('.chart-container').show(); + $(parent).find('.chart-empty-state').hide(); + this.correlation_chart.update(data); + } + } + }); + } + + show_assessment_parameter_progress() { + let me = this; + let parameter = frappe.ui.form.make_control({ + parent: $('.assessment-parameter-search'), + df: { + fieldtype: 'Link', + options: 'Patient Assessment Parameter', + fieldname: 'assessment', + placeholder: __('Select Assessment Parameter'), + only_select: true, + change: () => { + if (me.parameter != parameter.get_value() && parameter.get_value()) { + me.parameter = parameter.get_value(); + me.render_assessment_parameter_progress_chart(); + } + } + } + }); + parameter.refresh(); + this.create_time_span_filters('render_assessment_parameter_progress_chart', '.assessment-parameter-progress'); + } + + render_assessment_parameter_progress_chart(time_span='Last Month') { + if (!this.parameter) return; + + frappe.xcall( + 'healthcare.healthcare.page.patient_progress.patient_progress.get_assessment_parameter_data', { + patient: this.patient_id, + parameter: this.parameter, + time_span: time_span + } + ).then(chart => { + let data = { + labels: chart.labels, + datasets: chart.datasets + } + let parent = '.assessment-parameter-progress-chart'; + if (!chart.labels.length) { + this.show_null_state(parent); + } else { + if (!this.parameter_chart) { + this.parameter_chart = new frappe.Chart(parent, { + type: 'line', + height: 250, + data: data, + lineOptions: { + regionFill: 1 + }, + axisOptions: { + xIsSeries: 1 + }, + tooltipOptions: { + formatTooltipY: d => d + '%' + } + }); + } else { + $(parent).find('.chart-container').show(); + $(parent).find('.chart-empty-state').hide(); + this.parameter_chart.update(data); + } + } + }); + } + + show_null_state(parent) { + let null_state = $(parent).find('.chart-empty-state'); + if (null_state.length) { + $(null_state).show(); + } else { + null_state = $( + `
${__( + "No Data..." + )}
` + ); + $(parent).append(null_state); + } + $(parent).find('.chart-container').hide(); + } +} diff --git a/healthcare/healthcare/page/patient_progress/patient_progress.json b/healthcare/healthcare/page/patient_progress/patient_progress.json new file mode 100644 index 0000000..0175cb9 --- /dev/null +++ b/healthcare/healthcare/page/patient_progress/patient_progress.json @@ -0,0 +1,33 @@ +{ + "content": null, + "creation": "2020-06-12 15:46:23.111928", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2020-07-23 21:45:45.540055", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "patient-progress", + "owner": "Administrator", + "page_name": "patient-progress", + "restrict_to_domain": "Healthcare", + "roles": [ + { + "role": "Healthcare Administrator" + }, + { + "role": "Physician" + }, + { + "role": "Patient" + }, + { + "role": "System Manager" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Patient Progress" +} \ No newline at end of file diff --git a/healthcare/healthcare/page/patient_progress/patient_progress.py b/healthcare/healthcare/page/patient_progress/patient_progress.py new file mode 100644 index 0000000..8d27c86 --- /dev/null +++ b/healthcare/healthcare/page/patient_progress/patient_progress.py @@ -0,0 +1,232 @@ +import json +from datetime import datetime + +import frappe +from frappe import _ +from frappe.utils import get_timespan_date_range, getdate + + +@frappe.whitelist() +def get_therapy_sessions_count(patient): + total = frappe.db.count("Therapy Session", filters={"docstatus": 1, "patient": patient}) + + month_start = datetime.today().replace(day=1) + this_month = frappe.db.count( + "Therapy Session", filters={"creation": [">", month_start], "docstatus": 1, "patient": patient} + ) + + return {"total_therapy_sessions": total, "therapy_sessions_this_month": this_month} + + +@frappe.whitelist() +def get_patient_heatmap_data(patient, date): + return dict( + frappe.db.sql( + """ + SELECT + unix_timestamp(communication_date), count(*) + FROM + `tabPatient Medical Record` + WHERE + communication_date > subdate(%(date)s, interval 1 year) and + communication_date < subdate(%(date)s, interval -1 year) and + patient = %(patient)s + GROUP BY communication_date + ORDER BY communication_date asc""", + {"date": date, "patient": patient}, + ) + ) + + +@frappe.whitelist() +def get_therapy_sessions_distribution_data(patient, field): + if field == "therapy_type": + result = frappe.db.get_all( + "Therapy Session", + filters={"patient": patient, "docstatus": 1}, + group_by=field, + order_by=field, + fields=[field, "count(*)"], + as_list=True, + ) + + elif field == "exercise_type": + data = frappe.db.get_all( + "Therapy Session", filters={"docstatus": 1, "patient": patient}, as_list=True + ) + therapy_sessions = [entry[0] for entry in data] + + result = frappe.db.get_all( + "Exercise", + filters={"parenttype": "Therapy Session", "parent": ["in", therapy_sessions], "docstatus": 1}, + group_by=field, + order_by=field, + fields=[field, "count(*)"], + as_list=True, + ) + + return { + "labels": [r[0] for r in result if r[0] != None], + "datasets": [{"values": [r[1] for r in result]}], + } + + +@frappe.whitelist() +def get_therapy_progress_data(patient, therapy_type, time_span): + date_range = get_date_range(time_span) + query_values = { + "from_date": date_range[0], + "to_date": date_range[1], + "therapy_type": therapy_type, + "patient": patient, + } + result = frappe.db.sql( + """ + SELECT + start_date, total_counts_targeted, total_counts_completed + FROM + `tabTherapy Session` + WHERE + start_date BETWEEN %(from_date)s AND %(to_date)s and + docstatus = 1 and + therapy_type = %(therapy_type)s and + patient = %(patient)s + ORDER BY start_date""", + query_values, + as_list=1, + ) + + return { + "labels": [r[0] for r in result if r[0] != None], + "datasets": [ + {"name": _("Targetted"), "values": [r[1] for r in result if r[0] != None]}, + {"name": _("Completed"), "values": [r[2] for r in result if r[0] != None]}, + ], + } + + +@frappe.whitelist() +def get_patient_assessment_data(patient, assessment_template, time_span): + date_range = get_date_range(time_span) + query_values = { + "from_date": date_range[0], + "to_date": date_range[1], + "assessment_template": assessment_template, + "patient": patient, + } + result = frappe.db.sql( + """ + SELECT + assessment_datetime, total_score, total_score_obtained + FROM + `tabPatient Assessment` + WHERE + DATE(assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and + docstatus = 1 and + assessment_template = %(assessment_template)s and + patient = %(patient)s + ORDER BY assessment_datetime""", + query_values, + as_list=1, + ) + + return { + "labels": [getdate(r[0]) for r in result if r[0] != None], + "datasets": [{"name": _("Score Obtained"), "values": [r[2] for r in result if r[0] != None]}], + "max_score": result[0][1] if result else None, + } + + +@frappe.whitelist() +def get_therapy_assessment_correlation_data(patient, assessment_template, time_span): + date_range = get_date_range(time_span) + query_values = { + "from_date": date_range[0], + "to_date": date_range[1], + "assessment": assessment_template, + "patient": patient, + } + result = frappe.db.sql( + """ + SELECT + therapy.therapy_type, count(*), avg(assessment.total_score_obtained), total_score + FROM + `tabPatient Assessment` assessment INNER JOIN `tabTherapy Session` therapy + ON + assessment.therapy_session = therapy.name + WHERE + DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and + assessment.docstatus = 1 and + assessment.patient = %(patient)s and + assessment.assessment_template = %(assessment)s + GROUP BY therapy.therapy_type + """, + query_values, + as_list=1, + ) + + return { + "labels": [r[0] for r in result if r[0] != None], + "datasets": [ + {"name": _("Sessions"), "chartType": "bar", "values": [r[1] for r in result if r[0] != None]}, + { + "name": _("Average Score"), + "chartType": "line", + "values": [round(r[2], 2) for r in result if r[0] != None], + }, + ], + "max_score": result[0][1] if result else None, + } + + +@frappe.whitelist() +def get_assessment_parameter_data(patient, parameter, time_span): + date_range = get_date_range(time_span) + query_values = { + "from_date": date_range[0], + "to_date": date_range[1], + "parameter": parameter, + "patient": patient, + } + results = frappe.db.sql( + """ + SELECT + assessment.assessment_datetime, + sheet.score, + template.scale_max + FROM + `tabPatient Assessment Sheet` sheet + INNER JOIN `tabPatient Assessment` assessment + ON sheet.parent = assessment.name + INNER JOIN `tabPatient Assessment Template` template + ON template.name = assessment.assessment_template + WHERE + DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and + assessment.docstatus = 1 and + sheet.parameter = %(parameter)s and + assessment.patient = %(patient)s + ORDER BY + assessment.assessment_datetime asc + """, + query_values, + as_list=1, + ) + + score_percentages = [] + for r in results: + if r[2] != 0 and r[0] != None: + score = round((int(r[1]) / int(r[2])) * 100, 2) + score_percentages.append(score) + + return { + "labels": [getdate(r[0]) for r in results if r[0] != None], + "datasets": [{"name": _("Score"), "values": score_percentages}], + } + + +def get_date_range(time_span): + try: + time_span = json.loads(time_span) + return time_span + except json.decoder.JSONDecodeError: + return get_timespan_date_range(time_span.lower()) diff --git a/healthcare/healthcare/page/patient_progress/patient_progress_sidebar.html b/healthcare/healthcare/page/patient_progress/patient_progress_sidebar.html new file mode 100644 index 0000000..4ee6573 --- /dev/null +++ b/healthcare/healthcare/page/patient_progress/patient_progress_sidebar.html @@ -0,0 +1,29 @@ +
+
+ {% if patient_image %} +
+ {% endif %} +
+
+ {% if patient_name %} +

{{patient_name}}

+ {% endif %} + {% if patient_gender %} +

{%=__("Gender: ") %} {{patient_gender}}

+ {% endif %} + {% if patient_mobile %} +

{%=__("Contact: ") %} {{patient_mobile}}

+ {% endif %} + {% if total_therapy_sessions %} +

{%=__("Total Therapy Sessions: ") %} {{total_therapy_sessions}}

+ {% endif %} + {% if therapy_sessions_this_month %} +

{%=__("Monthly Therapy Sessions: ") %} {{therapy_sessions_this_month}}

+ {% endif %} +
+ +
diff --git a/healthcare/healthcare/print_format/__init__.py b/healthcare/healthcare/print_format/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/print_format/encounter_print/__init__.py b/healthcare/healthcare/print_format/encounter_print/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/print_format/encounter_print/encounter_print.json b/healthcare/healthcare/print_format/encounter_print/encounter_print.json new file mode 100644 index 0000000..3c90adb --- /dev/null +++ b/healthcare/healthcare/print_format/encounter_print/encounter_print.json @@ -0,0 +1,22 @@ +{ + "align_labels_right": 0, + "creation": "2017-04-10 14:05:53.355863", + "custom_format": 1, + "disabled": 0, + "doc_type": "Patient Encounter", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "
\n {% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n
\n {% else %}\n
\n

{{doc.name}}

\n
\n {%- endif %}\n
\n
\n {% if doc.appointment %}\n\t
\n\t\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t: {{doc.appointment}}\n\t\t\t
\n\t\t
\n\t\t{%- endif -%}\n\n
\n\t\t
\n\t\t\t \n\t\t
\n {% if doc.patient %}\n\t\t
\n\t\t\t : {{doc.patient}}\n\t\t
\n {% else %}\n
\n\t\t\t : Patient Name\n\t\t
\n {%- endif -%}\n\t\t
\n\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t : {{doc.patient_age}}\n\t\t\t
\n\t\t
\n\n
\n
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t : {{doc.patient_sex}}\n\t\t\t
\n
\n\n
\n
\n\n
\n\t
\n\t\t \n\t
\n {% if doc.practitioner %}\n\t
\n\t\t\t: {{doc.practitioner}}\n\t
\n {%- endif -%}\n\t
\n\n {% if doc.encounter_date %}\n\t
\n\t\t
\n\t\t\n\t\t
\n\t\t
\n\t\t: {{doc.encounter_date}}\n\t\t
\n
\n\t {%- endif -%}\n {% if doc.encounter_time %}\n\t
\n\t\t
\n\t\t\n\t\t
\n\t\t
\n\t\t: {{doc.encounter_time}}\n\t\t
\n
\n\t {%- endif -%}\n {% if doc.medical_department %}\n\t
\n\t\t
\n\t\t\n\t\t
\n\t\t
\n\t\t: {{doc.visit_department}}\n\t\t
\n
\n {%- endif -%}\n
\n\n
\n\n
\n
\n
\n {% if doc.symptoms_in_print%}\n {% if doc.symptoms %}\n Complaints:\n {{doc.symptoms}}\n \t
\n {%- endif -%}\n {%- endif -%}\n\n {% if doc.diagnosis_in_print%}\n {% if doc.diagnosis %}\n \t Diagnosis:\n {{doc.diagnosis}}\n
\n {%- endif -%}\n {%- endif -%}\n\n
\n\n
\n {% if doc.drug_prescription %}\n
\n Rx,\n \n \n \n\n {%- for row in doc.drug_prescription -%}\n \n \n \t\n \t\n \n \n\t {%- endfor -%}\n \n
\n {%- if row.drug_name -%}{{ row.drug_name }}{%- endif -%}\n \n {%- if row.dosage -%}{{ row.dosage }}{%- endif -%}\n \n {%- if row.period -%}{{ row.period }}{%- endif -%}\n\t\t \n\t\t\t
\n {%- if row.comment -%}{{ row.comment }}{%- endif -%}\n
\n\t\t
\n\n\n {%- endif -%}\n
\n\n\n
\n {% if doc.lab_test_prescription %}\n Investigations,\n \n \n \n\n {%- for row in doc.lab_test_prescription -%}\n \n \n \n \n\n\t {%- endfor -%}\n \n
\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}{%- endif -%}\n \n\t\t\t
\n {%- if row.lab_test_comment -%}{{ row.lab_test_comment }}{%- endif -%}\n
\n\t\t
\n\n\n {%- endif -%}\n
\n
\n {% if doc.encounter_comment %}\n
\n {{doc.encounter_comment}}\n {%- endif -%}\n
\n", + "idx": 0, + "line_breaks": 0, + "modified": "2018-09-04 11:52:54.473702", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Encounter Print", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/healthcare/healthcare/print_format/lab_test_print/__init__.py b/healthcare/healthcare/print_format/lab_test_print/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/print_format/lab_test_print/lab_test_print.json b/healthcare/healthcare/print_format/lab_test_print/lab_test_print.json new file mode 100644 index 0000000..f7d1676 --- /dev/null +++ b/healthcare/healthcare/print_format/lab_test_print/lab_test_print.json @@ -0,0 +1,23 @@ +{ + "align_labels_right": 0, + "creation": "2017-04-24 15:38:45.332473", + "custom_format": 1, + "disabled": 0, + "doc_type": "Lab Test", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "
\n {% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n
\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n

WORKSHEET

\n\t
\n\t
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n {% else %}\n\t\t\t{% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t {% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n
\n
\n\n\t
\n

Department of {{ doc.department }}

\n
\n\n\t\n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n \n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n\t\n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }}
\n
\n {% if doc.worksheet_instructions %}\n
\n Instructions\n {{ doc.worksheet_instructions }}\n {%- endif -%}\n
\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"require_test_result_approval\") == '1' and doc.status != \"Approved\") %}\n Lab Tests have to be Approved for Print .. !\n {%- else -%}\n
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n\t\t{% else %}\n\t\t {% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t\t{% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n\n {% if doc.result_date %}\n
\n
\n \n
\n
\n {{ doc.result_date }}\n
\n
\n {%- endif -%}\n\n
\n\n
\n\n
\n

Department of {{ doc.department }}

\n
\n\n\t
\n\t\t{% if doc.result_legend and (doc.legend_print_position == \"Top\" or doc.legend_print_position == \"Both\")%}\n\t\tResult Legend:\n\t\t{{ doc.result_legend }}\n\t\t{%- endif -%}\n\t
\n\n \n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n\t\t\t\t\t{%- if row.result_value -%}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.result_value }}\n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t{%- endif -%}\n \n\t\t\t\t\t{%- if row.secondary_uom and row.conversion_factor and row.secondary_uom_result -%}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.secondary_uom_result }}\n  {{ row.secondary_uom }}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t \n\t\t\t\t\t{%- endif -%}\n
\n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n \n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n\n\t\t\t{%- if doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n\t\t\t{%- if doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }} \n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n
OrganismColony Population
{{ row.organism }} \n\t\t\t\t\t{{ row.colony_population }}\n\t\t\t\t\t{% if row.colony_uom %}\n\t\t\t\t\t\t{{ row.colony_uom }}\n\t\t\t\t\t{% endif %}\n\t\t\t\t
AntibioticSensitivity
{{ row.antibiotic }} {{ row.antibiotic_sensitivity }}
\n
\n {% if doc.custom_result %}\n
\n
{{ doc.custom_result }}
\n {%- endif -%}\n
\n\n
\n {% if doc.lab_test_comment %}\n
\n Comments\n {{ doc.lab_test_comment }}\n {%- endif -%}\n
\n\n
\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n {%- if doc.employee_name -%}\n
{{ doc.employee_name }}
\n {%- endif -%}\n {%- if doc.employee_designation -%}\n
{{ doc.employee_designation }}
\n {%- endif -%}\n {%- else -%}\n {%- if frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") -%}\n
{{ frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}
\n {%- endif -%}\n {%- endif -%}\n
\n\n
\n {% if doc.result_legend and (doc.legend_print_position == \"Bottom\" or doc.legend_print_position == \"Both\" or doc.legend_print_position == \"\")%}\n
\n Result Legend\n {{ doc.result_legend }}\n {%- endif -%}\n
\n {%- endif -%}\n
", + "idx": 0, + "line_breaks": 0, + "modified": "2020-07-08 15:34:28.866798", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test Print", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/healthcare/healthcare/print_format/sample_id_print/__init__.py b/healthcare/healthcare/print_format/sample_id_print/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/print_format/sample_id_print/sample_id_print.json b/healthcare/healthcare/print_format/sample_id_print/sample_id_print.json new file mode 100644 index 0000000..4819e6d --- /dev/null +++ b/healthcare/healthcare/print_format/sample_id_print/sample_id_print.json @@ -0,0 +1,22 @@ +{ + "align_labels_left": 0, + "creation": "2017-02-17 17:40:52.967840", + "custom_format": 1, + "disabled": 0, + "doc_type": "Sample Collection", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "\n{% set column = 0 %}\n\n{% for _ in range(0, doc.num_print) %}\n{% if column == 0 -%}{% endif %}\n\t\n{% if column == 0 %}{% set column = column+1 %}\n{% elif column == 2%} {%- set column = 0 %}\n{% else %}{%- set column = column+1 -%}{%- endif %}\n\t\n{% endfor %}\n
{{doc.name}}
{{doc.patient}}
\n{% if doc.patient_age %}{{doc.patient_age}}, {% endif %} {% if doc.patient_sex %}{{doc.patient_sex}}{% endif %}
{% if doc.collected_time %}{{doc.collected_time}} {% endif %}
{% if doc.collected_by %} {{doc.collected_by}} {% endif %}
", + "idx": 0, + "line_breaks": 0, + "modified": "2017-03-30 18:09:39.537609", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Sample ID Print", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "show_section_headings": 0, + "standard": "Yes" +} diff --git a/healthcare/healthcare/report/__init__.py b/healthcare/healthcare/report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/report/diagnosis_trends/__init__.py b/healthcare/healthcare/report/diagnosis_trends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.js b/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.js new file mode 100644 index 0000000..75b1899 --- /dev/null +++ b/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.js @@ -0,0 +1,93 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports['Diagnosis Trends'] = { + "filters": [ + { + fieldname: 'department', + label: __('Medical Department'), + fieldtype: 'Link', + options: 'Medical Department' + }, + { + fieldname: 'from_date', + label: __('From Date'), + fieldtype: 'Date', + default: frappe.defaults.get_user_default('year_start_date'), + reqd: 1 + }, + { + fieldname: 'to_date', + label: __('To Date'), + fieldtype: 'Date', + default: frappe.defaults.get_user_default('year_end_date'), + reqd: 1 + }, + { + fieldname: 'range', + label: __('Range'), + fieldtype: 'Select', + options:[ + {label: __('Weekly'), value: 'Weekly'}, + {label: __('Monthly'), value: 'Monthly'}, + {label: __('Quarterly'), value: 'Quarterly'}, + {label: __('Yearly'), value: 'Yearly'} + ], + default: 'Monthly', + reqd: 1 + } + ], + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + events: { + onCheckRow: function(data) { + row_name = data[2].content; + length = data.length; + + row_values = data.slice(3,length-1).map(function (column) { + return column.content; + }) + + entry = { + 'name': row_name, + 'values': row_values + } + + let raw_data = frappe.query_report.chart.data; + let new_datasets = raw_data.datasets; + + let found = false; + for (let i=0; i < new_datasets.length; i++) { + if (new_datasets[i].name == row_name) { + found = true; + new_datasets.splice(i,1); + break; + } + } + + if (!found) { + new_datasets.push(entry); + } + + let new_data = { + labels: raw_data.labels, + datasets: new_datasets + } + + setTimeout(() => { + frappe.query_report.chart.update(new_data) + }, 500) + + + setTimeout(() => { + frappe.query_report.chart.draw(true); + }, 1000) + + frappe.query_report.raw_chart_data = new_data; + }, + } + }) + }, +}; diff --git a/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.json b/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.json new file mode 100644 index 0000000..70d8f17 --- /dev/null +++ b/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.json @@ -0,0 +1,35 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-09-08 11:21:13.655665", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-11-10 09:27:19.153846", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Diagnosis Trends", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Patient Encounter Diagnosis", + "report_name": "Diagnosis Trends", + "report_type": "Script Report", + "roles": [ + { + "role": "Healthcare Administrator" + }, + { + "role": "Nursing User" + }, + { + "role": "Physician" + }, + { + "role": "System Manager" + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.py b/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.py new file mode 100644 index 0000000..789a460 --- /dev/null +++ b/healthcare/healthcare/report/diagnosis_trends/diagnosis_trends.py @@ -0,0 +1,167 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + +import frappe +from frappe import _, scrub +from frappe.database.query import OPERATOR_MAP +from frappe.utils import add_days, add_to_date, flt, getdate + +from erpnext.accounts.utils import get_fiscal_year + + +def execute(filters=None): + return DiagnosisTrends(filters).run() + + +class DiagnosisTrends(object): + """ + Diagnosis Trends Report. + """ + + def __init__(self, filters=None): + self.data = [] + self.periodic_daterange = [] + self.filters = frappe._dict(filters or {}) + self.months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ] + self.get_period_date_ranges() + + def run(self): + self.get_columns() + self.get_data() + self.get_chart_data() + + return self.columns, self.data, None, self.chart + + def get_period_date_ranges(self): + from dateutil.relativedelta import MO, relativedelta + + from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date) + + increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get( + self.filters.range, 1 + ) + + if self.filters.range in ["Monthly", "Quarterly"]: + from_date = from_date.replace(day=1) + elif self.filters.range == "Yearly": + from_date = get_fiscal_year(from_date)[1] + else: + from_date = from_date + relativedelta(from_date, weekday=MO(-1)) + + for _ in range(1, 53): + if self.filters.range == "Weekly": + period_end_date = add_days(from_date, 6) + else: + period_end_date = add_to_date(from_date, months=increment, days=-1) + + if period_end_date > to_date: + period_end_date = to_date + + self.periodic_daterange.append(period_end_date) + + from_date = add_days(period_end_date, 1) + if period_end_date == to_date: + break + + def get_columns(self): + self.columns = [] + + self.columns.append( + { + "label": _("Diagnosis"), + "fieldname": "diagnosis", + "fieldtype": "Link", + "options": "Diagnosis", + "width": 150, + } + ) + + for end_date in self.periodic_daterange: + period = self.get_period(end_date) + self.columns.append( + {"label": _(period), "fieldname": scrub(period), "fieldtype": "Int", "width": 120} + ) + + self.columns.append( + {"label": _("Total"), "fieldname": "total", "fieldtype": "Int", "width": 120} + ) + + def get_data(self): + pe_diagnosis = frappe.qb.DocType("Patient Encounter Diagnosis") + query = ( + frappe.qb.from_(pe_diagnosis) + .select("name", "creation", "diagnosis") + .where(pe_diagnosis.creation[self.filters.from_date : self.filters.to_date]) + ) + + department = self.filters.get("department") + + if department: + encounters = frappe.get_all( + "Patient Encounter", filters={"medical_department": department}, pluck="name" + ) + if encounters: + _operator = OPERATOR_MAP["in"] + query = query.where(_operator(pe_diagnosis.parent, encounters)) + + self.entries = query.run(as_dict=True) + self.get_rows() + + def get_period(self, appointment_date): + if self.filters.range == "Weekly": + period = "Week " + str(appointment_date.isocalendar()[1]) + elif self.filters.range == "Monthly": + period = str(self.months[appointment_date.month - 1]) + elif self.filters.range == "Quarterly": + period = "Quarter " + str(((appointment_date.month - 1) // 3) + 1) + else: + year = get_fiscal_year(appointment_date, company=self.filters.company) + period = str(year[0]) + + if getdate(self.filters.from_date).year != getdate(self.filters.to_date).year: + period += " " + str(appointment_date.year) + + return period + + def get_rows(self): + self.get_periodic_data() + + for entity, period_data in self.appointment_periodic_data.items(): + row = {"diagnosis": entity} + + total = 0 + for end_date in self.periodic_daterange: + period = self.get_period(end_date) + amount = flt(period_data.get(period, 0.0)) + row[scrub(period)] = amount + total += amount + + row["total"] = total + + self.data.append(row) + + def get_periodic_data(self): + self.appointment_periodic_data = frappe._dict() + + for d in self.entries: + period = self.get_period(d.get("creation")) + self.appointment_periodic_data.setdefault(d.diagnosis, frappe._dict()).setdefault(period, 0.0) + self.appointment_periodic_data[d.diagnosis][period] += 1 + + def get_chart_data(self): + length = len(self.columns) + labels = [d.get("label") for d in self.columns[1 : length - 1]] + self.chart = {"data": {"labels": labels, "datasets": []}, "type": "line"} diff --git a/healthcare/healthcare/report/diagnosis_trends/test_diagnosis_trends.py b/healthcare/healthcare/report/diagnosis_trends/test_diagnosis_trends.py new file mode 100644 index 0000000..e38995e --- /dev/null +++ b/healthcare/healthcare/report/diagnosis_trends/test_diagnosis_trends.py @@ -0,0 +1,104 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe import DuplicateEntryError +from frappe.utils import add_months, getdate, add_days +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_practitioner, +) + +from healthcare.healthcare.report.diagnosis_trends.diagnosis_trends import execute +from healthcare.healthcare.test_utils import create_encounter + +months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + + +class TestDiagnosisTrends(FrappeTestCase): + @classmethod + def setUpClass(cls): + cls.create_diagnosis() + + @classmethod + def create_diagnosis(cls): + medical_department = frappe.get_doc( + {"doctype": "Medical Department", "department": "Cardiology"} + ) + try: + medical_department.insert() + except DuplicateEntryError: + pass + + patient = frappe.get_list("Patient")[0] + practitioner_name = create_practitioner(medical_department=medical_department.name) + encounter_cardiology = create_encounter( + patient=patient.name, + practitioner=practitioner_name, + ) + encounter = frappe.get_list( + "Patient Encounter", + )[0] + + try: + cls.diagnosis = frappe.get_doc( + { + "doctype": "Diagnosis", + "diagnosis": "Fever", + } + ) + cls.diagnosis.insert() + except DuplicateEntryError: + pass + + try: + cls.diagnosis_cardio = frappe.get_doc( + { + "doctype": "Diagnosis", + "diagnosis": "Heart Attack", + } + ) + cls.diagnosis_cardio.insert() + except DuplicateEntryError: + pass + + encounter = frappe.get_doc("Patient Encounter", encounter["name"]) + encounter.append( + "diagnosis", + { + "diagnosis": "Fever", + }, + ) + encounter.save() + + encounter_cardiology.reload() + encounter_cardiology.append( + "diagnosis", + { + "diagnosis": "Heart Attack", + }, + ) + encounter_cardiology.save() + + def test_report_data(self): + filters = { + "from_date": str(add_months(getdate(), -12)), + "to_date": str(add_days(getdate(), 1)), + "range": "Monthly", + } + + report = execute(filters) + data = [i["diagnosis"] for i in report[1]] + self.assertIn(self.diagnosis.diagnosis, data) + + def test_report_data_with_filters(self): + medical_department = frappe.get_doc("Medical Department", "Cardiology") + + filters = { + "from_date": str(add_months(getdate(), -12)), + "to_date": str(add_days(getdate(), 1)), + "range": "Monthly", + "department": medical_department.name, + } + report = execute(filters) + + data = [i["diagnosis"] for i in report[1]] + + self.assertIn(self.diagnosis_cardio.diagnosis, data) diff --git a/healthcare/healthcare/report/inpatient_medication_orders/__init__.py b/healthcare/healthcare/report/inpatient_medication_orders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js b/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js new file mode 100644 index 0000000..ce9260a --- /dev/null +++ b/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js @@ -0,0 +1,57 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Inpatient Medication Orders"] = { + "filters": [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + reqd: 1 + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.now_date(), + reqd: 1 + }, + { + fieldname: "patient", + label: __("Patient"), + fieldtype: "Link", + options: "Patient" + }, + { + fieldname: "service_unit", + label: __("Healthcare Service Unit"), + fieldtype: "Link", + options: "Healthcare Service Unit", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company, + 'is_group': 0 + } + } + } + }, + { + fieldname: "show_completed_orders", + label: __("Show Completed Orders"), + fieldtype: "Check", + default: 1 + } + ] +}; diff --git a/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json b/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json new file mode 100644 index 0000000..9217fa1 --- /dev/null +++ b/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json @@ -0,0 +1,36 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2020-11-23 17:25:58.802949", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2020-11-23 19:40:20.227591", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Medication Orders", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Inpatient Medication Order", + "report_name": "Inpatient Medication Orders", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Healthcare Administrator" + }, + { + "role": "Nursing User" + }, + { + "role": "Physician" + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py new file mode 100644 index 0000000..4bad03c --- /dev/null +++ b/healthcare/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py @@ -0,0 +1,189 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + + +import frappe + +from healthcare.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( + get_current_healthcare_service_unit, +) + + +def execute(filters=None): + columns = get_columns() + data = get_data(filters) + chart = get_chart_data(data) + + return columns, data, None, chart + + +def get_columns(): + return [ + { + "fieldname": "patient", + "fieldtype": "Link", + "label": "Patient", + "options": "Patient", + "width": 200, + }, + { + "fieldname": "healthcare_service_unit", + "fieldtype": "Link", + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit", + "width": 150, + }, + { + "fieldname": "drug", + "fieldtype": "Link", + "label": "Drug Code", + "options": "Item", + "width": 150, + }, + {"fieldname": "drug_name", "fieldtype": "Data", "label": "Drug Name", "width": 150}, + { + "fieldname": "dosage", + "fieldtype": "Link", + "label": "Dosage", + "options": "Prescription Dosage", + "width": 80, + }, + { + "fieldname": "dosage_form", + "fieldtype": "Link", + "label": "Dosage Form", + "options": "Dosage Form", + "width": 100, + }, + {"fieldname": "date", "fieldtype": "Date", "label": "Date", "width": 100}, + {"fieldname": "time", "fieldtype": "Time", "label": "Time", "width": 100}, + {"fieldname": "is_completed", "fieldtype": "Check", "label": "Is Order Completed", "width": 100}, + { + "fieldname": "healthcare_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "width": 200, + }, + { + "fieldname": "inpatient_medication_entry", + "fieldtype": "Link", + "label": "Inpatient Medication Entry", + "options": "Inpatient Medication Entry", + "width": 200, + }, + { + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "width": 200, + }, + ] + + +def get_data(filters): + conditions, values = get_conditions(filters) + + data = frappe.db.sql( + """ + SELECT + parent.patient, parent.inpatient_record, parent.practitioner, + child.drug, child.drug_name, child.dosage, child.dosage_form, + child.date, child.time, child.is_completed, child.name + FROM `tabInpatient Medication Order` parent + INNER JOIN `tabInpatient Medication Order Entry` child + ON child.parent = parent.name + WHERE + parent.docstatus = 1 + {conditions} + ORDER BY date, time + """.format( + conditions=conditions + ), + values, + as_dict=1, + ) + + data = get_inpatient_details(data, filters.get("service_unit")) + + return data + + +def get_conditions(filters): + conditions = "" + values = dict() + + if filters.get("company"): + conditions += " AND parent.company = %(company)s" + values["company"] = filters.get("company") + + if filters.get("from_date") and filters.get("to_date"): + conditions += " AND child.date BETWEEN %(from_date)s and %(to_date)s" + values["from_date"] = filters.get("from_date") + values["to_date"] = filters.get("to_date") + + if filters.get("patient"): + conditions += " AND parent.patient = %(patient)s" + values["patient"] = filters.get("patient") + + if not filters.get("show_completed_orders"): + conditions += " AND child.is_completed = 0" + + return conditions, values + + +def get_inpatient_details(data, service_unit): + service_unit_filtered_data = [] + + for entry in data: + entry["healthcare_service_unit"] = get_current_healthcare_service_unit(entry.inpatient_record) + if entry.is_completed: + entry["inpatient_medication_entry"] = get_inpatient_medication_entry(entry.name) + + if ( + service_unit and entry.healthcare_service_unit and service_unit != entry.healthcare_service_unit + ): + service_unit_filtered_data.append(entry) + + entry.pop("name", None) + + for entry in service_unit_filtered_data: + data.remove(entry) + + return data + + +def get_inpatient_medication_entry(order_entry): + return frappe.db.get_value( + "Inpatient Medication Entry Detail", {"against_imoe": order_entry}, "parent" + ) + + +def get_chart_data(data): + if not data: + return None + + labels = ["Pending", "Completed"] + datasets = [] + + status_wise_data = {"Pending": 0, "Completed": 0} + + for d in data: + if d.is_completed: + status_wise_data["Completed"] += 1 + else: + status_wise_data["Pending"] += 1 + + datasets.append( + { + "name": "Inpatient Medication Order Status", + "values": [status_wise_data.get("Pending"), status_wise_data.get("Completed")], + } + ) + + chart = {"data": {"labels": labels, "datasets": datasets}, "type": "donut", "height": 300} + + chart["fieldtype"] = "Data" + + return chart diff --git a/healthcare/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/healthcare/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py new file mode 100644 index 0000000..cc9cc0f --- /dev/null +++ b/healthcare/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py @@ -0,0 +1,145 @@ +# Copyright (c) 2018, InfluxERP +# License: GNU General Public License v3. See license.txt + + +import datetime +from frappe.tests.utils import FrappeTestCase + +import frappe +from frappe.utils import getdate, now_datetime + +from healthcare.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import ( + create_ipme, + create_ipmo, +) +from healthcare.healthcare.doctype.inpatient_record.inpatient_record import ( + admit_patient, + discharge_patient, + schedule_discharge, +) +from healthcare.healthcare.doctype.inpatient_record.test_inpatient_record import ( + create_inpatient, + create_patient, + get_healthcare_service_unit, + mark_invoiced_inpatient_occupancy, +) +from healthcare.healthcare.report.inpatient_medication_orders.inpatient_medication_orders import ( + execute, +) + + +class TestInpatientMedicationOrders(FrappeTestCase): + @classmethod + def setUpClass(self): + frappe.db.sql("delete from `tabInpatient Medication Order` where company='_Test Company'") + frappe.db.sql("delete from `tabInpatient Medication Entry` where company='_Test Company'") + self.patient = create_patient() + self.ip_record = create_records(self.patient) + + def test_inpatient_medication_orders_report(self): + filters = { + "company": "_Test Company", + "from_date": getdate(), + "to_date": getdate(), + "patient": "_Test IPD Patient", + "service_unit": "_Test Service Unit Ip Occupancy - _TC", + } + + report = execute(filters) + + expected_data = [ + { + "patient": "_Test IPD Patient", + "inpatient_record": self.ip_record.name, + "practitioner": None, + "drug": "Dextromethorphan", + "drug_name": "Dextromethorphan", + "dosage": 1.0, + "dosage_form": "Tablet", + "date": getdate(), + "time": datetime.timedelta(seconds=32400), + "is_completed": 0, + "healthcare_service_unit": "_Test Service Unit Ip Occupancy - _TC", + }, + { + "patient": "_Test IPD Patient", + "inpatient_record": self.ip_record.name, + "practitioner": None, + "drug": "Dextromethorphan", + "drug_name": "Dextromethorphan", + "dosage": 1.0, + "dosage_form": "Tablet", + "date": getdate(), + "time": datetime.timedelta(seconds=50400), + "is_completed": 0, + "healthcare_service_unit": "_Test Service Unit Ip Occupancy - _TC", + }, + { + "patient": "_Test IPD Patient", + "inpatient_record": self.ip_record.name, + "practitioner": None, + "drug": "Dextromethorphan", + "drug_name": "Dextromethorphan", + "dosage": 1.0, + "dosage_form": "Tablet", + "date": getdate(), + "time": datetime.timedelta(seconds=75600), + "is_completed": 0, + "healthcare_service_unit": "_Test Service Unit Ip Occupancy - _TC", + }, + ] + + self.assertEqual(expected_data, report[1]) + + filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time="", to_time="") + ipme = create_ipme(filters) + ipme.submit() + + filters = { + "company": "_Test Company", + "from_date": getdate(), + "to_date": getdate(), + "patient": "_Test IPD Patient", + "service_unit": "_Test Service Unit Ip Occupancy - _TC", + "show_completed_orders": 0, + } + + report = execute(filters) + self.assertEqual(len(report[1]), 0) + + def tearDown(self): + if frappe.db.get_value("Patient", self.patient, "inpatient_record"): + # cleanup - Discharge + schedule_discharge(frappe.as_json({"patient": self.patient})) + self.ip_record.reload() + mark_invoiced_inpatient_occupancy(self.ip_record) + + self.ip_record.reload() + discharge_patient(self.ip_record) + + for entry in frappe.get_all("Inpatient Medication Entry"): + doc = frappe.get_doc("Inpatient Medication Entry", entry.name) + doc.cancel() + doc.delete() + + for entry in frappe.get_all("Inpatient Medication Order"): + doc = frappe.get_doc("Inpatient Medication Order", entry.name) + doc.cancel() + doc.delete() + + +def create_records(patient): + frappe.db.sql("""delete from `tabInpatient Record`""") + + # Admit + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save() + ip_record.reload() + service_unit = get_healthcare_service_unit("_Test Service Unit Ip Occupancy") + admit_patient(ip_record, service_unit, now_datetime()) + + ipmo = create_ipmo(patient) + ipmo.submit() + + return ip_record diff --git a/healthcare/healthcare/report/lab_test_report/__init__.py b/healthcare/healthcare/report/lab_test_report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/report/lab_test_report/lab_test_report.js b/healthcare/healthcare/report/lab_test_report/lab_test_report.js new file mode 100644 index 0000000..7754e2e --- /dev/null +++ b/healthcare/healthcare/report/lab_test_report/lab_test_report.js @@ -0,0 +1,57 @@ +// Copyright (c) 2016, ESS +// License: See license.txt + +frappe.query_reports["Lab Test Report"] = { + "filters": [ + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + "reqd": 1 + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.now_date(), + "reqd": 1 + }, + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "default": frappe.defaults.get_default("Company"), + "options": "Company" + }, + { + "fieldname": "template", + "label": __("Lab Test Template"), + "fieldtype": "Link", + "options": "Lab Test Template" + }, + { + "fieldname": "patient", + "label": __("Patient"), + "fieldtype": "Link", + "options": "Patient" + }, + { + "fieldname": "department", + "label": __("Medical Department"), + "fieldtype": "Link", + "options": "Medical Department" + }, + { + "fieldname": "status", + "label": __("Status"), + "fieldtype": "Select", + "options": "\nCompleted\nApproved\nRejected" + }, + { + "fieldname": "invoiced", + "label": __("Invoiced"), + "fieldtype": "Check" + } + ] +}; diff --git a/healthcare/healthcare/report/lab_test_report/lab_test_report.json b/healthcare/healthcare/report/lab_test_report/lab_test_report.json new file mode 100644 index 0000000..aeb4289 --- /dev/null +++ b/healthcare/healthcare/report/lab_test_report/lab_test_report.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "creation": "2013-04-23 18:15:29", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 1, + "is_standard": "Yes", + "modified": "2020-07-30 18:53:20.102873", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Lab Test Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Lab Test", + "report_name": "Lab Test Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Laboratory User" + }, + { + "role": "Nursing User" + }, + { + "role": "LabTest Approver" + }, + { + "role": "Healthcare Administrator" + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/report/lab_test_report/lab_test_report.py b/healthcare/healthcare/report/lab_test_report/lab_test_report.py new file mode 100644 index 0000000..0d14920 --- /dev/null +++ b/healthcare/healthcare/report/lab_test_report/lab_test_report.py @@ -0,0 +1,207 @@ +# Copyright (c) 2016, ESS +# License: See license.txt + + +import frappe +from frappe import _, msgprint + + +def execute(filters=None): + if not filters: + filters = {} + + data, columns = [], [] + + columns = get_columns() + lab_test_list = get_lab_tests(filters) + + if not lab_test_list: + msgprint(_("No records found")) + return columns, lab_test_list + + data = [] + for lab_test in lab_test_list: + row = frappe._dict( + { + "test": lab_test.name, + "template": lab_test.template, + "company": lab_test.company, + "patient": lab_test.patient, + "patient_name": lab_test.patient_name, + "practitioner": lab_test.practitioner, + "employee": lab_test.employee, + "status": lab_test.status, + "invoiced": lab_test.invoiced, + "result_date": lab_test.result_date, + "department": lab_test.department, + } + ) + data.append(row) + + chart = get_chart_data(data) + report_summary = get_report_summary(data) + return columns, data, None, chart, report_summary + + +def get_columns(): + return [ + { + "fieldname": "test", + "label": _("Lab Test"), + "fieldtype": "Link", + "options": "Lab Test", + "width": "120", + }, + { + "fieldname": "template", + "label": _("Lab Test Template"), + "fieldtype": "Link", + "options": "Lab Test Template", + "width": "120", + }, + { + "fieldname": "company", + "label": _("Company"), + "fieldtype": "Link", + "options": "Company", + "width": "120", + }, + { + "fieldname": "patient", + "label": _("Patient"), + "fieldtype": "Link", + "options": "Patient", + "width": "120", + }, + {"fieldname": "patient_name", "label": _("Patient Name"), "fieldtype": "Data", "width": "120"}, + { + "fieldname": "employee", + "label": _("Lab Technician"), + "fieldtype": "Link", + "options": "Employee", + "width": "120", + }, + {"fieldname": "status", "label": _("Status"), "fieldtype": "Data", "width": "100"}, + {"fieldname": "invoiced", "label": _("Invoiced"), "fieldtype": "Check", "width": "100"}, + {"fieldname": "result_date", "label": _("Result Date"), "fieldtype": "Date", "width": "100"}, + { + "fieldname": "practitioner", + "label": _("Requesting Practitioner"), + "fieldtype": "Link", + "options": "Healthcare Practitioner", + "width": "120", + }, + { + "fieldname": "department", + "label": _("Medical Department"), + "fieldtype": "Link", + "options": "Medical Department", + "width": "100", + }, + ] + + +def get_lab_tests(filters): + conditions = get_conditions(filters) + data = frappe.get_all( + doctype="Lab Test", + fields=[ + "name", + "template", + "company", + "patient", + "patient_name", + "practitioner", + "employee", + "status", + "invoiced", + "result_date", + "department", + ], + filters=conditions, + order_by="submitted_date desc", + ) + return data + + +def get_conditions(filters): + conditions = {"docstatus": ("=", 1)} + + if filters.get("from_date") and filters.get("to_date"): + conditions["result_date"] = ("between", (filters.get("from_date"), filters.get("to_date"))) + filters.pop("from_date") + filters.pop("to_date") + + for key, value in filters.items(): + if filters.get(key): + conditions[key] = value + + return conditions + + +def get_chart_data(data): + if not data: + return None + + labels = ["Completed", "Approved", "Rejected"] + + status_wise_data = {"Completed": 0, "Approved": 0, "Rejected": 0} + + datasets = [] + + for entry in data: + status_wise_data[entry.status] += 1 + + datasets.append( + { + "name": "Lab Test Status", + "values": [ + status_wise_data.get("Completed"), + status_wise_data.get("Approved"), + status_wise_data.get("Rejected"), + ], + } + ) + + chart = { + "data": {"labels": labels, "datasets": datasets}, + "type": "bar", + "height": 300, + } + + return chart + + +def get_report_summary(data): + if not data: + return None + + total_lab_tests = len(data) + invoiced_lab_tests, unbilled_lab_tests = 0, 0 + + for entry in data: + if entry.invoiced: + invoiced_lab_tests += 1 + else: + unbilled_lab_tests += 1 + + return [ + { + "value": total_lab_tests, + "indicator": "Blue", + "label": "Total Lab Tests", + "datatype": "Int", + }, + { + "value": invoiced_lab_tests, + "indicator": "Green", + "label": "Invoiced Lab Tests", + "datatype": "Int", + }, + { + "value": unbilled_lab_tests, + "indicator": "Red", + "label": "Unbilled Lab Tests", + "datatype": "Int", + }, + ] diff --git a/healthcare/healthcare/report/patient_appointment_analytics/__init__.py b/healthcare/healthcare/report/patient_appointment_analytics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js b/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js new file mode 100644 index 0000000..63f63c0 --- /dev/null +++ b/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js @@ -0,0 +1,128 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports['Patient Appointment Analytics'] = { + "filters": [ + { + fieldname: 'tree_type', + label: __('Tree Type'), + fieldtype: 'Select', + options: ['Healthcare Practitioner', 'Medical Department'], + default: 'Healthcare Practitioner', + reqd: 1 + }, + { + fieldname: 'status', + label: __('Appointment Status'), + fieldtype: 'Select', + options:[ + {label: __('Scheduled'), value: 'Scheduled'}, + {label: __('Open'), value: 'Open'}, + {label: __('Closed'), value: 'Closed'}, + {label: __('Expired'), value: 'Expired'}, + {label: __('Cancelled'), value: 'Cancelled'} + ] + }, + { + fieldname: 'appointment_type', + label: __('Appointment Type'), + fieldtype: 'Link', + options: 'Appointment Type' + }, + { + fieldname: 'practitioner', + label: __('Healthcare Practitioner'), + fieldtype: 'Link', + options: 'Healthcare Practitioner' + }, + { + fieldname: 'department', + label: __('Medical Department'), + fieldtype: 'Link', + options: 'Medical Department' + }, + { + fieldname: 'from_date', + label: __('From Date'), + fieldtype: 'Date', + default: frappe.defaults.get_user_default('year_start_date'), + reqd: 1 + }, + { + fieldname: 'to_date', + label: __('To Date'), + fieldtype: 'Date', + default: frappe.defaults.get_user_default('year_end_date'), + reqd: 1 + }, + { + fieldname: 'range', + label: __('Range'), + fieldtype: 'Select', + options:[ + {label: __('Weekly'), value: 'Weekly'}, + {label: __('Monthly'), value: 'Monthly'}, + {label: __('Quarterly'), value: 'Quarterly'}, + {label: __('Yearly'), value: 'Yearly'} + ], + default: 'Monthly', + reqd: 1 + } + ], + after_datatable_render: function(datatable_obj) { + $(datatable_obj.wrapper).find(".dt-row-0").find('input[type=checkbox]').click(); + }, + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + events: { + onCheckRow: function(data) { + row_name = data[2].content; + length = data.length; + + row_values = data.slice(3,length-1).map(function (column) { + return column.content; + }) + + entry = { + 'name': row_name, + 'values': row_values + } + + let raw_data = frappe.query_report.chart.data; + let new_datasets = raw_data.datasets; + + let found = false; + for (let i=0; i < new_datasets.length;i++) { + if (new_datasets[i].name == row_name) { + found = true; + new_datasets.splice(i,1); + break; + } + } + + if (!found) { + new_datasets.push(entry); + } + + let new_data = { + labels: raw_data.labels, + datasets: new_datasets + } + + setTimeout(() => { + frappe.query_report.chart.update(new_data) + }, 500) + + + setTimeout(() => { + frappe.query_report.chart.draw(true); + }, 1000) + + frappe.query_report.raw_chart_data = new_data; + }, + } + }) + }, +}; diff --git a/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json b/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json new file mode 100644 index 0000000..64750c0 --- /dev/null +++ b/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json @@ -0,0 +1,36 @@ +{ + "add_total_row": 1, + "creation": "2020-03-02 15:13:16.273493", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-03-02 15:13:16.273493", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Appointment Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Patient Appointment", + "report_name": "Patient Appointment Analytics", + "report_type": "Script Report", + "roles": [ + { + "role": "Healthcare Administrator" + }, + { + "role": "LabTest Approver" + }, + { + "role": "Physician" + }, + { + "role": "Nursing User" + }, + { + "role": "Laboratory User" + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py b/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py new file mode 100644 index 0000000..a20f51b --- /dev/null +++ b/healthcare/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py @@ -0,0 +1,203 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _, scrub +from frappe.utils import add_days, add_to_date, flt, getdate + +from erpnext.accounts.utils import get_fiscal_year + + +def execute(filters=None): + return Analytics(filters).run() + + +class Analytics(object): + def __init__(self, filters=None): + """Patient Appointment Analytics Report.""" + self.filters = frappe._dict(filters or {}) + self.months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ] + self.get_period_date_ranges() + + def run(self): + self.get_columns() + self.get_data() + self.get_chart_data() + + return self.columns, self.data, None, self.chart + + def get_period_date_ranges(self): + from dateutil.relativedelta import MO, relativedelta + + from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date) + + increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get( + self.filters.range, 1 + ) + + if self.filters.range in ["Monthly", "Quarterly"]: + from_date = from_date.replace(day=1) + elif self.filters.range == "Yearly": + from_date = get_fiscal_year(from_date)[1] + else: + from_date = from_date + relativedelta(from_date, weekday=MO(-1)) + + self.periodic_daterange = [] + for dummy in range(1, 53): + if self.filters.range == "Weekly": + period_end_date = add_days(from_date, 6) + else: + period_end_date = add_to_date(from_date, months=increment, days=-1) + + if period_end_date > to_date: + period_end_date = to_date + + self.periodic_daterange.append(period_end_date) + + from_date = add_days(period_end_date, 1) + if period_end_date == to_date: + break + + def get_columns(self): + self.columns = [] + + if self.filters.tree_type == "Healthcare Practitioner": + self.columns.append( + { + "label": _("Healthcare Practitioner"), + "options": "Healthcare Practitioner", + "fieldname": "practitioner", + "fieldtype": "Link", + "width": 200, + } + ) + + elif self.filters.tree_type == "Medical Department": + self.columns.append( + { + "label": _("Medical Department"), + "fieldname": "department", + "fieldtype": "Link", + "options": "Medical Department", + "width": 150, + } + ) + + for end_date in self.periodic_daterange: + period = self.get_period(end_date) + self.columns.append( + {"label": _(period), "fieldname": scrub(period), "fieldtype": "Int", "width": 120} + ) + + self.columns.append( + {"label": _("Total"), "fieldname": "total", "fieldtype": "Int", "width": 120} + ) + + def get_data(self): + if self.filters.tree_type == "Healthcare Practitioner": + self.get_appointments_based_on_healthcare_practitioner() + self.get_rows() + + elif self.filters.tree_type == "Medical Department": + self.get_appointments_based_on_medical_department() + self.get_rows() + + def get_period(self, appointment_date): + if self.filters.range == "Weekly": + period = "Week " + str(appointment_date.isocalendar()[1]) + elif self.filters.range == "Monthly": + period = str(self.months[appointment_date.month - 1]) + elif self.filters.range == "Quarterly": + period = "Quarter " + str(((appointment_date.month - 1) // 3) + 1) + else: + year = get_fiscal_year(appointment_date, company=self.filters.company) + period = str(year[0]) + + if getdate(self.filters.from_date).year != getdate(self.filters.to_date).year: + period += " " + str(appointment_date.year) + + return period + + def get_appointments_based_on_healthcare_practitioner(self): + filters = self.get_common_filters() + + self.entries = frappe.db.get_all( + "Patient Appointment", + fields=["appointment_date", "name", "patient", "practitioner"], + filters=filters, + ) + + def get_appointments_based_on_medical_department(self): + filters = self.get_common_filters() + if not filters.get("department"): + filters["department"] = ("!=", "") + + self.entries = frappe.db.get_all( + "Patient Appointment", + fields=["appointment_date", "name", "patient", "practitioner", "department"], + filters=filters, + ) + + def get_common_filters(self): + filters = {} + filters["appointment_date"] = ("between", [self.filters.from_date, self.filters.to_date]) + for entry in ["appointment_type", "practitioner", "department", "status"]: + if self.filters.get(entry): + filters[entry] = self.filters.get(entry) + + return filters + + def get_rows(self): + self.data = [] + self.get_periodic_data() + + for entity, period_data in self.appointment_periodic_data.items(): + if self.filters.tree_type == "Healthcare Practitioner": + row = {"practitioner": entity} + elif self.filters.tree_type == "Medical Department": + row = {"department": entity} + + total = 0 + for end_date in self.periodic_daterange: + period = self.get_period(end_date) + amount = flt(period_data.get(period, 0.0)) + row[scrub(period)] = amount + total += amount + + row["total"] = total + + self.data.append(row) + + def get_periodic_data(self): + self.appointment_periodic_data = frappe._dict() + + for d in self.entries: + period = self.get_period(d.get("appointment_date")) + if self.filters.tree_type == "Healthcare Practitioner": + self.appointment_periodic_data.setdefault(d.practitioner, frappe._dict()).setdefault( + period, 0.0 + ) + self.appointment_periodic_data[d.practitioner][period] += 1 + + elif self.filters.tree_type == "Medical Department": + self.appointment_periodic_data.setdefault(d.department, frappe._dict()).setdefault(period, 0.0) + self.appointment_periodic_data[d.department][period] += 1 + + def get_chart_data(self): + length = len(self.columns) + labels = [d.get("label") for d in self.columns[1 : length - 1]] + self.chart = {"data": {"labels": labels, "datasets": []}, "type": "line"} diff --git a/healthcare/healthcare/test_utils.py b/healthcare/healthcare/test_utils.py new file mode 100644 index 0000000..7266fb9 --- /dev/null +++ b/healthcare/healthcare/test_utils.py @@ -0,0 +1,12 @@ +import frappe +from frappe import DuplicateEntryError + + +def create_encounter(patient, practitioner, submit=False): + encounter = frappe.new_doc("Patient Encounter") + encounter.patient = patient + encounter.practitioner = practitioner + encounter.save() + if submit: + encounter.submit() + return encounter diff --git a/healthcare/healthcare/utils.py b/healthcare/healthcare/utils.py new file mode 100644 index 0000000..57f7e19 --- /dev/null +++ b/healthcare/healthcare/utils.py @@ -0,0 +1,960 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, earthians and contributors +# For license information, please see license.txt + + +import json +import math + +import frappe +from frappe import _ +from frappe.utils import cstr, get_link_to_form, rounded, time_diff_in_hours +from frappe.utils.formatters import format_value +from erpnext.setup.utils import insert_record + +from healthcare.healthcare.doctype.fee_validity.fee_validity import create_fee_validity +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import ( + get_income_account, +) +from healthcare.healthcare.doctype.lab_test.lab_test import create_multiple +from healthcare.setup import setup_healthcare + + +@frappe.whitelist() +def get_healthcare_services_to_invoice(patient, company): + patient = frappe.get_doc("Patient", patient) + items_to_invoice = [] + if patient: + validate_customer_created(patient) + # Customer validated, build a list of billable services + items_to_invoice += get_appointments_to_invoice(patient, company) + items_to_invoice += get_encounters_to_invoice(patient, company) + items_to_invoice += get_lab_tests_to_invoice(patient, company) + items_to_invoice += get_clinical_procedures_to_invoice(patient, company) + items_to_invoice += get_inpatient_services_to_invoice(patient, company) + items_to_invoice += get_therapy_plans_to_invoice(patient, company) + items_to_invoice += get_therapy_sessions_to_invoice(patient, company) + + return items_to_invoice + + +def validate_customer_created(patient): + if not frappe.db.get_value("Patient", patient.name, "customer"): + msg = _("Please set a Customer linked to the Patient") + msg += " {0}".format(patient.name) + frappe.throw(msg, title=_("Customer Not Found")) + + +def get_appointments_to_invoice(patient, company): + appointments_to_invoice = [] + patient_appointments = frappe.get_list( + "Patient Appointment", + fields="*", + filters={ + "patient": patient.name, + "company": company, + "invoiced": 0, + "status": ["not in", "Cancelled"], + }, + order_by="appointment_date", + ) + + for appointment in patient_appointments: + # Procedure Appointments + if appointment.procedure_template: + if frappe.db.get_value( + "Clinical Procedure Template", appointment.procedure_template, "is_billable" + ): + appointments_to_invoice.append( + { + "reference_type": "Patient Appointment", + "reference_name": appointment.name, + "service": appointment.procedure_template, + } + ) + # Consultation Appointments, should check fee validity + else: + if frappe.db.get_single_value( + "Healthcare Settings", "enable_free_follow_ups" + ) and frappe.db.exists("Fee Validity Reference", {"appointment": appointment.name}): + continue # Skip invoicing, fee validty present + practitioner_charge = 0 + income_account = None + service_item = None + if appointment.practitioner: + details = get_service_item_and_practitioner_charge(appointment) + service_item = details.get("service_item") + practitioner_charge = details.get("practitioner_charge") + income_account = get_income_account(appointment.practitioner, appointment.company) + appointments_to_invoice.append( + { + "reference_type": "Patient Appointment", + "reference_name": appointment.name, + "service": service_item, + "rate": practitioner_charge, + "income_account": income_account, + } + ) + + return appointments_to_invoice + + +def get_encounters_to_invoice(patient, company): + if not isinstance(patient, str): + patient = patient.name + encounters_to_invoice = [] + encounters = frappe.get_list( + "Patient Encounter", + fields=["*"], + filters={"patient": patient, "company": company, "invoiced": False, "docstatus": 1}, + ) + if encounters: + for encounter in encounters: + if not encounter.appointment: + practitioner_charge = 0 + income_account = None + service_item = None + if encounter.practitioner: + if encounter.inpatient_record and frappe.db.get_single_value( + "Healthcare Settings", "do_not_bill_inpatient_encounters" + ): + continue + + details = get_service_item_and_practitioner_charge(encounter) + service_item = details.get("service_item") + practitioner_charge = details.get("practitioner_charge") + income_account = get_income_account(encounter.practitioner, encounter.company) + + encounters_to_invoice.append( + { + "reference_type": "Patient Encounter", + "reference_name": encounter.name, + "service": service_item, + "rate": practitioner_charge, + "income_account": income_account, + } + ) + + return encounters_to_invoice + + +def get_lab_tests_to_invoice(patient, company): + lab_tests_to_invoice = [] + lab_tests = frappe.get_list( + "Lab Test", + fields=["name", "template"], + filters={"patient": patient.name, "company": company, "invoiced": False, "docstatus": 1}, + ) + for lab_test in lab_tests: + item, is_billable = frappe.get_cached_value( + "Lab Test Template", lab_test.template, ["item", "is_billable"] + ) + if is_billable: + lab_tests_to_invoice.append( + {"reference_type": "Lab Test", "reference_name": lab_test.name, "service": item} + ) + + lab_prescriptions = frappe.db.sql( + """ + SELECT + lp.name, lp.lab_test_code + FROM + `tabPatient Encounter` et, `tabLab Prescription` lp + WHERE + et.patient=%s + and lp.parent=et.name + and lp.lab_test_created=0 + and lp.invoiced=0 + """, + (patient.name), + as_dict=1, + ) + + for prescription in lab_prescriptions: + item, is_billable = frappe.get_cached_value( + "Lab Test Template", prescription.lab_test_code, ["item", "is_billable"] + ) + if prescription.lab_test_code and is_billable: + lab_tests_to_invoice.append( + {"reference_type": "Lab Prescription", "reference_name": prescription.name, "service": item} + ) + + return lab_tests_to_invoice + + +def get_clinical_procedures_to_invoice(patient, company): + clinical_procedures_to_invoice = [] + procedures = frappe.get_list( + "Clinical Procedure", + fields="*", + filters={"patient": patient.name, "company": company, "invoiced": False}, + ) + for procedure in procedures: + if not procedure.appointment: + item, is_billable = frappe.get_cached_value( + "Clinical Procedure Template", procedure.procedure_template, ["item", "is_billable"] + ) + if procedure.procedure_template and is_billable: + clinical_procedures_to_invoice.append( + {"reference_type": "Clinical Procedure", "reference_name": procedure.name, "service": item} + ) + + # consumables + if ( + procedure.invoice_separately_as_consumables + and procedure.consume_stock + and procedure.status == "Completed" + and not procedure.consumption_invoiced + ): + + service_item = frappe.db.get_single_value( + "Healthcare Settings", "clinical_procedure_consumable_item" + ) + if not service_item: + frappe.throw( + _("Please configure Clinical Procedure Consumable Item in {0}").format( + frappe.utils.get_link_to_form("Healthcare Settings", "Healthcare Settings") + ), + title=_("Missing Configuration"), + ) + + clinical_procedures_to_invoice.append( + { + "reference_type": "Clinical Procedure", + "reference_name": procedure.name, + "service": service_item, + "rate": procedure.consumable_total_amount, + "description": procedure.consumption_details, + } + ) + + procedure_prescriptions = frappe.db.sql( + """ + SELECT + pp.name, pp.procedure + FROM + `tabPatient Encounter` et, `tabProcedure Prescription` pp + WHERE + et.patient=%s + and pp.parent=et.name + and pp.procedure_created=0 + and pp.invoiced=0 + and pp.appointment_booked=0 + """, + (patient.name), + as_dict=1, + ) + + for prescription in procedure_prescriptions: + item, is_billable = frappe.get_cached_value( + "Clinical Procedure Template", prescription.procedure, ["item", "is_billable"] + ) + if is_billable: + clinical_procedures_to_invoice.append( + { + "reference_type": "Procedure Prescription", + "reference_name": prescription.name, + "service": item, + } + ) + + return clinical_procedures_to_invoice + + +def get_inpatient_services_to_invoice(patient, company): + services_to_invoice = [] + inpatient_services = frappe.db.sql( + """ + SELECT + io.* + FROM + `tabInpatient Record` ip, `tabInpatient Occupancy` io + WHERE + ip.patient=%s + and ip.company=%s + and io.parent=ip.name + and io.left=1 + and io.invoiced=0 + """, + (patient.name, company), + as_dict=1, + ) + + for inpatient_occupancy in inpatient_services: + service_unit_type = frappe.db.get_value( + "Healthcare Service Unit", inpatient_occupancy.service_unit, "service_unit_type" + ) + service_unit_type = frappe.get_cached_doc("Healthcare Service Unit Type", service_unit_type) + if service_unit_type and service_unit_type.is_billable: + hours_occupied = time_diff_in_hours(inpatient_occupancy.check_out, inpatient_occupancy.check_in) + qty = 0.5 + if hours_occupied > 0: + actual_qty = hours_occupied / service_unit_type.no_of_hours + floor = math.floor(actual_qty) + decimal_part = actual_qty - floor + if decimal_part > 0.5: + qty = rounded(floor + 1, 1) + elif decimal_part < 0.5 and decimal_part > 0: + qty = rounded(floor + 0.5, 1) + if qty <= 0: + qty = 0.5 + services_to_invoice.append( + { + "reference_type": "Inpatient Occupancy", + "reference_name": inpatient_occupancy.name, + "service": service_unit_type.item, + "qty": qty, + } + ) + + return services_to_invoice + + +def get_therapy_plans_to_invoice(patient, company): + therapy_plans_to_invoice = [] + therapy_plans = frappe.get_list( + "Therapy Plan", + fields=["therapy_plan_template", "name"], + filters={ + "patient": patient.name, + "invoiced": 0, + "company": company, + "therapy_plan_template": ("!=", ""), + }, + ) + for plan in therapy_plans: + therapy_plans_to_invoice.append( + { + "reference_type": "Therapy Plan", + "reference_name": plan.name, + "service": frappe.db.get_value( + "Therapy Plan Template", plan.therapy_plan_template, "linked_item" + ), + } + ) + + return therapy_plans_to_invoice + + +def get_therapy_sessions_to_invoice(patient, company): + therapy_sessions_to_invoice = [] + therapy_plans = frappe.db.get_all("Therapy Plan", {"therapy_plan_template": ("!=", "")}) + therapy_plans_created_from_template = [] + for entry in therapy_plans: + therapy_plans_created_from_template.append(entry.name) + + therapy_sessions = frappe.get_list( + "Therapy Session", + fields="*", + filters={ + "patient": patient.name, + "invoiced": 0, + "company": company, + "therapy_plan": ("not in", therapy_plans_created_from_template), + }, + ) + for therapy in therapy_sessions: + if not therapy.appointment: + if therapy.therapy_type and frappe.db.get_value( + "Therapy Type", therapy.therapy_type, "is_billable" + ): + therapy_sessions_to_invoice.append( + { + "reference_type": "Therapy Session", + "reference_name": therapy.name, + "service": frappe.db.get_value("Therapy Type", therapy.therapy_type, "item"), + } + ) + + return therapy_sessions_to_invoice + + +@frappe.whitelist() +def get_service_item_and_practitioner_charge(doc): + if isinstance(doc, str): + doc = json.loads(doc) + doc = frappe.get_doc(doc) + + service_item = None + practitioner_charge = None + department = doc.medical_department if doc.doctype == "Patient Encounter" else doc.department + + is_inpatient = doc.inpatient_record + + if doc.get("appointment_type"): + service_item, practitioner_charge = get_appointment_type_service_item( + doc.appointment_type, department, is_inpatient + ) + + if not service_item and not practitioner_charge: + service_item, practitioner_charge = get_practitioner_service_item(doc.practitioner, is_inpatient) + if not service_item: + service_item = get_healthcare_service_item(is_inpatient) + + if not service_item: + throw_config_service_item(is_inpatient) + + if not practitioner_charge: + throw_config_practitioner_charge(is_inpatient, doc.practitioner) + + return {"service_item": service_item, "practitioner_charge": practitioner_charge} + + +def get_appointment_type_service_item(appointment_type, department, is_inpatient): + from healthcare.healthcare.doctype.appointment_type.appointment_type import ( + get_service_item_based_on_department, + ) + + item_list = get_service_item_based_on_department(appointment_type, department) + service_item = None + practitioner_charge = None + + if item_list: + if is_inpatient: + service_item = item_list.get("inpatient_visit_charge_item") + practitioner_charge = item_list.get("inpatient_visit_charge") + else: + service_item = item_list.get("op_consulting_charge_item") + practitioner_charge = item_list.get("op_consulting_charge") + + return service_item, practitioner_charge + + +def throw_config_service_item(is_inpatient): + service_item_label = _("Out Patient Consulting Charge Item") + if is_inpatient: + service_item_label = _("Inpatient Visit Charge Item") + + msg = _( + ("Please Configure {0} in ").format(service_item_label) + + """Healthcare Settings""" + ) + frappe.throw(msg, title=_("Missing Configuration")) + + +def throw_config_practitioner_charge(is_inpatient, practitioner): + charge_name = _("OP Consulting Charge") + if is_inpatient: + charge_name = _("Inpatient Visit Charge") + + msg = _( + ("Please Configure {0} for Healthcare Practitioner").format(charge_name) + + """ {0}""".format(practitioner) + ) + frappe.throw(msg, title=_("Missing Configuration")) + + +def get_practitioner_service_item(practitioner, is_inpatient): + service_item = None + practitioner_charge = None + + if is_inpatient: + service_item, practitioner_charge = frappe.db.get_value( + "Healthcare Practitioner", + practitioner, + ["inpatient_visit_charge_item", "inpatient_visit_charge"], + ) + else: + service_item, practitioner_charge = frappe.db.get_value( + "Healthcare Practitioner", practitioner, ["op_consulting_charge_item", "op_consulting_charge"] + ) + + return service_item, practitioner_charge + + +def get_healthcare_service_item(is_inpatient): + service_item = None + + if is_inpatient: + service_item = frappe.db.get_single_value("Healthcare Settings", "inpatient_visit_charge_item") + else: + service_item = frappe.db.get_single_value("Healthcare Settings", "op_consulting_charge_item") + + return service_item + + +def get_practitioner_charge(practitioner, is_inpatient): + if is_inpatient: + practitioner_charge = frappe.db.get_value( + "Healthcare Practitioner", practitioner, "inpatient_visit_charge" + ) + else: + practitioner_charge = frappe.db.get_value( + "Healthcare Practitioner", practitioner, "op_consulting_charge" + ) + if practitioner_charge: + return practitioner_charge + return False + + +def manage_invoice_submit_cancel(doc, method): + if doc.items: + for item in doc.items: + if item.get("reference_dt") and item.get("reference_dn"): + if frappe.get_meta(item.reference_dt).has_field("invoiced"): + set_invoiced(item, method, doc.name) + + if method == "on_submit" and frappe.db.get_single_value( + "Healthcare Settings", "create_lab_test_on_si_submit" + ): + create_multiple("Sales Invoice", doc.name) + + +def set_invoiced(item, method, ref_invoice=None): + invoiced = False + if method == "on_submit": + validate_invoiced_on_submit(item) + invoiced = True + + if item.reference_dt == "Clinical Procedure": + service_item = frappe.db.get_single_value( + "Healthcare Settings", "clinical_procedure_consumable_item" + ) + if service_item == item.item_code: + frappe.db.set_value(item.reference_dt, item.reference_dn, "consumption_invoiced", invoiced) + else: + frappe.db.set_value(item.reference_dt, item.reference_dn, "invoiced", invoiced) + else: + frappe.db.set_value(item.reference_dt, item.reference_dn, "invoiced", invoiced) + + if item.reference_dt == "Patient Appointment": + if frappe.db.get_value("Patient Appointment", item.reference_dn, "procedure_template"): + dt_from_appointment = "Clinical Procedure" + else: + dt_from_appointment = "Patient Encounter" + manage_doc_for_appointment(dt_from_appointment, item.reference_dn, invoiced) + + elif item.reference_dt == "Lab Prescription": + manage_prescriptions( + invoiced, item.reference_dt, item.reference_dn, "Lab Test", "lab_test_created" + ) + + elif item.reference_dt == "Procedure Prescription": + manage_prescriptions( + invoiced, item.reference_dt, item.reference_dn, "Clinical Procedure", "procedure_created" + ) + + +def validate_invoiced_on_submit(item): + if ( + item.reference_dt == "Clinical Procedure" + and frappe.db.get_single_value("Healthcare Settings", "clinical_procedure_consumable_item") + == item.item_code + ): + is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, "consumption_invoiced") + else: + is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, "invoiced") + if is_invoiced: + frappe.throw( + _("The item referenced by {0} - {1} is already invoiced").format( + item.reference_dt, item.reference_dn + ) + ) + + +def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field): + created = frappe.db.get_value(ref_dt, ref_dn, created_check_field) + if created: + # Fetch the doc created for the prescription + doc_created = frappe.db.get_value(dt, {"prescription": ref_dn}) + frappe.db.set_value(dt, doc_created, "invoiced", invoiced) + + +def check_fee_validity(appointment): + if not frappe.db.get_single_value("Healthcare Settings", "enable_free_follow_ups"): + return + + validity = frappe.db.exists( + "Fee Validity", + { + "practitioner": appointment.practitioner, + "patient": appointment.patient, + "valid_till": (">=", appointment.appointment_date), + }, + ) + if not validity: + return + + validity = frappe.get_doc("Fee Validity", validity) + return validity + + +def manage_fee_validity(appointment): + fee_validity = check_fee_validity(appointment) + + if fee_validity: + if appointment.status == "Cancelled" and fee_validity.visited > 0: + fee_validity.visited -= 1 + frappe.db.delete("Fee Validity Reference", {"appointment": appointment.name}) + elif fee_validity.status == "Completed": + return + else: + fee_validity.visited += 1 + fee_validity.append("ref_appointments", {"appointment": appointment.name}) + fee_validity.save(ignore_permissions=True) + else: + fee_validity = create_fee_validity(appointment) + return fee_validity + + +def manage_doc_for_appointment(dt_from_appointment, appointment, invoiced): + dn_from_appointment = frappe.db.get_value( + dt_from_appointment, filters={"appointment": appointment} + ) + if dn_from_appointment: + frappe.db.set_value(dt_from_appointment, dn_from_appointment, "invoiced", invoiced) + + +@frappe.whitelist() +def get_drugs_to_invoice(encounter): + encounter = frappe.get_doc("Patient Encounter", encounter) + if encounter: + patient = frappe.get_doc("Patient", encounter.patient) + if patient: + if patient.customer: + items_to_invoice = [] + for drug_line in encounter.drug_prescription: + if drug_line.drug_code: + qty = 1 + if frappe.db.get_value("Item", drug_line.drug_code, "stock_uom") == "Nos": + qty = drug_line.get_quantity() + + description = "" + if drug_line.dosage and drug_line.period: + description = _("{0} for {1}").format(drug_line.dosage, drug_line.period) + + items_to_invoice.append( + {"drug_code": drug_line.drug_code, "quantity": qty, "description": description} + ) + return items_to_invoice + else: + validate_customer_created(patient) + + +@frappe.whitelist() +def get_children(doctype, parent=None, company=None, is_root=False): + parent_fieldname = "parent_" + doctype.lower().replace(" ", "_") + fields = ["name as value", "is_group as expandable", "lft", "rgt"] + + filters = [["ifnull(`{0}`,'')".format(parent_fieldname), "=", "" if is_root else parent]] + + if is_root: + fields += ["service_unit_type"] if doctype == "Healthcare Service Unit" else [] + filters.append(["company", "=", company]) + else: + fields += ( + ["service_unit_type", "allow_appointments", "inpatient_occupancy", "occupancy_status"] + if doctype == "Healthcare Service Unit" + else [] + ) + fields += [parent_fieldname + " as parent"] + + service_units = frappe.get_list(doctype, fields=fields, filters=filters) + for each in service_units: + if each["expandable"] == 1: # group node + available_count = frappe.db.count( + "Healthcare Service Unit", + filters={"parent_healthcare_service_unit": each["value"], "inpatient_occupancy": 1}, + ) + + if available_count > 0: + occupied_count = frappe.db.count( + "Healthcare Service Unit", + { + "parent_healthcare_service_unit": each["value"], + "inpatient_occupancy": 1, + "occupancy_status": "Occupied", + }, + ) + # set occupancy status of group node + each["occupied_of_available"] = str(occupied_count) + " Occupied of " + str(available_count) + + return service_units + + +@frappe.whitelist() +def get_patient_vitals(patient, from_date=None, to_date=None): + if not patient: + return + + vitals = frappe.db.get_all( + "Vital Signs", + filters={"docstatus": 1, "patient": patient}, + order_by="signs_date, signs_time", + fields=["*"], + ) + + if len(vitals): + return vitals + return False + + +@frappe.whitelist() +def render_docs_as_html(docs): + # docs key value pair {doctype: docname} + docs_html = "
" + for doc in docs: + docs_html += render_doc_as_html(doc["doctype"], doc["docname"])["html"] + "
" + return {"html": docs_html} + + +@frappe.whitelist() +def render_doc_as_html(doctype, docname, exclude_fields=[]): + """ + Render document as HTML + """ + + doc = frappe.get_doc(doctype, docname) + meta = frappe.get_meta(doctype) + doc_html = section_html = section_label = html = "" + sec_on = has_data = False + col_on = 0 + + for df in meta.fields: + # on section break append previous section and html to doc html + if df.fieldtype == "Section Break": + if has_data and col_on and sec_on: + doc_html += section_html + html + "
" + + elif has_data and not col_on and sec_on: + doc_html += """ +
+
+
+ {0} +
+
+
+
+ {1} {2} +
+
+ """.format( + section_label, section_html, html + ) + + # close divs for columns + while col_on: + doc_html += "
" + col_on -= 1 + + sec_on = True + has_data = False + col_on = 0 + section_html = html = "" + + if df.label: + section_label = df.label + continue + + # on column break append html to section html or doc html + if df.fieldtype == "Column Break": + if sec_on and not col_on and has_data: + section_html += """ +
+
+
+ {0} +
+
+
+
+ {1} +
+ """.format( + section_label, html + ) + elif col_on == 1 and has_data: + section_html += "
" + html + "
" + elif col_on > 1 and has_data: + doc_html += "
" + html + "
" + else: + doc_html += """ +
+
+ {0} +
+
+ """.format( + html + ) + + html = "" + col_on += 1 + + if df.label: + html += "
" + df.label + continue + + # on table iterate through items and create table + # based on the in_list_view property + # append to section html or doc html + if df.fieldtype == "Table": + items = doc.get(df.fieldname) + if not items: + continue + child_meta = frappe.get_meta(df.options) + + if not has_data: + has_data = True + table_head = table_row = "" + create_head = True + + for item in items: + table_row += "" + for cdf in child_meta.fields: + if cdf.in_list_view: + if create_head: + table_head += "" + cdf.label + "" + if item.get(cdf.fieldname): + table_row += "" + cstr(item.get(cdf.fieldname)) + "" + else: + table_row += "" + + create_head = False + table_row += "" + + if sec_on: + section_html += """ + + {0} {1} +
+ """.format( + table_head, table_row + ) + else: + html += """ + + {0} {1} +
+ """.format( + table_head, table_row + ) + continue + + # on any other field type add label and value to html + if ( + not df.hidden + and not df.print_hide + and doc.get(df.fieldname) + and df.fieldname not in exclude_fields + ): + formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc) + html += "
{0} : {1}".format(df.label or df.fieldname, formatted_value) + + if not has_data: + has_data = True + + if sec_on and col_on and has_data: + doc_html += section_html + html + "
" + elif sec_on and not col_on and has_data: + doc_html += """ +
+
+ {0} {1} +
+
+ """.format( + section_html, html + ) + + return {"html": doc_html} + + +def update_address_links(address, method): + """ + Hook validate Address + If Patient is linked in Address, also link the associated Customer + """ + if "Healthcare" not in frappe.get_active_domains(): + return + + patient_links = list(filter(lambda link: link.get("link_doctype") == "Patient", address.links)) + + for link in patient_links: + customer = frappe.db.get_value("Patient", link.get("link_name"), "customer") + if customer and not address.has_link("Customer", customer): + address.append("links", dict(link_doctype="Customer", link_name=customer)) + + +def update_patient_email_and_phone_numbers(contact, method): + """ + Hook validate Contact + Update linked Patients' primary mobile and phone numbers + """ + if "Healthcare" not in frappe.get_active_domains() or contact.flags.skip_patient_update: + return + + if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone): + patient_links = list(filter(lambda link: link.get("link_doctype") == "Patient", contact.links)) + + for link in patient_links: + contact_details = frappe.db.get_value( + "Patient", link.get("link_name"), ["email", "mobile", "phone"], as_dict=1 + ) + if contact.email_id and contact.email_id != contact_details.get("email"): + frappe.db.set_value("Patient", link.get("link_name"), "email", contact.email_id) + if contact.mobile_no and contact.mobile_no != contact_details.get("mobile"): + frappe.db.set_value("Patient", link.get("link_name"), "mobile", contact.mobile_no) + if contact.phone and contact.phone != contact_details.get("phone"): + frappe.db.set_value("Patient", link.get("link_name"), "phone", contact.phone) + + +def before_tests(): + # complete setup if missing + from frappe.desk.page.setup_wizard.setup_wizard import setup_complete + + if not frappe.get_list("Company"): + setup_complete( + { + "currency": "INR", + "full_name": "Test User", + "company_name": "InfluxERP Care LLC", + "timezone": "America/New_York", + "company_abbr": "WP", + "industry": "Healthcare", + "country": "United States", + "fy_start_date": "2022-04-01", + "fy_end_date": "2023-03-31", + "language": "english", + "company_tagline": "Testing", + "email": "test@erpnext.com", + "password": "test", + "chart_of_accounts": "Standard", + "domains": ["Healthcare"], + } + ) + + setup_healthcare() + + +def create_healthcare_service_unit_tree_root(doc, method=None): + record = [ + { + "doctype": "Healthcare Service Unit", + "healthcare_service_unit_name": "All Healthcare Service Units", + "is_group": 1, + "company": doc.name, + } + ] + insert_record(record) + + +def validate_nursing_tasks(document): + if not frappe.db.get_single_value("Healthcare Settings", "validate_nursing_checklists"): + return True + + filters = { + "reference_name": document.name, + "mandatory": 1, + "status": ["not in", ["Completed", "Cancelled"]], + } + tasks = frappe.get_all("Nursing Task", filters=filters) + if not tasks: + return True + + frappe.throw( + _("Please complete linked Nursing Tasks before submission: {}").format( + ", ".join(get_link_to_form("Nursing Task", task.name) for task in tasks) + ) + ) diff --git a/healthcare/healthcare/web_form/__init__.py b/healthcare/healthcare/web_form/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/web_form/lab_test/__init__.py b/healthcare/healthcare/web_form/lab_test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/web_form/lab_test/lab_test.js b/healthcare/healthcare/web_form/lab_test/lab_test.js new file mode 100644 index 0000000..efcd8ab --- /dev/null +++ b/healthcare/healthcare/web_form/lab_test/lab_test.js @@ -0,0 +1,34 @@ +frappe.ready(function() { + // bind events here + var normal_test_items = $('div[data-fieldname = "normal_test_items"]'); + var normal_test_items_add_btn = $('button[data-fieldname = "normal_test_items"]'); + var special_test_items = $('div[data-fieldname = "special_test_items"]'); + var special_test_items_add_btn = $('button[data-fieldname = "special_test_items"]'); + var sensitivity_test_items = $('div[data-fieldname = "sensitivity_test_items"]'); + var sensitivity_test_items_add_btn = $('button[data-fieldname = "sensitivity_test_items"]'); + var sensitivity_toggle = $('input[name = "sensitivity_toggle"]'); + var special_toggle = $('input[name = "special_toggle"]'); + var normal_toggle = $('input[name = "normal_toggle"]'); + if(normal_toggle.val() == 1){ + // normal_test_items[0].style.display = "none"; + // normal_test_items[0].setAttribute("hidden", true); + // normal_test_items_add_btn[0].style.visibility = "hidden"; + special_test_items[0].style.display = "none"; + special_test_items_add_btn[0].style.display = "none"; + sensitivity_test_items[0].style.display = "none"; + sensitivity_test_items_add_btn[0].style.display = "none"; + normal_test_items_add_btn[0].style.display = "none"; + }else if(sensitivity_toggle.val() == 1){ + special_test_items[0].style.display = "none"; + special_test_items_add_btn[0].style.display = "none"; + normal_test_items[0].style.display = "none"; + normal_test_items_add_btn[0].style.display = "none"; + sensitivity_test_items_add_btn[0].style.display = "none"; + }else if(special_toggle.val() == 1){ + normal_test_items[0].style.display = "none"; + normal_test_items_add_btn[0].style.display = "none"; + sensitivity_test_items[0].style.display = "none"; + sensitivity_test_items_add_btn[0].style.display = "none"; + special_test_items_add_btn[0].style.display = "none"; + } +}); diff --git a/healthcare/healthcare/web_form/lab_test/lab_test.json b/healthcare/healthcare/web_form/lab_test/lab_test.json new file mode 100644 index 0000000..3509917 --- /dev/null +++ b/healthcare/healthcare/web_form/lab_test/lab_test.json @@ -0,0 +1,460 @@ +{ + "accept_payment": 0, + "allow_comments": 1, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 1, + "amount": 0.0, + "amount_based_on_field": 0, + "creation": "2017-06-06 16:12:33.052258", + "currency": "INR", + "doc_type": "Lab Test", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "introduction_text": "Lab Test", + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2020-06-22 12:59:49.126398", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "lab-test", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "print_format": "Lab Test Print", + "published": 1, + "route": "lab-test", + "route_to_success_link": 0, + "show_attachments": 0, + "show_in_grid": 0, + "show_sidebar": 1, + "sidebar_items": [], + "success_url": "/lab-test", + "title": "Lab Test", + "web_form_fields": [ + { + "allow_read_on_all_link_options": 0, + "fieldname": "lab_test_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Test Name", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "department", + "fieldtype": "Link", + "hidden": 0, + "label": "Department", + "max_length": 0, + "max_value": 0, + "options": "Medical Department", + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "column_break_26", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "label": "Company", + "max_length": 0, + "max_value": 0, + "options": "Company", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "label": "Status", + "max_length": 0, + "max_value": 0, + "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "submitted_date", + "fieldtype": "Datetime", + "hidden": 0, + "label": "Submitted Date", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_first", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "patient", + "fieldtype": "Link", + "hidden": 0, + "label": "Patient", + "max_length": 0, + "max_value": 0, + "options": "Patient", + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "patient_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Patient Name", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "patient_age", + "fieldtype": "Data", + "hidden": 0, + "label": "Age", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "patient_sex", + "fieldtype": "Link", + "hidden": 0, + "label": "Gender", + "max_length": 0, + "max_value": 0, + "options": "Gender", + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "inpatient_record", + "fieldtype": "Link", + "hidden": 0, + "label": "Inpatient Record", + "max_length": 0, + "max_value": 0, + "options": "Inpatient Record", + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "report_preference", + "fieldtype": "Data", + "hidden": 0, + "label": "Report Preference", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "email", + "fieldtype": "Data", + "hidden": 1, + "label": "Email", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "mobile", + "fieldtype": "Data", + "hidden": 1, + "label": "Mobile", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "c_b", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "practitioner", + "fieldtype": "Link", + "hidden": 0, + "label": "Requesting Practitioner", + "max_length": 0, + "max_value": 0, + "options": "Healthcare Practitioner", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "practitioner_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Requesting Practitioner", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "requesting_department", + "fieldtype": "Link", + "hidden": 0, + "label": "Requesting Department", + "max_length": 0, + "max_value": 0, + "options": "Medical Department", + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "label": "Employee (Lab Technician)", + "max_length": 0, + "max_value": 0, + "options": "Employee", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "employee_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Lab Technician Name", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "employee_designation", + "fieldtype": "Data", + "hidden": 0, + "label": "Lab Technician Designation", + "max_length": 0, + "max_value": 0, + "read_only": 1, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_normal", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "lab_test_html", + "fieldtype": "HTML", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "normal_test_items", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Normal Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_descriptive", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "descriptive_test_items", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Descriptive Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "depends_on": "special_toggle", + "fieldname": "organisms_section", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "organisms", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Organism Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_sensitivity", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sensitivity_test_items", + "fieldtype": "Table", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "options": "Sensitivity Test Result", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_comments", + "fieldtype": "Section Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "lab_test_comment", + "fieldtype": "Text", + "hidden": 0, + "label": "Comments", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sb_customresult", + "fieldtype": "Section Break", + "hidden": 0, + "label": "Custom Result", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "custom_result", + "fieldtype": "Text Editor", + "hidden": 0, + "label": "Custom Result", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/web_form/lab_test/lab_test.py b/healthcare/healthcare/web_form/lab_test/lab_test.py new file mode 100644 index 0000000..1bccbd4 --- /dev/null +++ b/healthcare/healthcare/web_form/lab_test/lab_test.py @@ -0,0 +1,34 @@ +import frappe + + +def get_context(context): + context.read_only = 1 + + +def get_list_context(context): + context.row_template = "erpnext/templates/includes/healthcare/lab_test_row_template.html" + context.get_list = get_lab_test_list + + +def get_lab_test_list( + doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified desc" +): + patient = get_patient() + lab_tests = frappe.db.sql( + """select * from `tabLab Test` + where patient = %s order by result_date""", + patient, + as_dict=True, + ) + return lab_tests + + +def get_patient(): + return frappe.get_value("Patient", {"email": frappe.session.user}, "name") + + +def has_website_permission(doc, ptype, user, verbose=False): + if doc.patient == get_patient(): + return True + else: + return False diff --git a/healthcare/healthcare/web_form/patient_appointments/__init__.py b/healthcare/healthcare/web_form/patient_appointments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/web_form/patient_appointments/patient_appointments.js b/healthcare/healthcare/web_form/patient_appointments/patient_appointments.js new file mode 100644 index 0000000..f09e540 --- /dev/null +++ b/healthcare/healthcare/web_form/patient_appointments/patient_appointments.js @@ -0,0 +1,3 @@ +frappe.ready(function() { + // bind events here +}); diff --git a/healthcare/healthcare/web_form/patient_appointments/patient_appointments.json b/healthcare/healthcare/web_form/patient_appointments/patient_appointments.json new file mode 100644 index 0000000..e9cf7a8 --- /dev/null +++ b/healthcare/healthcare/web_form/patient_appointments/patient_appointments.json @@ -0,0 +1,111 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 1, + "amount": 0.0, + "amount_based_on_field": 0, + "creation": "2017-06-07 15:30:44.984832", + "currency": "INR", + "doc_type": "Patient Appointment", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "introduction_text": "Patient Appointments", + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2018-07-16 13:11:08.626316", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "patient-appointments", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "patient-appointments", + "show_sidebar": 1, + "sidebar_items": [], + "success_url": "/patient-appointments", + "title": "Patient Appointments", + "web_form_fields": [ + { + "fieldname": "patient", + "fieldtype": "Link", + "hidden": 0, + "label": "Patient", + "max_length": 0, + "max_value": 0, + "options": "Patient", + "read_only": 0, + "reqd": 1 + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "hidden": 0, + "label": "Healthcare Practitioner", + "max_length": 0, + "max_value": 0, + "options": "Healthcare Practitioner", + "read_only": 0, + "reqd": 1 + }, + { + "fieldname": "appointment_date", + "fieldtype": "Date", + "hidden": 0, + "label": "Date", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1 + }, + { + "fieldname": "appointment_time", + "fieldtype": "Data", + "hidden": 0, + "label": "Time", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0 + }, + { + "fieldname": "department", + "fieldtype": "Link", + "hidden": 0, + "label": "Department", + "max_length": 0, + "max_value": 0, + "options": "Medical Department", + "read_only": 0, + "reqd": 0 + }, + { + "fieldname": "appointment_type", + "fieldtype": "Link", + "hidden": 0, + "label": "Type", + "max_length": 0, + "max_value": 0, + "options": "Appointment Type", + "read_only": 0, + "reqd": 0 + }, + { + "default": "Scheduled", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "label": "Status", + "max_length": 0, + "max_value": 0, + "options": "\nScheduled\nOpen\nClosed\nPending\nCancelled", + "read_only": 1, + "reqd": 0 + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/web_form/patient_appointments/patient_appointments.py b/healthcare/healthcare/web_form/patient_appointments/patient_appointments.py new file mode 100644 index 0000000..89e8eb1 --- /dev/null +++ b/healthcare/healthcare/web_form/patient_appointments/patient_appointments.py @@ -0,0 +1,34 @@ +import frappe + + +def get_context(context): + context.read_only = 1 + + +def get_list_context(context): + context.row_template = "erpnext/templates/includes/healthcare/appointment_row_template.html" + context.get_list = get_appointment_list + + +def get_appointment_list( + doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified desc" +): + patient = get_patient() + lab_tests = frappe.db.sql( + """select * from `tabPatient Appointment` + where patient = %s and (status = 'Open' or status = 'Scheduled') order by appointment_date""", + patient, + as_dict=True, + ) + return lab_tests + + +def get_patient(): + return frappe.get_value("Patient", {"email": frappe.session.user}, "name") + + +def has_website_permission(doc, ptype, user, verbose=False): + if doc.patient == get_patient(): + return True + else: + return False diff --git a/healthcare/healthcare/web_form/patient_registration/__init__.py b/healthcare/healthcare/web_form/patient_registration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/web_form/patient_registration/patient_registration.js b/healthcare/healthcare/web_form/patient_registration/patient_registration.js new file mode 100644 index 0000000..f09e540 --- /dev/null +++ b/healthcare/healthcare/web_form/patient_registration/patient_registration.js @@ -0,0 +1,3 @@ +frappe.ready(function() { + // bind events here +}); diff --git a/healthcare/healthcare/web_form/patient_registration/patient_registration.json b/healthcare/healthcare/web_form/patient_registration/patient_registration.json new file mode 100644 index 0000000..9ed92de --- /dev/null +++ b/healthcare/healthcare/web_form/patient_registration/patient_registration.json @@ -0,0 +1,397 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 0, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "button_label": "Register", + "creation": "2020-03-03 01:01:16.250607", + "currency": "INR", + "doc_type": "Patient", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "introduction_text": "", + "is_standard": 1, + "login_required": 0, + "max_attachment_size": 0, + "modified": "2020-03-26 17:25:15.361918", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "patient-registration", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "patient-registration", + "route_to_success_link": 0, + "show_attachments": 0, + "show_in_grid": 0, + "show_sidebar": 1, + "sidebar_items": [], + "success_message": "Registration Successfully. Thank You!", + "success_url": "/patient-registration", + "title": "Patient Registration", + "web_form_fields": [ + { + "allow_read_on_all_link_options": 0, + "fieldname": "basic_info", + "fieldtype": "Section Break", + "hidden": 0, + "label": "Patient Demographics", + "max_length": 0, + "max_value": 0, + "options": "fa fa-user", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "first_name", + "fieldtype": "Data", + "hidden": 0, + "label": "First Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "middle_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Middle Name (optional)", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "last_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Last Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "sex", + "fieldtype": "Link", + "hidden": 0, + "label": "Gender", + "max_length": 0, + "max_value": 0, + "options": "Gender", + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "blood_group", + "fieldtype": "Select", + "hidden": 0, + "label": "Blood Group", + "max_length": 0, + "max_value": 0, + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "dob", + "fieldtype": "Date", + "hidden": 0, + "label": "Date of birth", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "mobile", + "fieldtype": "Data", + "hidden": 0, + "label": "Mobile", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "email", + "fieldtype": "Data", + "hidden": 0, + "label": "Email", + "max_length": 0, + "max_value": 0, + "options": "Email", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "phone", + "fieldtype": "Data", + "hidden": 0, + "label": "Phone", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "", + "fieldtype": "Section Break", + "hidden": 0, + "label": "Personal Details", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "occupation", + "fieldtype": "Data", + "hidden": 0, + "label": "Occupation", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "marital_status", + "fieldtype": "Select", + "hidden": 0, + "label": "Marital Status", + "max_length": 0, + "max_value": 0, + "options": "\nSingle\nMarried\nDivorced\nWidow", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "allergy_medical_and_surgical_history", + "fieldtype": "Section Break", + "hidden": 0, + "label": "Allergies, Medical and Surgical History", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "allergies", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Allergies", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "medication", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Medication", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "column_break_20", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "medical_history", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Medical History", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "surgical_history", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Surgical History", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "risk_factors", + "fieldtype": "Section Break", + "hidden": 0, + "label": "Risk Factors", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "default": "0", + "fieldname": "tobacco_past_use", + "fieldtype": "Check", + "hidden": 0, + "label": "Check if you have a history of Tobacco Consumption", + "max_length": 0, + "max_value": 0, + "options": "", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "default": "0", + "fieldname": "tobacco_current_use", + "fieldtype": "Check", + "hidden": 0, + "label": "Check if you consume Tobacco", + "max_length": 0, + "max_value": 0, + "options": "", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "default": "0", + "fieldname": "alcohol_past_use", + "fieldtype": "Check", + "hidden": 0, + "label": "Check if you have a history of Alcohol Consumption", + "max_length": 0, + "max_value": 0, + "options": "", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "default": "0", + "fieldname": "alcohol_current_use", + "fieldtype": "Check", + "hidden": 0, + "label": "Check if you consume Alcohol", + "max_length": 0, + "max_value": 0, + "options": "", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "column_break_32", + "fieldtype": "Column Break", + "hidden": 0, + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "surrounding_factors", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Occupational Hazards and Environmental Factors", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "allow_read_on_all_link_options": 0, + "fieldname": "other_risk_factors", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Other Risk Factors", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/web_form/patient_registration/patient_registration.py b/healthcare/healthcare/web_form/patient_registration/patient_registration.py new file mode 100644 index 0000000..02e3e93 --- /dev/null +++ b/healthcare/healthcare/web_form/patient_registration/patient_registration.py @@ -0,0 +1,3 @@ +def get_context(context): + # do your magic here + pass diff --git a/healthcare/healthcare/web_form/personal_details/__init__.py b/healthcare/healthcare/web_form/personal_details/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/web_form/personal_details/personal_details.js b/healthcare/healthcare/web_form/personal_details/personal_details.js new file mode 100644 index 0000000..f09e540 --- /dev/null +++ b/healthcare/healthcare/web_form/personal_details/personal_details.js @@ -0,0 +1,3 @@ +frappe.ready(function() { + // bind events here +}); diff --git a/healthcare/healthcare/web_form/personal_details/personal_details.json b/healthcare/healthcare/web_form/personal_details/personal_details.json new file mode 100644 index 0000000..aad987a --- /dev/null +++ b/healthcare/healthcare/web_form/personal_details/personal_details.json @@ -0,0 +1,87 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 0, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "creation": "2018-07-03 19:33:23.332661", + "currency": "INR", + "doc_type": "Patient", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "introduction_text": "", + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2018-07-04 17:22:28.936442", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "personal-details", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "published": 1, + "route": "personal-details", + "show_sidebar": 1, + "sidebar_items": [], + "success_url": "/personal-details", + "title": "Personal Details", + "web_form_fields": [ + { + "fieldname": "patient_name", + "fieldtype": "Data", + "hidden": 0, + "label": "Full Name", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1 + }, + { + "fieldname": "sex", + "fieldtype": "Select", + "hidden": 0, + "label": "Gender", + "max_length": 0, + "max_value": 0, + "options": "\nMale\nFemale\nOther", + "read_only": 0, + "reqd": 1 + }, + { + "fieldname": "dob", + "fieldtype": "Date", + "hidden": 0, + "label": "Date of birth", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1 + }, + { + "fieldname": "mobile", + "fieldtype": "Data", + "hidden": 0, + "label": "Mobile", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0 + }, + { + "fieldname": "email", + "fieldtype": "Data", + "hidden": 0, + "label": "Email", + "max_length": 0, + "max_value": 0, + "options": "Email", + "read_only": 1, + "reqd": 0 + } + ] +} diff --git a/healthcare/healthcare/web_form/personal_details/personal_details.py b/healthcare/healthcare/web_form/personal_details/personal_details.py new file mode 100644 index 0000000..dfbd73e --- /dev/null +++ b/healthcare/healthcare/web_form/personal_details/personal_details.py @@ -0,0 +1,28 @@ +import frappe +from frappe import _ + +no_cache = 1 + + +def get_context(context): + if frappe.session.user == "Guest": + frappe.throw(_("You need to be logged in to access this page"), frappe.PermissionError) + + context.show_sidebar = True + + if frappe.db.exists("Patient", {"email": frappe.session.user}): + patient = frappe.get_doc("Patient", {"email": frappe.session.user}) + context.doc = patient + frappe.form_dict.new = 0 + frappe.form_dict.name = patient.name + + +def get_patient(): + return frappe.get_value("Patient", {"email": frappe.session.user}, "name") + + +def has_website_permission(doc, ptype, user, verbose=False): + if doc.name == get_patient(): + return True + else: + return False diff --git a/healthcare/healthcare/web_form/prescription/__init__.py b/healthcare/healthcare/web_form/prescription/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/healthcare/web_form/prescription/prescription.js b/healthcare/healthcare/web_form/prescription/prescription.js new file mode 100644 index 0000000..f09e540 --- /dev/null +++ b/healthcare/healthcare/web_form/prescription/prescription.js @@ -0,0 +1,3 @@ +frappe.ready(function() { + // bind events here +}); diff --git a/healthcare/healthcare/web_form/prescription/prescription.json b/healthcare/healthcare/web_form/prescription/prescription.json new file mode 100644 index 0000000..8e19e32 --- /dev/null +++ b/healthcare/healthcare/web_form/prescription/prescription.json @@ -0,0 +1,120 @@ +{ + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_incomplete": 0, + "allow_multiple": 1, + "allow_print": 1, + "amount": 0.0, + "amount_based_on_field": 0, + "creation": "2017-06-06 17:13:19.101374", + "currency": "INR", + "doc_type": "Patient Encounter", + "docstatus": 0, + "doctype": "Web Form", + "idx": 0, + "introduction_text": "Patient Prescriptions", + "is_standard": 1, + "login_required": 1, + "max_attachment_size": 0, + "modified": "2018-09-04 11:53:40.954517", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "prescription", + "owner": "Administrator", + "payment_button_label": "Buy Now", + "print_format": "Encounter Print", + "published": 1, + "route": "prescription", + "show_in_grid": 0, + "show_sidebar": 1, + "sidebar_items": [], + "success_url": "/prescription", + "title": "Prescription", + "web_form_fields": [ + { + "fieldname": "practitioner", + "fieldtype": "Link", + "hidden": 0, + "label": "Healthcare Practitioner", + "max_length": 0, + "max_value": 0, + "options": "Healthcare Practitioner", + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "fieldname": "visit_department", + "fieldtype": "Link", + "hidden": 0, + "label": "Department", + "max_length": 0, + "max_value": 0, + "options": "Medical Department", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "default": "Today", + "fieldname": "encounter_date", + "fieldtype": "Date", + "hidden": 0, + "label": "Encounter Date", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "default": "", + "fieldname": "encounter_time", + "fieldtype": "Data", + "hidden": 0, + "label": "Encounter Time", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 1, + "show_in_filter": 0 + }, + { + "fieldname": "drug_prescription", + "fieldtype": "Table", + "hidden": 0, + "label": "Drug Prescription", + "max_length": 0, + "max_value": 0, + "options": "Drug Prescription", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "fieldname": "lab_test_prescription", + "fieldtype": "Table", + "hidden": 0, + "label": "Investigations", + "max_length": 0, + "max_value": 0, + "options": "Lab Prescription", + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + }, + { + "fieldname": "encounter_comment", + "fieldtype": "Small Text", + "hidden": 0, + "label": "Review Details", + "max_length": 0, + "max_value": 0, + "read_only": 0, + "reqd": 0, + "show_in_filter": 0 + } + ] +} \ No newline at end of file diff --git a/healthcare/healthcare/web_form/prescription/prescription.py b/healthcare/healthcare/web_form/prescription/prescription.py new file mode 100644 index 0000000..44caeb0 --- /dev/null +++ b/healthcare/healthcare/web_form/prescription/prescription.py @@ -0,0 +1,34 @@ +import frappe + + +def get_context(context): + context.read_only = 1 + + +def get_list_context(context): + context.row_template = "erpnext/templates/includes/healthcare/prescription_row_template.html" + context.get_list = get_encounter_list + + +def get_encounter_list( + doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified desc" +): + patient = get_patient() + encounters = frappe.db.sql( + """select * from `tabPatient Encounter` + where patient = %s order by creation desc""", + patient, + as_dict=True, + ) + return encounters + + +def get_patient(): + return frappe.get_value("Patient", {"email": frappe.session.user}, "name") + + +def has_website_permission(doc, ptype, user, verbose=False): + if doc.patient == get_patient(): + return True + else: + return False diff --git a/healthcare/healthcare/workspace/healthcare/healthcare.json b/healthcare/healthcare/workspace/healthcare/healthcare.json new file mode 100644 index 0000000..83cc13e --- /dev/null +++ b/healthcare/healthcare/workspace/healthcare/healthcare.json @@ -0,0 +1,714 @@ +{ + "charts": [ + { + "chart_name": "Patient Appointments", + "label": "Patient Appointments" + } + ], + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Healthcare\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Patient Appointments\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient Appointment\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Healthcare Service Unit\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Healthcare Practitioner\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient History\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Consultation Setup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Consultation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Facility Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Inpatient\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Laboratory Setup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Laboratory\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Rehabilitation and Physiotherapy\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Nursing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Records and History\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", + "creation": "2020-03-02 17:23:17.919682", + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "healthcare", + "idx": 0, + "label": "Healthcare", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Masters", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient", + "link_count": 0, + "link_to": "Patient", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Practitioner", + "link_count": 0, + "link_to": "Healthcare Practitioner", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Practitioner Schedule", + "link_count": 0, + "link_to": "Practitioner Schedule", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Medical Department", + "link_count": 0, + "link_to": "Medical Department", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Consultation Setup", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Appointment Type", + "link_count": 0, + "link_to": "Appointment Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Clinical Procedure Template", + "link_count": 0, + "link_to": "Clinical Procedure Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Prescription Dosage", + "link_count": 0, + "link_to": "Prescription Dosage", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Prescription Duration", + "link_count": 0, + "link_to": "Prescription Duration", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Antibiotic", + "link_count": 0, + "link_to": "Antibiotic", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Complaint", + "link_count": 0, + "link_to": "Complaint", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Diagnosis", + "link_count": 0, + "link_to": "Diagnosis", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Treatment Plan Template", + "link_count": 0, + "link_to": "Treatment Plan Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Consultation", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Appointment", + "link_count": 0, + "link_to": "Patient Appointment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Clinical Procedure", + "link_count": 0, + "link_to": "Clinical Procedure", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Encounter", + "link_count": 0, + "link_to": "Patient Encounter", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Vital Signs", + "link_count": 0, + "link_to": "Vital Signs", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Validity", + "link_count": 0, + "link_to": "Fee Validity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Facility Management", + "link_count": 0, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Service Unit Type", + "link_count": 0, + "link_to": "Healthcare Service Unit Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Service Unit", + "link_count": 0, + "link_to": "Healthcare Service Unit", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Medical Code Standard", + "link_count": 0, + "link_to": "Medical Code Standard", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Medical Code", + "link_count": 0, + "link_to": "Medical Code", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Settings", + "link_count": 0, + "link_to": "Healthcare Settings", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient History Settings", + "link_count": 0, + "link_to": "Patient History Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Laboratory Setup", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test Template", + "link_count": 0, + "link_to": "Lab Test Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test Sample", + "link_count": 0, + "link_to": "Lab Test Sample", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test UOM", + "link_count": 0, + "link_to": "Lab Test UOM", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sensitivity", + "link_count": 0, + "link_to": "Sensitivity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Dosage Form", + "link_count": 0, + "link_to": "Dosage Form", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Laboratory", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test", + "link_count": 0, + "link_to": "Lab Test", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sample Collection", + "link_count": 0, + "link_to": "Sample Collection", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Inpatient", + "link_count": 0, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Inpatient Medication Order", + "link_count": 0, + "link_to": "Inpatient Medication Order", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Inpatient Record", + "link_count": 0, + "link_to": "Inpatient Record", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Inpatient Medication Entry", + "link_count": 0, + "link_to": "Inpatient Medication Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Rehabilitation and Physiotherapy", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Exercise Type", + "link_count": 0, + "link_to": "Exercise Type", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Therapy Type", + "link_count": 0, + "link_to": "Therapy Type", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Therapy Plan Template", + "link_count": 0, + "link_to": "Therapy Plan Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Therapy Plan", + "link_count": 0, + "link_to": "Therapy Plan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Therapy Session", + "link_count": 0, + "link_to": "Therapy Session", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Assessment Template", + "link_count": 0, + "link_to": "Patient Assessment Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Assessment", + "link_count": 0, + "link_to": "Patient Assessment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Records and History", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient History", + "link_count": 0, + "link_to": "patient_history", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Progress", + "link_count": 0, + "link_to": "patient-progress", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Medical Record", + "link_count": 0, + "link_to": "Patient Medical Record", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Inpatient Record", + "link_count": 0, + "link_to": "Inpatient Record", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "link_count": 3, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Patient Appointment Analytics", + "link_count": 0, + "link_to": "Patient Appointment Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Lab Test Report", + "link_count": 0, + "link_to": "Lab Test Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Diagnosis Trends", + "link_count": 0, + "link_to": "Diagnosis Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Nursing", + "link_count": 3, + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Nursing Checklist Template", + "link_count": 0, + "link_to": "Nursing Checklist Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Nursing Task", + "link_count": 0, + "link_to": "Nursing Task", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Activity", + "link_count": 0, + "link_to": "Healthcare Activity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2022-07-25 20:32:22.664308", + "modified_by": "rucha@frappe.io", + "module": "Healthcare", + "name": "Healthcare", + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "restrict_to_domain": "Healthcare", + "roles": [], + "sequence_id": 13.0, + "shortcuts": [ + { + "color": "Orange", + "format": "{} Open", + "label": "Patient Appointment", + "link_to": "Patient Appointment", + "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}", + "type": "DocType" + }, + { + "color": "Orange", + "format": "{} Active", + "label": "Patient", + "link_to": "Patient", + "stats_filter": "{\n \"status\": \"Active\"\n}", + "type": "DocType" + }, + { + "color": "Green", + "format": "{} Vacant", + "label": "Healthcare Service Unit", + "link_to": "Healthcare Service Unit", + "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}", + "type": "DocType" + }, + { + "label": "Healthcare Practitioner", + "link_to": "Healthcare Practitioner", + "type": "DocType" + }, + { + "label": "Patient History", + "link_to": "patient_history", + "type": "Page" + }, + { + "label": "Dashboard", + "link_to": "Healthcare", + "type": "Dashboard" + } + ], + "title": "Healthcare" +} \ No newline at end of file diff --git a/healthcare/hooks.py b/healthcare/hooks.py new file mode 100644 index 0000000..1d238df --- /dev/null +++ b/healthcare/hooks.py @@ -0,0 +1,282 @@ +from . import __version__ as app_version +from frappe import _ + + +app_name = "healthcare" +app_title = "Healthcare" +app_publisher = "healthcare" +app_description = "healthcare" +app_icon = "octicon octicon-file-directory" +app_color = "grey" +app_email = "contact@frappe.io" +app_license = "MIT" +required_apps = ["erpnext"] + +# Includes in +# ------------------ + +# include js, css files in header of desk.html +# app_include_css = "/assets/healthcare/css/healthcare.css" +# app_include_js = "/assets/healthcare/js/healthcare.js" + +# include js, css files in header of web template +# web_include_css = "/assets/healthcare/css/healthcare.css" +# web_include_js = "/assets/healthcare/js/healthcare.js" + +# include custom scss in every website theme (without file extension ".scss") +# website_theme_scss = "healthcare/public/scss/website" + +# include js, css files in header of web form +# webform_include_js = {"doctype": "public/js/doctype.js"} +# webform_include_css = {"doctype": "public/css/doctype.css"} + +# include js in page +# page_js = {"page" : "public/js/file.js"} + +# include js in doctype views +doctype_js = {"Sales Invoice": "public/js/sales_invoice.js"} +# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} +# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} +# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} + +# Home Pages +# ---------- + +# application home page (will override Website Settings) +# home_page = "login" + +# website user home page (by Role) +# role_home_page = { +# "Role": "home_page" +# } + +# Generators +# ---------- + +# automatically create page for each record of this doctype +# website_generators = ["Web Page"] + +# Jinja +# ---------- + +# add methods and filters to jinja environment +# jinja = { +# "methods": "healthcare.utils.jinja_methods", +# "filters": "healthcare.utils.jinja_filters" +# } + +# Installation +# ------------ + +# before_install = "healthcare.install.before_install" +after_install = "healthcare.setup.setup_healthcare" + +# Desk Notifications +# ------------------ +# See frappe.core.notifications.get_notification_config + +# notification_config = "healthcare.notifications.get_notification_config" + +# Permissions +# ----------- +# Permissions evaluated in scripted ways + +# permission_query_conditions = { +# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", +# } +# +# has_permission = { +# "Event": "frappe.desk.doctype.event.event.has_permission", +# } + +# DocType Class +# --------------- +# Override standard doctype classes + +override_doctype_class = { + "Sales Invoice": "healthcare.healthcare.custom_doctype.sales_invoice.HealthcareSalesInvoice", +} + +# Document Events +# --------------- +# Hook on document methods and events + +doc_events = { + "*": { + "on_submit": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", + "on_cancel": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record", + "on_update_after_submit": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", + }, + "Sales Invoice": { + "on_submit": "healthcare.healthcare.utils.manage_invoice_submit_cancel", + "on_cancel": "healthcare.healthcare.utils.manage_invoice_submit_cancel", + }, + "Company": { + "after_insert": "healthcare.healthcare.utils.create_healthcare_service_unit_tree_root" + }, +} + +scheduler_events = { + "all": [ + "healthcare.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder", + ], + "daily": [ + "healthcare.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status", + ], +} + +# Scheduled Tasks +# --------------- + +# scheduler_events = { +# "all": [ +# "healthcare.tasks.all" +# ], +# "daily": [ +# "healthcare.tasks.daily" +# ], +# "hourly": [ +# "healthcare.tasks.hourly" +# ], +# "weekly": [ +# "healthcare.tasks.weekly" +# ], +# "monthly": [ +# "healthcare.tasks.monthly" +# ], +# } + +# Testing +# ------- + +before_tests = "healthcare.healthcare.utils.before_tests" + +# Overriding Methods +# ------------------------------ +# +# override_whitelisted_methods = { +# "frappe.desk.doctype.event.event.get_events": "healthcare.event.get_events" +# } +# +# each overriding function accepts a `data` argument; +# generated from the base implementation of the doctype dashboard, +# along with any modifications made in other InfluxERP apps +# override_doctype_dashboards = { +# "Task": "healthcare.task.get_dashboard_data" +# } + +# exempt linked doctypes from being automatically cancelled +# +auto_cancel_exempted_doctypes = [ + "Inpatient Medication Entry", +] + +# User Data Protection +# -------------------- + +# user_data_fields = [ +# { +# "doctype": "{doctype_1}", +# "filter_by": "{filter_by}", +# "redact_fields": ["{field_1}", "{field_2}"], +# "partial": 1, +# }, +# { +# "doctype": "{doctype_2}", +# "filter_by": "{filter_by}", +# "partial": 1, +# }, +# { +# "doctype": "{doctype_3}", +# "strict": False, +# }, +# { +# "doctype": "{doctype_4}" +# } +# ] + +# Authentication and authorization +# -------------------------------- + +# auth_hooks = [ +# "healthcare.auth.validate" +# ] + +global_search_doctypes = { + "Healthcare": [ + {"doctype": "Patient", "index": 1}, + {"doctype": "Medical Department", "index": 2}, + {"doctype": "Vital Signs", "index": 3}, + {"doctype": "Healthcare Practitioner", "index": 4}, + {"doctype": "Patient Appointment", "index": 5}, + {"doctype": "Healthcare Service Unit", "index": 6}, + {"doctype": "Patient Encounter", "index": 7}, + {"doctype": "Antibiotic", "index": 8}, + {"doctype": "Diagnosis", "index": 9}, + {"doctype": "Lab Test", "index": 10}, + {"doctype": "Clinical Procedure", "index": 11}, + {"doctype": "Inpatient Record", "index": 12}, + {"doctype": "Sample Collection", "index": 13}, + {"doctype": "Patient Medical Record", "index": 14}, + {"doctype": "Appointment Type", "index": 15}, + {"doctype": "Fee Validity", "index": 16}, + {"doctype": "Practitioner Schedule", "index": 17}, + {"doctype": "Dosage Form", "index": 18}, + {"doctype": "Lab Test Sample", "index": 19}, + {"doctype": "Prescription Duration", "index": 20}, + {"doctype": "Prescription Dosage", "index": 21}, + {"doctype": "Sensitivity", "index": 22}, + {"doctype": "Complaint", "index": 23}, + {"doctype": "Medical Code", "index": 24}, + ] +} + +domains = { + "Healthcare": "healthcare.setup", +} + +standard_portal_menu_items = [ + { + "title": _("Personal Details"), + "route": "/personal-details", + "reference_doctype": "Patient", + "role": "Patient", + }, + { + "title": _("Timesheets"), + "route": "/timesheets", + "reference_doctype": "Timesheet", + "role": "Customer", + }, + { + "title": _("Lab Test"), + "route": "/lab-test", + "reference_doctype": "Lab Test", + "role": "Patient", + }, + { + "title": _("Prescription"), + "route": "/prescription", + "reference_doctype": "Patient Encounter", + "role": "Patient", + }, + { + "title": _("Patient Appointment"), + "route": "/patient-appointments", + "reference_doctype": "Patient Appointment", + "role": "Patient", + }, +] + +has_website_permission = { + "Issue": "erpnext.support.doctype.issue.issue.has_website_permission", + "Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission", + "Lab Test": "healthcare.healthcare.web_form.lab_test.lab_test.has_website_permission", + "Patient Encounter": "healthcare.healthcare.web_form.prescription.prescription.has_website_permission", + "Patient Appointment": "healthcare.healthcare.web_form.patient_appointments.patient_appointments.has_website_permission", + "Patient": "healthcare.healthcare.web_form.personal_details.personal_details.has_website_permission", +} + +standard_queries = { + "Healthcare Practitioner": "healthcare.healthcare.doctype.healthcare_practitioner.healthcare_practitioner.get_practitioner_list" +} diff --git a/healthcare/modules.txt b/healthcare/modules.txt new file mode 100644 index 0000000..915d8d2 --- /dev/null +++ b/healthcare/modules.txt @@ -0,0 +1 @@ +Healthcare \ No newline at end of file diff --git a/healthcare/patches.txt b/healthcare/patches.txt new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/public/js/sales_invoice.js b/healthcare/public/js/sales_invoice.js new file mode 100644 index 0000000..cb03cc9 --- /dev/null +++ b/healthcare/public/js/sales_invoice.js @@ -0,0 +1,307 @@ +// Healthcare +frappe.ui.form.on('Sales Invoice', { + refresh(frm) { + if (frm.doc.docstatus === 0 && !frm.doc.is_return) { + frm.add_custom_button(__('Healthcare Services'), function() { + get_healthcare_services_to_invoice(frm); + },__('Get Items From')); + frm.add_custom_button(__('Prescriptions'), function() { + get_drugs_to_invoice(frm); + },__('Get Items From')); + } + }, + + patient(frm) { + if (frm.doc.patient) { + frappe.db.get_value("Patient", frm.doc.patient, "customer") + .then(r => { + if (!r.exc && r.message.customer) { + frm.set_value("customer", r.message.customer); + } else { + frappe.show_alert({ + indicator: "warning", + message: __("Patient {0} is not linked to a Customer", + [`${frm.doc.patient}`] + ), + }); + frm.set_value("customer", ""); + } + frm.set_df_property("customer", "read_only", frm.doc.customer ? 1 : 0); + }) + } else { + frm.set_value("customer", ""); + frm.set_df_property("customer", "read_only", 0); + } + } +}); + +var get_healthcare_services_to_invoice = function(frm) { + var me = this; + let selected_patient = ''; + var dialog = new frappe.ui.Dialog({ + title: __("Get Items from Healthcare Services"), + fields:[ + { + fieldtype: 'Link', + options: 'Patient', + label: 'Patient', + fieldname: "patient", + reqd: true + }, + { fieldtype: 'Section Break' }, + { fieldtype: 'HTML', fieldname: 'results_area' } + ] + }); + var $wrapper; + var $results; + var $placeholder; + dialog.set_values({ + 'patient': frm.doc.patient + }); + dialog.fields_dict["patient"].df.onchange = () => { + var patient = dialog.fields_dict.patient.input.value; + if(patient && patient!=selected_patient){ + selected_patient = patient; + var method = "healthcare.healthcare.utils.get_healthcare_services_to_invoice"; + var args = {patient: patient, company: frm.doc.company}; + var columns = (["service", "reference_name", "reference_type"]); + get_healthcare_items(frm, true, $results, $placeholder, method, args, columns); + } + else if(!patient){ + selected_patient = ''; + $results.empty(); + $results.append($placeholder); + } + } + $wrapper = dialog.fields_dict.results_area.$wrapper.append(`
`); + $results = $wrapper.find('.results'); + $placeholder = $(`
+ + +

No billable Healthcare Services found

+
+
`); + $results.on('click', '.list-item--head :checkbox', (e) => { + $results.find('.list-item-container .list-row-check') + .prop("checked", ($(e.target).is(':checked'))); + }); + set_primary_action(frm, dialog, $results, true); + dialog.show(); +}; + +var get_healthcare_items = function(frm, invoice_healthcare_services, $results, $placeholder, method, args, columns) { + var me = this; + $results.empty(); + frappe.call({ + method: method, + args: args, + callback: function(data) { + if(data.message){ + $results.append(make_list_row(columns, invoice_healthcare_services)); + for(let i=0; i + ${ + head ? `${__(frappe.model.unscrub(column))}` + + :(column !== "name" ? `${__(result[column])}` + : ` + ${__(result[column])}`) + } +
`; + }) + + let $row = $(`
+
+ +
+ ${contents} +
`); + + $row = list_row_data_items(head, $row, result, invoice_healthcare_services); + return $row; +}; + +var set_primary_action= function(frm, dialog, $results, invoice_healthcare_services) { + var me = this; + dialog.set_primary_action(__('Add'), function() { + let checked_values = get_checked_values($results); + if(checked_values.length > 0){ + if(invoice_healthcare_services) { + frm.set_value("patient", dialog.fields_dict.patient.input.value); + } + add_to_item_line(frm, checked_values, invoice_healthcare_services); + dialog.hide(); + } + else{ + if(invoice_healthcare_services){ + frappe.msgprint(__("Please select Healthcare Service")); + } + else{ + frappe.msgprint(__("Please select Drug")); + } + } + }); +}; + +var get_checked_values= function($results) { + return $results.find('.list-item-container').map(function() { + let checked_values = {}; + if ($(this).find('.list-row-check:checkbox:checked').length > 0 ) { + checked_values['dn'] = $(this).attr('data-dn'); + checked_values['dt'] = $(this).attr('data-dt'); + checked_values['item'] = $(this).attr('data-item'); + if($(this).attr('data-rate') != 'undefined'){ + checked_values['rate'] = $(this).attr('data-rate'); + } + else{ + checked_values['rate'] = false; + } + if($(this).attr('data-income-account') != 'undefined'){ + checked_values['income_account'] = $(this).attr('data-income-account'); + } + else{ + checked_values['income_account'] = false; + } + if($(this).attr('data-qty') != 'undefined'){ + checked_values['qty'] = $(this).attr('data-qty'); + } + else{ + checked_values['qty'] = false; + } + if($(this).attr('data-description') != 'undefined'){ + checked_values['description'] = $(this).attr('data-description'); + } + else{ + checked_values['description'] = false; + } + return checked_values; + } + }).get(); +}; + +var get_drugs_to_invoice = function(frm) { + var me = this; + let selected_encounter = ''; + var dialog = new frappe.ui.Dialog({ + title: __("Get Items from Prescriptions"), + fields:[ + { fieldtype: 'Link', options: 'Patient', label: 'Patient', fieldname: "patient", reqd: true }, + { fieldtype: 'Link', options: 'Patient Encounter', label: 'Patient Encounter', fieldname: "encounter", reqd: true, + description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', + get_query: function(doc) { + return { + filters: { + patient: dialog.get_value("patient"), + company: frm.doc.company, + docstatus: 1 + } + }; + } + }, + { fieldtype: 'Section Break' }, + { fieldtype: 'HTML', fieldname: 'results_area' } + ] + }); + var $wrapper; + var $results; + var $placeholder; + dialog.set_values({ + 'patient': frm.doc.patient, + 'encounter': "" + }); + dialog.fields_dict["encounter"].df.onchange = () => { + var encounter = dialog.fields_dict.encounter.input.value; + if(encounter && encounter!=selected_encounter){ + selected_encounter = encounter; + var method = "healthcare.healthcare.utils.get_drugs_to_invoice"; + var args = {encounter: encounter}; + var columns = (["drug_code", "quantity", "description"]); + get_healthcare_items(frm, false, $results, $placeholder, method, args, columns); + } + else if(!encounter){ + selected_encounter = ''; + $results.empty(); + $results.append($placeholder); + } + } + $wrapper = dialog.fields_dict.results_area.$wrapper.append(`
`); + $results = $wrapper.find('.results'); + $placeholder = $(`
+ + +

No Drug Prescription found

+
+
`); + $results.on('click', '.list-item--head :checkbox', (e) => { + $results.find('.list-item-container .list-row-check') + .prop("checked", ($(e.target).is(':checked'))); + }); + set_primary_action(frm, dialog, $results, false); + dialog.show(); +}; + +var list_row_data_items = function(head, $row, result, invoice_healthcare_services) { + if(invoice_healthcare_services){ + head ? $row.addClass('list-item--head') + : $row = $(`
+
`).append($row); + } + else{ + head ? $row.addClass('list-item--head') + : $row = $(`
+
`).append($row); + } + return $row +}; + +var add_to_item_line = function(frm, checked_values, invoice_healthcare_services){ + if(invoice_healthcare_services){ + frappe.call({ + doc: frm.doc, + method: "set_healthcare_services", + args:{ + checked_values: checked_values + }, + callback: function() { + frm.trigger("validate"); + frm.refresh_fields(); + } + }); + } + else{ + for(let i=0; i 1){ + frappe.model.set_value(si_item.doctype, si_item.name, 'qty', parseFloat(checked_values[i]['qty'])); + } + } + frm.refresh_fields(); + } +}; diff --git a/healthcare/setup.py b/healthcare/setup.py new file mode 100644 index 0000000..46eba2b --- /dev/null +++ b/healthcare/setup.py @@ -0,0 +1,766 @@ +import frappe +from frappe import _ +from erpnext.setup.utils import insert_record + + +data = { + "desktop_icons": [ + "Patient", + "Patient Appointment", + "Patient Encounter", + "Lab Test", + "Healthcare", + "Vital Signs", + "Clinical Procedure", + "Inpatient Record", + "Accounts", + "Buying", + "Stock", + "HR", + "ToDo", + ], + "default_portal_role": "Patient", + "restricted_roles": [ + "Healthcare Administrator", + "LabTest Approver", + "Laboratory User", + "Nursing User", + "Physician", + "Patient", + ], + "custom_fields": { + "Sales Invoice": [ + { + "fieldname": "patient", + "label": "Patient", + "fieldtype": "Link", + "options": "Patient", + "insert_after": "naming_series", + }, + { + "fieldname": "patient_name", + "label": "Patient Name", + "fieldtype": "Data", + "fetch_from": "patient.patient_name", + "insert_after": "patient", + "read_only": True, + }, + { + "fieldname": "ref_practitioner", + "label": "Referring Practitioner", + "fieldtype": "Link", + "options": "Healthcare Practitioner", + "insert_after": "customer", + }, + ], + "Sales Invoice Item": [ + { + "fieldname": "reference_dt", + "label": "Reference DocType", + "fieldtype": "Link", + "options": "DocType", + "insert_after": "edit_references", + }, + { + "fieldname": "reference_dn", + "label": "Reference Name", + "fieldtype": "Dynamic Link", + "options": "reference_dt", + "insert_after": "reference_dt", + }, + ], + "Stock Entry": [ + { + "fieldname": "inpatient_medication_entry", + "label": "Inpatient Medication Entry", + "fieldtype": "Link", + "options": "Inpatient Medication Entry", + "insert_after": "credit_note", + "read_only": True, + } + ], + "Stock Entry Detail": [ + { + "fieldname": "patient", + "label": "Patient", + "fieldtype": "Link", + "options": "Patient", + "insert_after": "po_detail", + "read_only": True, + }, + { + "fieldname": "inpatient_medication_entry_child", + "label": "Inpatient Medication Entry Child", + "fieldtype": "Data", + "insert_after": "patient", + "read_only": True, + }, + ], + }, + "on_setup": "healthcare.setup.setup_healthcare", +} + + +def setup_healthcare(): + if frappe.db.exists("Medical Department", "Cardiology"): + # already setup + return + create_custom_records() + create_default_root_service_units() + + +def create_default_root_service_units(): + from healthcare.healthcare.utils import create_healthcare_service_unit_tree_root + + companies = frappe.get_all("Company") + for company in companies: + create_healthcare_service_unit_tree_root(company) + + +def create_custom_records(): + create_medical_departments() + create_antibiotics() + create_lab_test_uom() + create_duration() + create_dosage() + create_dosage_form() + create_healthcare_item_groups() + create_sensitivity() + setup_patient_history_settings() + + has_domain = frappe.get_doc( + { + "doctype": "Has Domain", + "parent": "Domain Settings", + "parentfield": "active_domains", + "parenttype": "Domain Settings", + "domain": "Healthcare", + } + ) + has_domain.save() + + domain = frappe.get_doc("Domain", "Healthcare") + domain.setup_domain() + + domain_settings = frappe.get_single("Domain Settings") + domain_settings.append("active_domains", dict(domain=domain)) + frappe.clear_cache() + + +def create_medical_departments(): + departments = [ + "Accident And Emergency Care", + "Anaesthetics", + "Biochemistry", + "Cardiology", + "Diabetology", + "Dermatology", + "Diagnostic Imaging", + "ENT", + "Gastroenterology", + "General Surgery", + "Gynaecology", + "Haematology", + "Maternity", + "Microbiology", + "Nephrology", + "Neurology", + "Oncology", + "Orthopaedics", + "Pathology", + "Physiotherapy", + "Rheumatology", + "Serology", + "Urology", + ] + for department in departments: + mediacal_department = frappe.new_doc("Medical Department") + mediacal_department.department = _(department) + try: + mediacal_department.save() + except frappe.DuplicateEntryError: + pass + + +def create_antibiotics(): + abt = [ + "Amoxicillin", + "Ampicillin", + "Bacampicillin", + "Carbenicillin", + "Cloxacillin", + "Dicloxacillin", + "Flucloxacillin", + "Mezlocillin", + "Nafcillin", + "Oxacillin", + "Penicillin G", + "Penicillin V", + "Piperacillin", + "Pivampicillin", + "Pivmecillinam", + "Ticarcillin", + "Cefacetrile (cephacetrile)", + "Cefadroxil (cefadroxyl)", + "Cefalexin (cephalexin)", + "Cefaloglycin (cephaloglycin)", + "Cefalonium (cephalonium)", + "Cefaloridine (cephaloradine)", + "Cefalotin (cephalothin)", + "Cefapirin (cephapirin)", + "Cefatrizine", + "Cefazaflur", + "Cefazedone", + "Cefazolin (cephazolin)", + "Cefradine (cephradine)", + "Cefroxadine", + "Ceftezole", + "Cefaclor", + "Cefamandole", + "Cefmetazole", + "Cefonicid", + "Cefotetan", + "Cefoxitin", + "Cefprozil (cefproxil)", + "Cefuroxime", + "Cefuzonam", + "Cefcapene", + "Cefdaloxime", + "Cefdinir", + "Cefditoren", + "Cefetamet", + "Cefixime", + "Cefmenoxime", + "Cefodizime", + "Cefotaxime", + "Cefpimizole", + "Cefpodoxime", + "Cefteram", + "Ceftibuten", + "Ceftiofur", + "Ceftiolene", + "Ceftizoxime", + "Ceftriaxone", + "Cefoperazone", + "Ceftazidime", + "Cefclidine", + "Cefepime", + "Cefluprenam", + "Cefoselis", + "Cefozopran", + "Cefpirome", + "Cefquinome", + "Ceftobiprole", + "Ceftaroline", + "Cefaclomezine", + "Cefaloram", + "Cefaparole", + "Cefcanel", + "Cefedrolor", + "Cefempidone", + "Cefetrizole", + "Cefivitril", + "Cefmatilen", + "Cefmepidium", + "Cefovecin", + "Cefoxazole", + "Cefrotil", + "Cefsumide", + "Cefuracetime", + "Ceftioxide", + "Ceftazidime/Avibactam", + "Ceftolozane/Tazobactam", + "Aztreonam", + "Imipenem", + "Imipenem/cilastatin", + "Doripenem", + "Meropenem", + "Ertapenem", + "Azithromycin", + "Erythromycin", + "Clarithromycin", + "Dirithromycin", + "Roxithromycin", + "Telithromycin", + "Clindamycin", + "Lincomycin", + "Pristinamycin", + "Quinupristin/dalfopristin", + "Amikacin", + "Gentamicin", + "Kanamycin", + "Neomycin", + "Netilmicin", + "Paromomycin", + "Streptomycin", + "Tobramycin", + "Flumequine", + "Nalidixic acid", + "Oxolinic acid", + "Piromidic acid", + "Pipemidic acid", + "Rosoxacin", + "Ciprofloxacin", + "Enoxacin", + "Lomefloxacin", + "Nadifloxacin", + "Norfloxacin", + "Ofloxacin", + "Pefloxacin", + "Rufloxacin", + "Balofloxacin", + "Gatifloxacin", + "Grepafloxacin", + "Levofloxacin", + "Moxifloxacin", + "Pazufloxacin", + "Sparfloxacin", + "Temafloxacin", + "Tosufloxacin", + "Besifloxacin", + "Clinafloxacin", + "Gemifloxacin", + "Sitafloxacin", + "Trovafloxacin", + "Prulifloxacin", + "Sulfamethizole", + "Sulfamethoxazole", + "Sulfisoxazole", + "Trimethoprim-Sulfamethoxazole", + "Demeclocycline", + "Doxycycline", + "Minocycline", + "Oxytetracycline", + "Tetracycline", + "Tigecycline", + "Chloramphenicol", + "Metronidazole", + "Tinidazole", + "Nitrofurantoin", + "Vancomycin", + "Teicoplanin", + "Telavancin", + "Linezolid", + "Cycloserine 2", + "Rifampin", + "Rifabutin", + "Rifapentine", + "Rifalazil", + "Bacitracin", + "Polymyxin B", + "Viomycin", + "Capreomycin", + ] + + for a in abt: + antibiotic = frappe.new_doc("Antibiotic") + antibiotic.antibiotic_name = a + try: + antibiotic.save() + except frappe.DuplicateEntryError: + pass + + +def create_lab_test_uom(): + records = [ + {"doctype": "Lab Test UOM", "name": "umol/L", "lab_test_uom": "umol/L", "uom_description": None}, + {"doctype": "Lab Test UOM", "name": "mg/L", "lab_test_uom": "mg/L", "uom_description": None}, + { + "doctype": "Lab Test UOM", + "name": "mg / dl", + "lab_test_uom": "mg / dl", + "uom_description": None, + }, + { + "doctype": "Lab Test UOM", + "name": "pg / ml", + "lab_test_uom": "pg / ml", + "uom_description": None, + }, + {"doctype": "Lab Test UOM", "name": "U/ml", "lab_test_uom": "U/ml", "uom_description": None}, + {"doctype": "Lab Test UOM", "name": "/HPF", "lab_test_uom": "/HPF", "uom_description": None}, + { + "doctype": "Lab Test UOM", + "name": "Million Cells / cumm", + "lab_test_uom": "Million Cells / cumm", + "uom_description": None, + }, + { + "doctype": "Lab Test UOM", + "name": "Lakhs Cells / cumm", + "lab_test_uom": "Lakhs Cells / cumm", + "uom_description": None, + }, + {"doctype": "Lab Test UOM", "name": "U / L", "lab_test_uom": "U / L", "uom_description": None}, + {"doctype": "Lab Test UOM", "name": "g / L", "lab_test_uom": "g / L", "uom_description": None}, + { + "doctype": "Lab Test UOM", + "name": "IU / ml", + "lab_test_uom": "IU / ml", + "uom_description": None, + }, + {"doctype": "Lab Test UOM", "name": "gm %", "lab_test_uom": "gm %", "uom_description": None}, + { + "doctype": "Lab Test UOM", + "name": "Microgram", + "lab_test_uom": "Microgram", + "uom_description": None, + }, + {"doctype": "Lab Test UOM", "name": "Micron", "lab_test_uom": "Micron", "uom_description": None}, + { + "doctype": "Lab Test UOM", + "name": "Cells / cumm", + "lab_test_uom": "Cells / cumm", + "uom_description": None, + }, + {"doctype": "Lab Test UOM", "name": "%", "lab_test_uom": "%", "uom_description": None}, + { + "doctype": "Lab Test UOM", + "name": "mm / dl", + "lab_test_uom": "mm / dl", + "uom_description": None, + }, + { + "doctype": "Lab Test UOM", + "name": "mm / hr", + "lab_test_uom": "mm / hr", + "uom_description": None, + }, + { + "doctype": "Lab Test UOM", + "name": "ulU / ml", + "lab_test_uom": "ulU / ml", + "uom_description": None, + }, + { + "doctype": "Lab Test UOM", + "name": "ng / ml", + "lab_test_uom": "ng / ml", + "uom_description": None, + }, + { + "doctype": "Lab Test UOM", + "name": "ng / dl", + "lab_test_uom": "ng / dl", + "uom_description": None, + }, + { + "doctype": "Lab Test UOM", + "name": "ug / dl", + "lab_test_uom": "ug / dl", + "uom_description": None, + }, + ] + + insert_record(records) + + +def create_duration(): + records = [ + {"doctype": "Prescription Duration", "name": "3 Month", "number": "3", "period": "Month"}, + {"doctype": "Prescription Duration", "name": "2 Month", "number": "2", "period": "Month"}, + {"doctype": "Prescription Duration", "name": "1 Month", "number": "1", "period": "Month"}, + {"doctype": "Prescription Duration", "name": "12 Hour", "number": "12", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "11 Hour", "number": "11", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "10 Hour", "number": "10", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "9 Hour", "number": "9", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "8 Hour", "number": "8", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "7 Hour", "number": "7", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "6 Hour", "number": "6", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "5 Hour", "number": "5", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "4 Hour", "number": "4", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "3 Hour", "number": "3", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "2 Hour", "number": "2", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "1 Hour", "number": "1", "period": "Hour"}, + {"doctype": "Prescription Duration", "name": "5 Week", "number": "5", "period": "Week"}, + {"doctype": "Prescription Duration", "name": "4 Week", "number": "4", "period": "Week"}, + {"doctype": "Prescription Duration", "name": "3 Week", "number": "3", "period": "Week"}, + {"doctype": "Prescription Duration", "name": "2 Week", "number": "2", "period": "Week"}, + {"doctype": "Prescription Duration", "name": "1 Week", "number": "1", "period": "Week"}, + {"doctype": "Prescription Duration", "name": "6 Day", "number": "6", "period": "Day"}, + {"doctype": "Prescription Duration", "name": "5 Day", "number": "5", "period": "Day"}, + {"doctype": "Prescription Duration", "name": "4 Day", "number": "4", "period": "Day"}, + {"doctype": "Prescription Duration", "name": "3 Day", "number": "3", "period": "Day"}, + {"doctype": "Prescription Duration", "name": "2 Day", "number": "2", "period": "Day"}, + {"doctype": "Prescription Duration", "name": "1 Day", "number": "1", "period": "Day"}, + ] + insert_record(records) + + +def create_dosage(): + records = [ + { + "doctype": "Prescription Dosage", + "name": "1-1-1-1", + "dosage": "1-1-1-1", + "dosage_strength": [ + {"strength": "1.0", "strength_time": "9:00:00"}, + {"strength": "1.0", "strength_time": "13:00:00"}, + {"strength": "1.0", "strength_time": "17:00:00"}, + {"strength": "1.0", "strength_time": "21:00:00"}, + ], + }, + { + "doctype": "Prescription Dosage", + "name": "0-0-1", + "dosage": "0-0-1", + "dosage_strength": [{"strength": "1.0", "strength_time": "21:00:00"}], + }, + { + "doctype": "Prescription Dosage", + "name": "1-0-0", + "dosage": "1-0-0", + "dosage_strength": [{"strength": "1.0", "strength_time": "9:00:00"}], + }, + { + "doctype": "Prescription Dosage", + "name": "0-1-0", + "dosage": "0-1-0", + "dosage_strength": [{"strength": "1.0", "strength_time": "14:00:00"}], + }, + { + "doctype": "Prescription Dosage", + "name": "1-1-1", + "dosage": "1-1-1", + "dosage_strength": [ + {"strength": "1.0", "strength_time": "9:00:00"}, + {"strength": "1.0", "strength_time": "14:00:00"}, + {"strength": "1.0", "strength_time": "21:00:00"}, + ], + }, + { + "doctype": "Prescription Dosage", + "name": "1-0-1", + "dosage": "1-0-1", + "dosage_strength": [ + {"strength": "1.0", "strength_time": "9:00:00"}, + {"strength": "1.0", "strength_time": "21:00:00"}, + ], + }, + { + "doctype": "Prescription Dosage", + "name": "Once Bedtime", + "dosage": "Once Bedtime", + "dosage_strength": [{"strength": "1.0", "strength_time": "21:00:00"}], + }, + { + "doctype": "Prescription Dosage", + "name": "5 times a day", + "dosage": "5 times a day", + "dosage_strength": [ + {"strength": "1.0", "strength_time": "5:00:00"}, + {"strength": "1.0", "strength_time": "9:00:00"}, + {"strength": "1.0", "strength_time": "13:00:00"}, + {"strength": "1.0", "strength_time": "17:00:00"}, + {"strength": "1.0", "strength_time": "21:00:00"}, + ], + }, + { + "doctype": "Prescription Dosage", + "name": "QID", + "dosage": "QID", + "dosage_strength": [ + {"strength": "1.0", "strength_time": "9:00:00"}, + {"strength": "1.0", "strength_time": "13:00:00"}, + {"strength": "1.0", "strength_time": "17:00:00"}, + {"strength": "1.0", "strength_time": "21:00:00"}, + ], + }, + { + "doctype": "Prescription Dosage", + "name": "TID", + "dosage": "TID", + "dosage_strength": [ + {"strength": "1.0", "strength_time": "9:00:00"}, + {"strength": "1.0", "strength_time": "14:00:00"}, + {"strength": "1.0", "strength_time": "21:00:00"}, + ], + }, + { + "doctype": "Prescription Dosage", + "name": "BID", + "dosage": "BID", + "dosage_strength": [ + {"strength": "1.0", "strength_time": "9:00:00"}, + {"strength": "1.0", "strength_time": "21:00:00"}, + ], + }, + { + "doctype": "Prescription Dosage", + "name": "Once Daily", + "dosage": "Once Daily", + "dosage_strength": [{"strength": "1.0", "strength_time": "9:00:00"}], + }, + ] + insert_record(records) + + +def create_dosage_form(): + records = [ + { + "doctype": "Dosage Form", + "dosage_form": "Tablet", + }, + { + "doctype": "Dosage Form", + "dosage_form": "Syrup", + }, + { + "doctype": "Dosage Form", + "dosage_form": "Injection", + }, + { + "doctype": "Dosage Form", + "dosage_form": "Capsule", + }, + { + "doctype": "Dosage Form", + "dosage_form": "Cream", + }, + ] + insert_record(records) + + +def create_healthcare_item_groups(): + item_group = { + "doctype": "Item Group", + "item_group_name": _("All Item Groups"), + "is_group": 0, + "parent_item_group": "", + } + if not frappe.db.exists(item_group["doctype"], item_group["item_group_name"]): + insert_record([item_group]) + + records = [ + { + "doctype": "Item Group", + "item_group_name": _("Laboratory"), + "is_group": 0, + "parent_item_group": _("All Item Groups"), + }, + { + "doctype": "Item Group", + "item_group_name": _("Drug"), + "is_group": 0, + "parent_item_group": _("All Item Groups"), + }, + ] + insert_record(records) + + +def create_sensitivity(): + records = [ + {"doctype": "Sensitivity", "sensitivity": _("Low Sensitivity")}, + {"doctype": "Sensitivity", "sensitivity": _("High Sensitivity")}, + {"doctype": "Sensitivity", "sensitivity": _("Moderate Sensitivity")}, + {"doctype": "Sensitivity", "sensitivity": _("Susceptible")}, + {"doctype": "Sensitivity", "sensitivity": _("Resistant")}, + {"doctype": "Sensitivity", "sensitivity": _("Intermediate")}, + ] + insert_record(records) + + +def setup_patient_history_settings(): + import json + + settings = frappe.get_single("Patient History Settings") + configuration = get_patient_history_config() + for dt, config in configuration.items(): + settings.append( + "standard_doctypes", + {"document_type": dt, "date_fieldname": config[0], "selected_fields": json.dumps(config[1])}, + ) + settings.save() + + +def get_patient_history_config(): + return { + "Patient Encounter": ( + "encounter_date", + [ + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"}, + {"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"}, + {"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"}, + {"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"}, + {"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"}, + {"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"}, + {"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"}, + ], + ), + "Clinical Procedure": ( + "start_date", + [ + {"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"}, + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"}, + {"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"}, + {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, + {"label": "Sample", "fieldname": "sample", "fieldtype": "Link"}, + ], + ), + "Lab Test": ( + "result_date", + [ + {"label": "Test Template", "fieldname": "template", "fieldtype": "Link"}, + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"}, + {"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"}, + {"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"}, + {"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"}, + { + "label": "Descriptive Test Result", + "fieldname": "descriptive_test_items", + "fieldtype": "Table", + }, + {"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"}, + { + "label": "Sensitivity Test Result", + "fieldname": "sensitivity_test_items", + "fieldtype": "Table", + }, + {"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"}, + ], + ), + "Therapy Session": ( + "start_date", + [ + {"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"}, + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"}, + {"label": "Duration", "fieldname": "duration", "fieldtype": "Int"}, + {"label": "Location", "fieldname": "location", "fieldtype": "Link"}, + {"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"}, + {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, + {"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"}, + {"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"}, + {"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"}, + ], + ), + "Vital Signs": ( + "signs_date", + [ + {"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"}, + {"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"}, + {"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"}, + {"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"}, + {"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"}, + {"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"}, + {"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"}, + {"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"}, + {"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"}, + {"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"}, + {"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"}, + ], + ), + "Inpatient Medication Order": ( + "start_date", + [ + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"}, + {"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"}, + {"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"}, + {"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"}, + ], + ), + } diff --git a/healthcare/templates/__init__.py b/healthcare/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/healthcare/templates/pages/__init__.py b/healthcare/templates/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/key-features.png b/key-features.png new file mode 100644 index 0000000000000000000000000000000000000000..801536a8c122dc17a8c066af3bfd765e8b772ceb GIT binary patch literal 738991 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⋙rA&llkZhF%2MMTxq@7$Ujx8*XVlK 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

_U5ymGjRlV`F5$qnNL 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

rW;S5x8T+;jDL`>mRqnyicghRSBO_>~K{>_UWW zn6w8_E?fY@#RS|dbZXrWw-@RA zC%v9lHb*rsFP(r&9Te8VkT#^(X;tJF}9D zX%P#Nc6Ma-RNu0>B0F|Gr@CQQ^{|#Tv#$JVq2BldW&D^%Mt1U8nu$}W26_6pT1rBb zhCFrWnn|m+j({T5Ul7ASuLd0y~zuRl`^hp?Y;0}CjD>r5&YG)hBqB8htJUvQoU&Bem?usT zRkSlgWY%54OvxeEfia~RWI^;-+>D!F&ny;}b~_M? zk(&1hvoY8?EV`Xz$>WdsewN{!HDHDlIk;$xv3RjuL)tdPY#G^yJM2SX| zr7U`$S`DuLhpMHrFo7cCoGYYeRpQY0@9w-(Np{;68X zX(8L6s64@_nISHRcoD)k<7Ogg!51VDH0Qj$6%r7(I^8Y6Q2aIfRko>z!B57`EPMiOE} z0yWUqjaWn$jli4miRK}_q{;dffb8tY>_D|=M7MYaMa1}cSCoeb5cV4%-}3C*^61*~ z+yZ=VZgFjCacgmXbU}_UQIh=+095T#z3nW(W`Yc8=(}_a&Lr#5cq9c=n|ObJm$W(L zqI(7GjopK7yI5Nz(Az05f~!T+I0!2vywLwO5rB~Nb6>pMM8H>V@eIr2Cd)dba#?as zJ>HV=S9nh4i*wJbP|DurT}mhn&?NCsN=sBmgSX@JxjDJr{TxAY&lgzpyEECp5&lz< zOK??9*XvDpIS=E$Fui!Jb2Zv4pydanpDk%gQ^K}M)S12QcW$$B*~RvbWI{X-INLcKU` z1~qN<{uV3baDo>#G#JDi;0Ed|v0+9*<$`stH&0^Cg;94#Rj~^8+#J1|>v#Gb?{Cgm zLt$|;l>5F+j|loc@8^HLZC(F+C-8sXU-@?wUibN!P2$7&e|ktofK<^ufQMhfz3&n( z5Lhv_+aN$eFTk5M!aZ@UEK7qO_h(RPwUJnKB7&o31;bPf#v3O58Dx|j@=p~kb2xg0 zP9IiBpw2&#N(u!9g_!W-!gfS`us>jMpW_5b-kw5=6+1J7IOWvw8e_&$nHC2qkHJBc zbiNrpUH#Jj$I<>xWF&pIJo5vM)S8p`oQ8Kbz58GpVr}Ub8mSnsGeB9LcXIp zAR2L)RHn{RDzdhg%%dpOuyM6Uur+Ib`E~2w_Huf7aW4N1Vq%>_ESw)q~X}b#Pqk zX6T%`ydzm#^CHq)#nUoYt$u({_0z0OrDNMW#G2b{RH8kWHIbVG|HUP{H}jV}=La-| zXmM=PC~Cpq+fOc=XUH<7V9WfBlboE3%)GOHh3CC`Uk@EYFhMDD#KZ_tXeCV`n+$mz zT12r2IJ9KWA9UuQ3&DTCOuN^w9z}L^{)pmG&uhHlS_wx*=5UnUHeof`G2_`vh^-p2 zXb9t*$`+m8mZcp9rNxDPB?dN}M)o%pPH($OX-m9qcx|k|YYDpvt#LQCbr)SLmZ%2heMChA9Fi)yw@r)wx1i**9Nf-OK;uJ zO+vlz$kJyHb{!XG?PtAsx3tSlLqwjH7MPiTG5|~gVwYg)31+)p?HBQK-tJR)O+~Hj zKfG-c_ikLeQ*lRQP_8yWt6`au=$8C(Eb;iAL#euJrfQ4vu?{BjNQ!Z5wW}I`V}R^j z9mbU!d=cem=1J0R6rjoe=Ql{93nwj$0xSW^o5UHeNRL|O%va*e)nhklE_+oLeFe%N z4=y|08PdBQTW%f|8wFn|DfpXcQP+K5P1wx9#F_E@H5+X*X)_cI;|0ZESV+sz8ayEB;&0#u7PZaO1%$ zJ3$9us#dvd?IMx8ZNsubDb}{wKVo}>x@_2d+|E}!{_wdELV&f-oDY9cot9SVW!&q!FC7+6XUi+1pJFXQ?D zsCvtQHp8Y{_bu*F3W4Gd1wtuM+}&M+I|O%!;_mJQcMqSV~r((3jmoqHC&aN2-ReC4N0+602XMVScP$7)gr~0`3<`GwQ*Ap^u^q!u0StJ zNk%%x9r;@{rK-&Dndv$9F08l_HFcs_josbtt;Flvhyiqt=7uZFdh3!M%tD64O{lmK z`i5J#U-S6xL}4225tWD~^ir5V1a;qnY0wnn2tL^fe40L?Q69USd|GI@7YGC43gq3N zozkhm3J}?hm^Uf3SE5grAEBknrvCxV3b>al5_HV_wn)T-6CL(fvtQuM=s#|v->92^ zT5%ECBGp6NPf?Ne7EOn38nephj8D{=>?_@zyX<|NaoM$b<_vikb^opCFRo}Tud99M z))wMj;N_W*9>Pv!_@vI8CjK)9V zS8bfDeNW$y@l0mykMfMs;>8M^PGV-u#iO>4G%D!J+C&hbG!<2Phy%LGGMqJfN^3@} zd-4Z`;tVTTAtkf1f`vk#*pU&D_ifOj$n!=HX3)rEn?7ZMWQmEQj*ihbTN;WRD!SUC zr5*)bu(t`;gA{L>N3DN^{@A5uH02Qm3g%-oZ}uHHB1rkC--$^%+!Gf9>aoM<)%C7@ zM1l6XKUrw3%LfV@IQ7(=eEKJnh%Fnx=2Z_Xm~POYNXB9}*k39w=MM!(fT&ba&8G5NXC-N5ogjq z0ASKM-$xBO9WKPeGz&Z^hT+%4PQ67pmd@Q|81=UQshPIr!a6|dA8X#=F50j}KC&alD9#3a$pWCt*3_Ux6uc8yFN|j&z7jxY{>*h*X zGnR6!mO8!wd$KMXF6V7xCGyB!-XCnJKvwGu>CLgM##He_UIh~p0-;RVd>NNz3;Ps z-#6#;-vi#Sjtt*!gHd$F0tadpgBA|sZUcs~AG@))QfbTr#mO}q zJ%cH_1rsReKK&&K0j>u_thjOGB{hqtEz8Z_Fk7gC;(+D#yKlkI$jVm_57@t&cZK3W zWH8bR=-)5p;&W=yD}dvSJdpRFlV3Y`;s1<|(SLkcH*5HjXsOsDjE_!7;F-a*fD`|3 z;undAhJU-(tTb#(yb_hwIDMuA|| zIEAf#`%0217b%MGLia1?>nm`8 zI=5)Mds}Tl`-L!)O``Opl`T;y?{|J>q3;62*`Fq5e_cDbYeTfv8O_1-Ns82eD+%UD zL(Lzn)tR+3@R?D(sK{)>6wH$&=SB_>KcIc8g6iPt#L_#lp*S{>maJGykmwrmo?1u+ zweTNHhP3^WrhT!NxAUDrJ(QUWT)@sr{*OFyFcsW zW@G7MI9m0;NXS|k-v4gJ{oR?Jpi}hV6Bd`rF*@IsL&`e8uH&t%p9)=m(5<$sKQ|j6 zzme>TOW{vINK*mqPquUbLD~7#%)_iJi^q5gr$L~oE%dXJq#*rPr7CRkc3U(-gSxBH zUsDFVyEt5PO{$J01S-J*t`HxKcskhaZoVP|J0X8-bz&>BV6MRx0V3+}H-ZFvG@d6* z8oB-ex}6yBU)jA{8;|`OVuOd2$OQeKP$0ngd@@0A@Bdu&4|I3?5JM7t+{2bY`l?y} zHT@Lh|Flgx z@B7*;4A%b44o=lEbwk{qFmHRqx_Tfqch2DykfGnu!9&aL->6?C!HVZ%XVO(=t}9nJ z;-$4Q+7_=|g<;eHqGi5;7FN8Hxo3ZFGvDN9Gvtd8Fw~yWancjbDG+u@nlp zf3EX;c;$k9_P@%6yr0M8*9BjTS`~YqmOJZSR{p&2k@vpW^gf2a@9PG<#tOYpzP}Qz zzy22Tf9Lx@l*v%oG3KTjEKRQu@n3iDk@y+3FAF>o|6$P05L`Wp3K}l%OR?cxA{Rl7 zm}7{bG~FN*nYl)P5-1Fj_PBs;BrXTS<2QyKIW&76ldkY1^VUqc;{@R70~|fy`v(Rd zK;wrEZQYuNefYvC(Lx7TPoxI<1KZF}e$Diu6IbF~mf%@)*LDTP1eWfJ+J+1~q+E5T zG#xG1WyE%cqFErLEf}iVRh-OxxL4vlnWIx;kg3ngI0uf@o5Z)Gac&y@Q3znH%?SHi z@@Xuym=Vw(CQ)mec&wAc%h?{M0Nzjqc{alaZ;op#1rpByATfyyqv=OE!Urlw&iF9k zkJGBi5z+DhiYnfrfYPBjbsp-D+*ZT)A#$()d&*6?Dc6r z!CvO}z%J>jguY)5#s{f$SE5|S)M<`Z26J?;S#etzT{}NH1u+{xQz<(+OEFy!pZ6)@ z|5v(vsKf1@-Fl0s^CvGy?3#nEV&Z)>m7~Sl%Poz}l1X2BvMRE=?s9DKxfBTu1Xi_k zXp6UfIsN+V;9U^n|2l<$^h6TuEvM|zYe#Qc;H3B^x0IS_Xg1NDG*^`jtVp)iZ`7hY zrC~ma(u(#{Yn6GFb9=7t`e5hQXwcdpdBQe|1z;-t_^7|6IeB+ixaNXF_ln|hC8<&! zNxQzFXLsAZv%!Amcxk-7X0J=b&9C;t)=4~H*NO*CgXGDN%YvREGXU0X|C^oZ0s3(4 z{<|-WUak)hj+Q=RY-8j{R+Zy2Og(N5I}3xdA?+L%2G$eZ-?Y;N4D^3gj1#kr9E^0X zW%>79E@b{*u_T_I(^F0G?OB=w#`r|bo|00m`l0gypQk`ak@*K zIZ+x=(n#BGV(ukTp$@wqk-TQanPqFyGyry5u4ZW^HU|0!z?4f=FzUnW#`9%R{z9Yd z^1_Pa-g3Bu-&K=g&wKD+!I#!n#rK0;7a@OGp_t-#I+{1^dG0lQ9BJ)+zJK5T+Vi~r zw#B#paDD#bk?{Ud_kV3FRiVsBcA)H`y+CJtL)E^m|5gZGKsQd`kdu%F#UDCm>X9}5Iln<(eS-MXT2Jd~Te`^7ZgDA7&tb zWp>=5BNVzV6>d)wp&BJK(mT9|N#2Ug`w{X?m8m=pDi9SG>D_;yYt-#Ek1=IKf6Nb2 zX!aFSf`RD^eMPN~hjt|1#Mx`3kd`YeJ6{lS{4yHZ)=XBv0$EL_SVhrxRK;Q^hu#Ow zbg98T(Yc^8*aBs7$Aq&YL4wgJIeIY#m#!p6u4=C#YY#D)?Tfp4X36|y=D3_i)xusQ zx1YVz)Hx=ipOI~Zq$x3Rc0@Q!GeqKvNy^w~d!BV>hj4EhgwRi7giO-a(Qv63h})Ro z$0TKiG?QuCkx>jlnr|s6r|oG+)wehksUoN+*D=KWRTyZ603<-800t`0rWKXW9k1}! znz2*B67qu>L}HBS13|uIWdyt}!)X9mf#~P}TkdlgBIY^9jpZBHw{f5)%NKr(aHR^B zC~`WGCrf4@*qW^OVrS;e7b%I2$C{Hj>42@XlI1L~KFbRxBer7D#rJPaAKId&%22TP z&wf$o4E;?t+peCV4)`2TUNSW_VP6K{5Yr%(yx8Yk3U(?gN35Z2EX=&HYtX@PPo6f; z%Pxw!!EfQ*ZFW;vTU3V*R<e|`B&d(U$Pc2IS zU#kr#>eFxo`#BTcL~uE^sytI!6|P$1e2!DY&M3VXfl%#)(sm*pzlFk-ox%bRDFiUA zlOqx@(A-hpq{MC!?pSTT04LKXnl~HAW(NH9a%CWE%-3U*uoQz2H6o}CdkoE;>*7>3 zMlPw2*I*y7A>CZSaH;$_r}}{h9on3&HzPCZ@VgWyGO!SL>C6Pwl<%9^KOgeNlIz!b z@0*N7x_GP$YPs%N?B;f!GAA;&xSb1TWWhwio9SeFBhc%{x5Yx$^fIGv7>x>^twO|@ zk+xJbl`()}*@#+7f6erc+p>9-by(FC*R_(CGCi7Tx19%ST%cT&bF2J+6WklVT!U@= zL0Z;l!up5eQq;!v*=#&A^fQacA7_sQ~JC^2Ifc*Q-BefI|qD+AT(DN^wE~HhapSiBh;eNa}47e%!E98GWp%n0N z7MkDXvpGaR+2g%6UX{?2^R`*H7O?9h^m=FbdYb?8r}w4zZQsxjej>ngcjf=+8xYGS zT5X*&PYuH953WOBBCw;0`iLB|eKz9#y9x+hhUXibhG2LlIMqV{imFhTk_ICtKP)oSk+Tq`p6x8m^#zgBcI z;pFmi8 z`TcJwp|+}&**Sk8c=a+KnIUih%J{fH#v9Iui@rz=AtH>HDn|%8_Lna^jX6Dq8^Z)k z(W-H>v@MGmYd^Sf<3mkVga{doAl3-B5WD!V2hLu`BcC5ug>_Q?ZeKt98fCSbr1%%9bJ*aQdT&Suq_Sd=p?PODy?+ckiQ03j5E3-ueL*wjw<=_^_<`5oU+9 zp^b)e*ia!>bc`j4cf`0%FGF`olRZNDv^G8S1Iu>BHQo6-)HV z-h5cjRAbC>%%O3(KkY#a(gp+ODJW^OEuo1!NKnd3qq4zh_}?1pD4v)&pjKJDL*`6& zC8eM`JZC)&NN&Wb5Dls~i2!w2wb%T5ad(#a+GLTTEA+F9bWa_-p7Hs>9Oj}@G%;op z?ERdr!RKFPdm-sWK3o;LO5n0As8IEO0UqQdkXsY99cW?R)yJS z9X+o$D8+&CN%tiG*4I~-{;kC5-ZC}WqLsEnE0}xvuO&4WV$*)cOm3Y_c4pdnwPvx^ zaTXia_WD%_o;r*DhVJDw$91-M&zBU{U=}BS!1B)$gESuMu_V)POiZHg6YSBdj%*d$ z%Ct0Ns6?wmt5v_u6QxR)EgVAFYe(!`xU26%DhH9vexP=yef`w(;#8RCNQ2UuiYOicO?3w5W4QeN6rtg74pk zx3H1n6OCALgWSZX9dTqqR7NADky1qoO}|VxKq1mFSJslA;repOWLxLdX9E0|K3M69 z?+>WIzuqpp5E2);+Nu~OpdHo0A}3u7s1G#iy~$=)+Bh zh>L456?;P`i`kceMyn3r4}pN(k%FlsO;^d->H5O?5e=l0$#TvtH;c~s&-(hhtN%c& z(sKZaBTZamlBt)tuCV>7$mg+zH3Ku!r>5%j6WjGpWU^QRCLPhZ_2uRNMDp9%`S{?g z3{luswtk%3YUt3nC>z+RFWjRu8p;#~3Kp+7X*IL5FRD$gYK)lSaFehlE11+@m=UE( z=g}$GE6@KdZLr|xPZVYVOvnsgJ6CwhOd5ZZ^d3X~Wt^yocJ^_ErYZVbbmZT7Py*kO zB`P-uS?o9sj#aYf6=7UVzbc&*4MrSm++e(XOzUih8dfiXwbZE?Pc*#UOO86UZ|x76 zv@UZk0$7IbXc2DK=)23(tN^fGf7Oi;E{^Z3YW4(DC#mgVlzE`GxGB5OQ%98SRg|2I ztgOo@V-&bbmTx8d|95bfKDMl`zahIJzMuzamk6r|B@6QKDGG$ zU7$rb!Oq*cnhxyRlwKFe&gu2wxgkq;l9oqRIi$W+-^RsZJ-{*IEt)n%M#%f>g7T%T zf2h0?v1L?{oBvWLeNQIR@6wX`^+#7Iv22D+M=DkN%Vk|J}Z@Z{A<)8+hL&|qb?}r>qt62 zS}F*lSy%pDHdeA`{_w^E7AoQ4sdx5cX#3i`yH^Htnw%aC^IKNg7za!#ni^lr&L|r2 zUQTA_ce~ut(@(x#)HDfsKL4zu2zWibHxPOpg_V{S-7^1+Xpgtu)O_Flm4N56^`|qg zx2?|gm+14ik=CB)viFzQUd8`|q7;VtfY&VQy$UJ^$;0%Aa4@6)kl%=1;i5f=gnguy zqX}_LbA)+t28AUDD7G6=+D?Rv0I)>HxjBX7hp6!CQQK`r z$%ylZGA8@6|7D&j4BVL;bAX}|y>bvO z*Hwb2wLIfv6)jq>UQKdU45J?&hPw2`bYc?XsOrK3t{07ph!r}>xVfjPyS=lir+YU# z#BY;zuJYtG(2m24)q6*>(GXXaBgKjo_B%S+6-6Z=dnl57yRfBAvS}9XL-_js&7+=w z{@6bL;B?f0?KZLCv0}wAm z8FvH4S5&y0kQJvo?2@L-)5k-ny%zl<-Qf6f?_>*Z=PN}?VP>dKt#n`BE}JI@oN>nJ zPd+&R8xCcSXwu6eh2^adW#j1GHuf|2xga{%d}DTc-mJzBZ{o6YydL#Jw(MK1@ZYu{ zzXDNzp}PtOc$zetI@a5Gwpp4Ld$`)Vf^pe-V0_R2+NMgt+*wisg$c~b;g(Wa6{NfH zIMAFCfoFr|3J=+Hy%GhiC1_N^7{HM}3#Z+WEPeeL?IA~`mz~g6Vqsq^20nRiq5lx| ziA18jI2zL|u>qZ)F}NI2cUh_~ABR)n&)*m{S?^3l(v&WDSrvkAi;NX+PIwhLq5sA{ zEJppQ&6kGh7do7dH8A}`$DUnd}T^Yl??@9Y5+ksl9qGCK+W)#-8(UjuKUkmo=QqtNSbLmEukT%=PQ5;KUu}` zw&=Ro|M^BqDd%xI(nW~hX>-BMd*VR+_`tv1mz`K&rgJHlU zEb0PQ8YYC^&jQ|?&V8@{^j=Q{y!=h6q{V*^|DTwdd?_HfXxWH;vCk3rMsyDD5-kX1 zG~`ikCc@^Z2Xl5wP+CSxQGZKlU}!t3rbUY|Ij*{lh0ZI~7a|SZ{@$CUJBmTK9{WIL z%i|lt;qQ--3REg^-m%*7-}FRO*pbH#{<}Fa@QQp7n`kJTQ6zwr(Gy1D-^Dds31IuO zAEaE&6rw88^P7z{D$%GCK&%AC?!V?u%vJoHohw$~M^h|9muZv=0Q2KrV0)FXT2$o{ zRT-h>67*^r>Y(_s*Akfh>l#)Te4g*QT3a>$nm`i%p0a`D*}5$>yG0EXjZ3ag3o`{_ zxMy6iT<&D4qUB8K*;E<(!6Ki$9#%2%L61`~J#?%nro`08UeROqm-~9wQCpmK8?U+> zr0hZM$*RoP!q{BVBB6>S$sFpb^Ht5dWaCAvc?LgJxntZ=IT}Ap;-Xv>sPF8PyP<_= z5Ie-VP;^{Hd66xn-Cg|?fe%I(Sr}&wau5!cZd2{T;i3vhB$jH%>Fl<9dgY%{7*rng5X)<-vuWW7NvfXL|0Y@81@` zbfP>zeKHE|H9NWs#UpxC*W>3kl60n>`cV0nABy`4Bd+{`ypgmfbo9K3wq5LmKdw1< z8mi>}{Rc|@|7*kG-w04kOG_W`BYXL-P2^WG z;Kum-wMw)Or4AJ)dU*DiQ*(IQLul)tz5I8I?HeC_oxhp0MT*^Q8;y^vSlHsgGaB$^ zQTZUIN;JeF=pP#^k7710y5@liwOjv8EJ5nM>eWg;ZFe&$roc&7ot=*P(3HS^=4#~& zOyxMDRHU{jwGYvq|HE)fwa;bB4*vaL`$hMPOMAh!s=?6i3oI~RU+aqRk0msx>+!wm zp6pFUZ~yIG$g%hIzI*-OzdY{UWoyN+_L0ei#4C94r5S6OoZS(O!W@*|QYaz91#8E5 zYd+^qYAsr~8tDQ1IZOKDs3TAX$0hndHf*5CTy}Pw2_6T(ygx3scZ?CBHQk1Qvt@Yl zGr<;4Se`TKBj%|sfg~0N2IPv8p)ZdxJcTwG^jl3VSZn|VDvu#Aj}a4(8CUEb{@j84 z;b(2yC~r0o)I--nztiXLVbG6H(SF6#{XQHOaYBI1nL*y=P+mf)dn z=?6VjQJ`~!`j^xyGc>4Pq-u?(wHt46zZn#Csu+&$Vv2C2PDEoh2PY&^9tQJvA&DJ^ z{jpw#WB;Ino@` zob=iUC1at!G)WE0BBKO1k2BtBwuj0LI9SM3i4@};V3Eo+X>K(12s%g_jrI+lPe-SZ z&rCBSnr;El*~akH)bIt0zElWkzC>R)@4K0o@C@4?mhoXZl8 zUJ1&Hs4r$eolnw)3eSn!=>E$XgdGs!x4^cb>n1B(7Uo`Un>Pnkkq`w-lrEs}0xDF8 za0o-tK0V+<0JKu&M$WNpw4y+5#_t!_N;}CT0f9j%NT5F$_cym;)Reh#)cBexJX{Dl zCW71it=r74*EFr$9A(gmX~#Aw*uifp%_n^!C11zw{@-#G`tfqXI`v7!H1Qm;1%T`wmYiW)GS}O%<_aN~*$rQ_`uHbnd4HNLlkkH(0zn1~=Jy9V>!*IJY3jGZp+ z!7C#>O-rI=TFe_VA^a7DX?S1EL`(Dv5Og#DQ>souaBW5vm+}X%(dKwGYlVq50>m&b zWo|V_qx260ED{GKqj9+WN6<2*pP%cVygD;d-ma$U+IeE;cdg%`S>dr5%ThLJA%!UZ zlT%Xa5;}{FWykI7dff1dPbqiJXHW0#mU!Q}oR*+{Sc0WT_Oe0+9fg%0VOA6r{GT@s z4-c+xY(QH4@|Hi?cx=a`Kn30N2^LX7mnu8mY|aLAj!4(*1*Ei?p^mMS#Y+G+q2ddU z$!^!`uS@V;*IapP^|tSgJS%{153`ky$z|udRTMsf?TqxkZiGWxlnk7vi1* zhlMD=>xI3#HFvAqv=_IPd#yQW7OA%3`xL_I92;-klR^w-aQ|h)Wy4$G8)OsGaJhLI z3Av2ijEqb~)yB*}I*uBAQ6D}o&Cx7Vq&07Wtz_Kpk9I4QlI|)Q{e7}SCbrlKyA+XH zo05mjT$W5&wEDNdJcx!$7Xrq<GH_L^RxqfVm z{EYr&fF0>0gIngCB1DK0@)Kf(hnCa-Zj$K9ob%(e2>VFBESx)oE4YY-OcSa$Er>LI#ee#-^aI@&O|;bcB@%rLv*X1C%x z;P&@Pe&EaY4MD*5(|y22Q-J^3q600uQ8SEr z99o#r>Y)^%v`kKrl~;^pOnJCCJiK^tz^$NL;3l_s`DzP0Uo5cqYxrPs<3bwDFrR)j zfro<`N+ps$w0Fr{fX@;-jjEt4jSUvv`8g8@{5%V=O0;q7tjQRXG$Bi$6z3Lbo43Yg z)aNads9d-<2UN(8;Sdfo7O9&jFfbDZ2LYAqJtf;mE5CcMF81}6i361;rTOucBu!;5 zF5s0!aOrbd;@2!viCk?i;g&+WR}tM*10u4c(sxx1dO)&?h9 zS;XdkekA_I7%N=+=}TXnVnKlh&^b-|H_kx`I9Lz8ht|sasG4t9ReuWPQ(SWAD7Tu} z^7~V6jaK`KY-*BeqZLI5l^)^dZpAiN5n*9$yjTuQGKVjep zQOuC&rMTorc!_V6#fgFSNZ$)fMMXN0kliUnC-Q(S*#pO=6IRx9aHR@03g(099<8nm zGO1JzF@HV}(0M=S?Cq7#ELVpk?TN2&O(Tx?a)g(gd`|j@X@=ak({S@d~pM^JZd_j(50Ax*<0qoj8i0W?(}A zjB1hyD(IW?Tt2_)m!M|KV}8q9_1-5_pa05u)VCC>!GGp3@VqX*og?yYe=Bo z5T=cbfX6*y^};~vWDf^9Cr0b-Y*pdqBSL@RgA5JHGXSc1h-r343zE9FKT5uMDrLp@ zfz4S>pD`37$V7qE)YL}y@NtO0QNu7xOK+ZGSC6&-1sQK({}wABjNNjO0wyQMDtt|C zNpoLgtmNx_pM1mA;$#+R^xV7iav)#o2wPGY*}%dMZQmpp^(8zTpfnGB_z$v@_0Ll@ zIvr;@YxsSz`ExANjIi%V({yGOfJlOL4NwVCwGDYtKt-TUrS4gIJPzjBxNc?tHt{+r`_>bwt2?XsW!;N+}aYo2!7 z=J2!4m1WH`m(6@0Pu1O|VUxqslK|7g`C9$8v`g8rn7FFgfu^v)*u$@bsb7m1^xM?= z@;`KLr5zmZr!H7ChGrIkDE&XO`O(gt8Sp5-2+4?!{fG6?!v{qmBWiRWH&c~46X%8Rd?}6$hi{?*#WcD(QsPer^@B%MOeq@ED+yAPK-hp|6C=q=>hzI?YuEi* zKv8H9ZR3pM%z4|MFR;Cjmiko?ZSr4Xpf-O>gqWNZ>toDQvI_aA5gU z!09?Xl%G-mQI0LG7qO8a07ZCnMu0a8l)!0IE8WQF_3*r`T!N{-rprNp*E8RjJJAh& z9F~sR)4HDao_k-mWAa~|mg5b5u0su9&V+jIgWs=f&R+&x)?f42y*9Jg-};5#_XA#M z|M(}10oxy#62*XjD99v$be$td4NKpM&xRHTUwo8MQ+^hf!)~x8{xEE2M&r;7wOocdm8YI z4JXb2h#8>A&rprQj*r2>!p6e5yDxicGyrZ|4af5Ex6R6Vqo7gyt=R^Bbwbc{1RVv< z3cwhfx=#Zn6f$MAcC4Q^zE_RWs0aBqiI?old<1W1_fw-o08Ns0MyNZ?c`71B0Ht>| zVcxiB3`NuO?|tM|Iy< z1t;t(Q`r$1TNWu)!H;a++9k+*FnXg}v|NJ0^a4{ADpEQfiQiQkws2JVdsM9a@g{Ju zQcOYPQDGiAiwT=ci`=GKa2gblx8xK&z`Wg9RlJ3jnpHO~!_1jGVckOEf&WhlsYoS& zX6qK2b@coeF;X}Ijq(Jpl`??l1UuXhk|2bKMwKZ~lUWe-!OV55Fk2v-RC#{uqGKjE z?<4rK53aB>>}kx7^q!ZOX_@b=#Usswm633qk*+FiqGI=osQpDUuRiiC_+u2GxN4a8 zWXcyFwC{!`_*HIfu_Coxl^7P7^3e-c^ZoCzceQH8;+p#;V>Ylo$DsAoeH%)I;uKci z2Z6(Ho<<)&;ITlz23g1?%g}7WIMwA7Dl16sS=7N0h1V4c@ znG+x?=oxWACCr`}0sv?NP2{$x=E@aM5g=H8S#!bb*zC3ar4yOcV5NC$ct(eBUx}~B zkF>KSiY6ys1ek#gRzhuM|Dh~Lz3*)Pd<)X_>MT~VwNvw|Tadvb;5z3qe5C+ekHFd! z%PMRr#T-r!KeZ0GC~$iqUtV4mD24-#d8eI&n;}3ryiAg8 zQh>~i;(-~o5{E;mP-%cLe;KW8gF2HM(GIvsykc7p-xd-=ixapLQEZY5s$kEm#>$3o z;_FgG&R9<%qYv$THNfstY9r8NN5c-eRK~y_8j=2o{Y8!+c80kq5MhVeKLTW8C^QH6 z?yP|LVo;=`Z=iE^*^)ZD|N2<7RM*9`U*Z9P!S9FU7A#)3u}eY$x2FUK-rLWxq+*Z|AGr-%6+U<%n0kuF&kXOWg<>P9Ilehi&Y~{>`Dc;<&Ps$Q^3=h7P)bxX>@DRNW%xEm_Q(olCZ@+BuxrW1AP;xf!d6 zPTcw|Tp7+@tBT{NNXcJv3Vh#;aPC#6aQ-ISU&nZc;Ey94b&}6nWrmSzrcWHPEg9J* z4j_L2YEZW>g#bsB;eiya&VaC&2fIK(9@)2CqRmVgA8vAK!~uwrgil0?^(?0truZE4 z9(D07vgJ=sCajyXCbKJggS8ya>GL^Np(Bi;%1)*Tzz?);FviBinf8llEFV0mCnl|T zm9~La#L)GK|Hi$74N!7@=6$&IbkTdv{F3zaW#AEs|9b^bYp=Z<2Fj+HZvMm`l z!t4hIesDy2il|UOI*$RJNQPE#vusZ;{YDRGL~GGsZ+UuXy!QxTZDKe#HPEezcQmHi z3+uU^gNZ7VgPG1_HAr{6Q42QnF<0)^MZ+^RPWn~JBtYv`p9YIRk6F~Tr~8bEWn(diwMiW25x0_zmkh!ZL$!cE!lj3b+k&LdBWdhrMCGG8{I-| z&e`Z}S?pSnS-bvhsu27Bt3j*fM4FghMP}5^EmmA?ADpjtpwKs7{tvXkcijTNs%jIEYT}cJ$ChO8ZmRz?n@az zbL#BM$o|hP5ec^U#Se6vkM7T(@{G6uN0$hn)|AJ|<;1tXvF3|6(8HvT# zp~LcQ4mop#wnEgH_wjNT`Apx49mZc)^Vgpz=Gwnb=)HtzMhCp_of!7;?>-v7Z?@;Z zovr7+kH8qR=lAXQ^Vb27o_C?%>z`bCKAXhvm#^>V^t~^N|CKpL{=WxaGJE={MQ0Uo z!2qGtz@Xf~EjW0+c!i@<7ef$m3RcN#={uqn*QMIjroqU@8KleJ{@s}rSu5^gsHH?M z4%Ar=3IPrT4^WE@6j2wMem+hcK}(O6f9qD3OKXaWg+1+HhSJ#=!i_|2eG=3uAo8$L5dP9JIBo9aO%lWHZ83H6b1@kT^J zY_xb$x|J(#YI%3=pGqSS1M?QW6I=cj&4B8PCr48a5wTs!lMf>xK*xK=LexJBdMh_{~gZ+H)LGkbLuAtca>N z9|ZvgLC0vnNSReZH^^~_08ONljc58eIaxd=4CTQo(%PJ}XD`<@pSq61wAYr+_<$VG!7wy&K;U=t|(>;bB^YLEZfq z2q0Xh#7v{9 z8Ki~XsI)4L+}+a|Z%9d)WxM8A9{(o4^^%Tm0&x7Am5^?~kXyLl1Bg8q#GXQmS#ey_Xd)jQDGqE}GCJkJy7LI}`_G|h09aantluY!|78k+J?R|a zNQ(c=V5*F%(Etqz=Wx+}7jAm;<3&B_Wk9<+TvyX}n*iOn3prZr8MiAH*M@mL2hcA~ z0k2mn1pc?nE<(?zy{`uZ>o3!P-VVdR`ftMwC;>tbhrKUI{uD*zxFMI{#oeiqXMEpGM3gAT?E(r_kp3L98oCO#te3Q~U_0)&8IDOXFg^YgQ- zv+I1QPz($VEYW2+rbtPnPY2UZ7a5@o2F;n?9YQKk{ckrE=%gH$pM!JEP0Y+}tvort zbHJ7l*f+D~w1_|p3b2eE=mkL(xS=8;kiiiU$V*UvYd(xF;;F|AZ_%hiRyTC;qY_ZG zM5AH7WKtzwVSc5LR3$>gH9BuJSZ#EbO7dZ`wVV=sc>L_E0X%5u=ZyQv;)y2W-ApLS z`;EjIu*f%J>Q+R`WzpZond6v{vO-Q%*dR>6k)vRw84gzg;vlKWqW(St^@fO!;2k2$ zE(S(0O{19^g}2yz`!e6{*PUpEGSm;RQ!>w5vISx(YVJPNb5T%H8=U?fWvZYdbu@Zi zC3~;2`cK2d$I$d~?e~=*rpFc7{;bW<{`$5sUg=E7!26P>tX+Q;kAd3+QH#nFy@Z)^ zQR|w9*ONQd0K;4asp2=oENG4{dc@XAM_w^{+PH9bhXdCC5qz-m`d1%?%+sQb1O4&q z)mjklBkB&_2eu7*h$byJDiq<%FT-JsAejJyj{pRnGy1q@;F_x{hvf+DYO4sKywS=0 zIE?J#R|#eG3X(oZ`8EOF&D))aS+R4_n0>{LE_}j>vtk#FD)r08{Axved^xRB?(gx~ zIp^GbGy5`#545afhk@nVactz_TEFb_zcospLcoRc#q;Zi9IIAw4frtsg?U~Kn>4Xy z5D^(M+Q5;b9_j752#~H(wK?xnRo7xDq+-~7zEB&FuEnz0>N>gfsv8pHO%CfRsG>m; z9AGCe)Yi}lXmo7JZS@#_jaC)!d9%#%L#b@sB{=utSIy+h*sagkInwUwZ=azjlI67L z@`(Bd&BwO$cgY@HWI+Aw0Jq&dISQ@w?k@13-PBGNmQ{E6{;*HyHG~6DUAQJ;SDdFRW)yo^GUPFXB5f>WxxNWzNx)?I)d8{K3*J)tnPC=! zRr8A}`Y%z%zIa)^-l(HzHwOxu(8=n8Ayr-!9Y3?6mMp?$K%xSDzO@Bh@X5`ayMpQueu2O^sK*2KhMlqfumgxhSsZ>rL?3=yUM*&1J0@9q7Gn>|jKlkllbm6I zojQP4FoAQ~2=n9N)nj*Jxlm%vuR_YQX&TF`6x+loY89tqi%?Dal(EmExx&UN<#T!! z2F2obdGp@w3zhQ$R~v}r*%=3ir@tLL4ZbEtfF5&X1@~Es9%X%2GxwH1oqm^y$@+Av zA+^GY%?axXG;$36t=CJai5VUA_FBKOl}j+2qtasZ#87Kq)AB;*C^QR$MHfsHz~hX; ziZwWLpK)omYVp6gs)lM-vJJ<&3ml1=d3ZK7TcTxJkk?-zi>ApR;v|(nupQxpW|BU( zQ1p*cR|41wiz(4jzq3WO2s?^7ye88Eb>`qOof4($vVV*ZN_aL++P-zzX{d%x2cPuHeWS0vIpuXOv<8KLCN*ZFvN z7d!rn(&~z;=kxXe*TRmWZo~de)GBNGc~3L*`Bu{D{++_{XPzz=}R*b?_IpXNgp&_zx5Z!Nf*+OP8#OQ%x6dRXb zx{P>Sl9V@C$Dx^M0;{HI13n z7KbG&bi;E^fr_HMy*dIp84A=l ziitIlU&lzX7$VjIyCY#wtFakF$LC> z!_vKf@KGjw5ITv%oF%vjyg{xIMv>KLm^n_dVQpI7r~JnoEm8PWUh|Wy^~VfaSpm6& zZ+thRnK}^Ykvv+d@E|@BK4TN;yphM-yMwLgJ-|dNgF-XCm(rG3gUwo}Xvq%lw2AzsowB0MkKt&2J znR%j~ahg!OJU7?^v=&-^n=elU-xhAfl5nE9MU&A8ThdM(e&^A%AgI=A^pT|^$ ztNGQ{U!84s?n*J8+1ScG?X+atI=yZyIQ1T677F7Knub?_>-gpP52xsn^kWn z7e0k`YowvVE0)ldfNi7ua^q8Y1abYWvW&^4D8nMt~76W1{ zA2j=KEOn?bqpf*ErLLDkOqfk8%lT+shkw({%MxFil17?AtwSZ)oTK#{2$X4OH~xRk z1GIz~f_8JF@eBLcf%D9Sed|kAh#tDOjB(YsEca)rabuzQ$gTN$O$m~~mpI;;&uM{L~Jn-=KYfNMmR>iid%S+&&nqZ3L z%y+hwEKgjp%q-xmBCj3{dnsKY`VO;mnW-I5P_<_hbTvV=S^KG zm?v}tH>ub_4{WhY25UK1Gyr7_VTbx5gT(b)lze0j-UCB*>{tf7Ye%k4CyG;=5m8%#9q(&ISHU=II?hZB z#WJpJXX{jK{&xNUL)ABiMgD-%Zf!QFw%V+%CTp{e%{AHBY}cJnhU2MjFtwnca)@n~+U0VM_sJcbP-oXEBiEtbEJJ+x((Ea*(+>DwB$ zl8{dI+EtL`f$f)=gfh6g5YhoRLBhwNUPlBWj>5R0T+zSe{=GsT{)has5Nk zOxg-F<$)p{hAG&tjz8|Tte`v-L)_SAd8pN1!XXae@m(pOLWbSq@Rhx zg{u)^N=S;=P|hWUNmCa(5?R7S6;uKv5cT|?F3U}?QJD8&t#o<4S+UT_4! zssj_+Zb@b%O}N~foAW6JvvG}vAw2&_Qe*`2Y#pBtKhCA%q%*2gYMluWhAAoXihzzN z408By;Cl}-1o?1%s+IOFw z!EK)h+uv1o<<_=#R!7#hN7goH)+7IC44@mB69TTUuN%yAKhX$Ef)iy5x?h535-Ht% z;d+EWlkZr&x)5Ausd1!}fop?6t2Xcs) zbgvgl!;(+LsO!w#acr5|S1Ols!n-x3Lh2aMD1KOE&IwQq=muF++on{{`8?cSB34(C z#yClU>+X;HlJHf2%^#zs5EwYng*=~LcWlh@?0B0jPL>}888aGl4UTtSEys{I9t|FT z)-}D~ucKB?uHBx``b2T!FfO~FPN!eHb~4Tgaz;~24U5CSuQNx1LNTqR7twN-S)0c? zl71j-?y3j`gMJfd^)Muq1!ILs6=Dsm>eK{MsWQVV1N6J`0zIJ{@jH7#Jy zxIqPr-vXZj^BzWUe1`pPa$-&Cr!1br_oeH)s|t->ZH?}eskOOuefx7+B*d0$jPBFOg; zR>HLZ|Hc7_Qa~@fSL}>#7=|I@6WdBEib+eCU`aNu*j(xl3;?z;{n?KNA}+w^RfHvJ zi2q2FAtAWFppAZJF6A(^xS*`4&8t@S+@RFY-u~05gaOOU*7Pu`2(e|G&c9#CPOnKb zLq=&Yfq1R^v@r(M9-VxT##`S$5LrV4;)p5Dgze!)0Pcs|IAVeiR6vXdAvIYm17_U7 z;(;gzg<>IuV=a*c3^`3lL>7)_f~_%9@cZNE`W0A{=>r8e=MuF(7_;QYFLfp?Ff0K81V`>t+SCz820)AJPKf64h`=gxCWt07{+~k zcY-7+EJS?8F&ur(o_8I$Crp--`cv&b!)1lV)OuJ{C~dF&Uq&gh)QYX7 zsL<|(*nqFaCJxQ(u1%}^fd!()DT(u^^^n;RVn(sxBD6M(0ns0XKgwr~A5#m{m(@J( z?|t=9TeM5fVrXyl8(KdxKOr2=1X*Qj6Kbyzto~nd6X@#OkFowBPx$IbNJYIT$K&k} zWNst6lw4kZ#X%m(T>!sp>L3qMHnq`P0?eA;_(KoROx?mAnET9Io_M1sdH?&_Ui!WE zyZM9dexvG+Ew)56A0-_12s>nd*m~3gCze}a12kOx8A{CctM=B;R5t>O#l34GLqo?? zcgmnK7q7zRURLt}ZSzX~hsfE6(>y{$vK>9cs^jTGAw>Zpl5M%*k9OD16ty;{is80q z=a&|7?MZ)~=BL{ilB{2y*O%H+4_$4iTO#r0UdpbKC)am} zzt%wFVFw}V#FO!%|DI-xsAjdhkOpOkd@IB-h+t&lQGl5(q=N=AXqh^hcFCJXo7Oby zA7tYM&e-RJN*2mlR&n7n1VjXqB&>@YbrOj zAMa~}TnUmi5{KJHI0+Am*9ratcDraIp5761E+|MxI~4;D!bkmAOP zdhF9eMLy+9W~3NJ&@RSUfBAkm<{ibzBvMa>HEu$SCElC*f*R3#B}JJKo-0FM9Ycd1 zaqg~RhjmYRe4*gxPkb08(IWa*vK;`nxAL!~5vhkw!HJ;=8N&xUj!>@y4|6mQ@h+0m zUIrw!Tu`vOqWk`ke35qoSOg*?jxircCAU_Y3o|3c)WI)c7by22#DJvyrWH>-4c=5K zp>2MWvzz2#zXD(pAGYnl_S1?#Br0LpmR|9Xeh{)mtrxI~fM*Bmq`?x6{l+z4f~U&{ z@>yt`y*czvR4vA>g2wixsCTz&QDPvxX9V_J5fTXS%!QGv$ZD!`iz}r+c*TPC8@e@U z%PipPrTa7YA|{LrBf9G|z~m=1m)UqB+B6 z&Y4^J)S1q2VJ|hwA?=6$*6RlTS)UZr|ABxcxd2qfQUe6a3Tu03W)#T9c~6$ZIzc#p zHjjWjNx4d;1lKA+b9CpDV3n6XGeh>L6_c8Jc4;}cSJmsktXgcxER^TnMDE9`PkB;e z(udIGoBs%MA>r)9BQFm(0{_j%zYuDF;DjP2&xSHc^<4{f@zsmk^LfWn%EOL zg7jjUHgVgS!CQZa(BbkB!?`UdXhI@NsZ4crNkrI~rl?g7JYq-pt)tyo`|j?V!OXS#ppc_nHxaRiw49H~Kq@3N{ zg1%Pw!d$>n$0Q+#*WQW!@u_!)!sI3jHT3Nw;ftu%EfDtKrB0G&M!D1_Xy6NId{qNq zFLm>~R;igL)D_6%+Gr?fkc@RTzU}6bFn({cgG8G#Vu$jsH4p9HdH!t! zq*LmP*DJGbb8dQa6xWvKZ)z8HXcKknP;$K|e$cdh*-l*8Er`xtEjQ*`L9XQd+P+o| z!JEmXJ?+?a=$0pl*w<2RY#t&|s#x){e2Tq^;>>zB*d2L-Y$;EdOVPMtg^U{y`|w_y zI?V?a;$E9to?CA&H2alII_*dOkwEdV$&H^o7SJQxIf&sP*4ncg#X(8x!BELil{^a3 z-SqvnFKT1%ejlG6Rf9!@Z{3tFCS{b8;rp^|tCr7|kAOxgdiku8?CJrdqY3E)o^qzh zC8wAz9KDNAye9q2ylX*epc@6Q@-^*N`=3{V6N~$Hot$>YJO_5I0)9@2!&=%W8>(Q_;__F9OmGma3d{f*Gl1mJCpf{gRF+YUmhW?!#$f+5ARSf4{i8CSG)M9v! zltEDf7&kzV9-&3N^R1@~L-8L2lppC~SRx>v%l_`)kvt_tV{nH$*7;{&aSsn0}QfDE~4;uhRK4c>8DX|BWyZMx&e0g!W%t zUT#ZZ6W8x_oM&q#;+WU@BN-2^R!8#pqE{%gOsq}K-jxqxPL$&f^ zUYyIgx(RR1uCC85@{Y8cD)Y}wGatk8vnMms5qu^7`h_?|8BbG>omGI9C%}a1C@{}@ zLaEU1mhunYVp_3ar6HLSUV1$?R(lVYGUsmI52MQ49kD(+p@)-VZ3GlBAAiw0-n?bEZ`k;zTRh zBIifdv*k-U9s7yvPyra<*a0z0J-8)L5Suf*MA1AHvEW<0DC*2;Doe3V`tZ_%_jRx( zx~^wZ2hMHF+#*sgKoD2xH|+gdlw{A*qX^lO(~u&jFA?PtO}rgr>fIwh<2`WLh*jhh3gl1hpGNJx*Dw>Z?KQ9uJK|w6u53?54vz?F~ zU~8{UaTUIL^6BVP#Ig-z*@U4h_qak6s+_6)EOsQ!^NZKE7)BOs^GrTAoAf-WQ*I)~ z>X8*Th{@9Mo0dqBCY1hx%sfhtlGN%>sk}^u!2Q~ih>v{OQObNwHg#EG}o7h8Cp~|oSV7rjC(S~i|(!W8&s8lA@-fwuI=3i z=3(HA0P$l$?ZlZ(N<)eIWfuGz3-&ebPaxC-*S1%MqJ4?FR~=ZA+|(0xD?^g5Wrs)5 zP0twW%jBXpeS6)LMMj-9pZCpf(Bk4*mkgJ?Ui}{UH7<^6!_jJ{FzKR-m(yn;pegM% zz18A@H|jw_yVY;-I;d`uDE(mu@se$<@wZObi+9iRb*Z`o>37y&--`d>lZ42JLx8AT zmw=e)3$e_mPQC^4*RZ6G_kbREKFm-~vP-7Y9{;#psU<+bWq`_j##f^T;bK;4XT}I( z?4x!g6;122?Jr64GJc8u9=HUG}$H8!+VUhgw@N`6)<>N*)8XJpz{ zwS2enIf{-OGqY`F`)U62;^lsGj|lj8BRG7(@XeqCXkz<)-Jw2tk@5=U^SRSqFpz#* zlOdcHCrT32TzZfiDuIDsI`@%CY0Nl%%=r5l_Kz|B9R@!NE8#EKJxo5G7)N7&0s7!= z)ML%u(X*G-cK#E*F7GFO-=|qF{kJ*4*Q>LG_gBUkkniPa@5{^(sjPI@aLYw@^*=v9J39X?-4rO16#julfe z%W6D%nk80{h7me!$sLyOsL)^N2v}*qi*V>$D_e~d1kG8&cYVC5Y}IxHq%PZsBk`VSz6aL`Zrp7H_;rN|4XQpJgy_#+{ESj?7dA{~B5_gjAMCsR1YA z)#q&soQu)01qgwIMhj8z!3?bb84apb1$R$JC7YE<&;;){SEDDW<^hIN7`Burw1h-u=6=?SDJ+1N2`|a`EZ{hm_&NmpD4NN+3Mj&clTbEyy zbXh``+tLa0(L%G65dDXz{W>Kbhh*;I7GW^b(w$Z=D_uP(1yqpqtwFg(y>wd2)G9%0 z5?5MF8CEg}bCo45tT(|Nsg!WkLYWw=?nlm+3HUmm&^EO~ZlA$@1r@K8X8*<%GKLgW z<=G4KXG0#FYup0T8-d^3&l#+0Jl2Yb>gJAfm7*YVC{o(xW^Dr&Y$x*6DXQPhF2B2+ zm1n3U$}VYopX|iy;x5XLDr7gedueN39hDvva?Cebot%R?Hf*dbpI35HnH>0R)o#86 zO$D>++!-COHY1rP!%m~v5{oeZpf#AVjGBOS_HM}vsf8m9Z3X_2EF!F>inO!IroMP ztp#eJxrJS0F{3JCY>F$6j@@^?6)op5UCkAbN6;7qWW1)-?D%~%C4ljDM08qdel~a6 zvh;4cn(1!7+l9H`f1)!zJ}$N2Np5I+Z0QbbxpuT!JdB&Z9Fz;NyPa)&oWxFi9QMPL z;8iYp9`EuWpl7$b6lD{heElNx;Fay~CqhIB@mawujfoL7pg=CAQbG`c#B&eK=0)DL z3spz()Ojxlb(p3NmSs6>o}}5W3`%}K)MqZ7ti`zdQGMK{&V6Nl9bMl z`-S>Dw+$_gch2!PyQ;2-b4gq_PW6ta0@umd))p`BoBODJ5)2?W>GPJqfi#r4JekvO zNuwy$ZwW-Ro4I1DFEN2sBFt!PD8lf;Oe317P&km~)w|(`ECSUMl)kb-#u+kZC-Z(( zb4mF6nTp%>`T0_p$;wOD<3t%G6Ev;sX@&6P>AO)eSU|4zUEh|5SClgE9Wbgn5E{xTJO58BIj(LV)cpj{P zZdjguiQNbb@-Onea(}6(5sU4Q3e%C7$P;AkK1e5Yo@;sVf_0UrM_?P_38Ycc(PH!A zB)8I*^Fx3~qkroQ7*99l>ToadbGuSzx}s6y0){Z(?BG@UK7S4>dB@?_L!!bL)92b@ zxLr~fEjfSJBPoZu&Rr165u#l`C&($Vr%V+GZYB3Ym@;s~BMUU!GD}d3Gd3V6(%H~8 z1ds!0l!sP|TD9Q1B(q{EyFBqxyu5Ol>J{|WqnMA5i+V#N(NB9-T@W5MK5Q z5j+f7XuU4MlcnqU#E^xVHMhP2O!KQkFTObmAmr5RI&KqEJYeXw+_P|859yqXn*Ue* z>(nf7BA<&u)hN9=Xsps^Z=qpyDK~US8_(^$wB0xF^fn#Yt0@KWr?Q-}q?ECw@>^L+ zi|*o&D;Ei%{_Nhurr*-X8HKNO)#)aVeuk4*>*u#wozN;{Z$?PXCgxEX^irT7+6HR7 zLe-|lkHiX^=jbaphow$AD- zahfrEdTCI76}C&{pI(c(qCnmBjJ?JMSg1tv4WsvUO~;C93%ccpv^J^9Y%Zo>SH>E4 zdRZle9mZ-1ztUXTN7}N)>e=zzm-s%Uj{+rSxd3ZR_Jzuj@MfJ$2tGR`;- z#5&1x{`uW-++9!Awy9Nbd>m+_f@l4w(`0ci_XaM9;H;WTpkYNb2}GA!cI40g41#>ym_gC1g|qVq~4{Px9GBik#+8UU>jN zjB;{ssCX!FC5~JjqCUby2($i@G>klT(ci+S*#+I3&CHLo#rM~W&9`m+59U3=&EM}? zO=iA!qvLq*>}O9Jg?#_%z#q&0mq;`Y^fxwMWCu1-4egHD!9ULOe z&CV?%*9rd2SY1pD6!`Y}s8YzD1QM>!^bHglC=(N(ZcEKZC2s6IG2?TgeG7axKP;RK z>NB1$fGq`e^P-vQfS$YYF|3Zxjv>%T^c9kY^r?a5T67Yt&;#kVFruGSPdJ!Lgtzf; zVlK0;8V=0j=q?%2R?ivbdUFI#-x*_@@=q)}wFFt~v=<=(w z&(>OmzaYLS7dhIw7|IG^SnoUOtrQ%|C&(Fupy5%plieoZy@d?vhR4OMPQ+a4sNSe0 zgQ=k`h9FjKx|>;=!>k50mP9CKg)3{Fcrwq|k{^z)M2(%ecqU}SVge2* z`H`1E>x>X_X)L?BgJyzm6vUp7UjYb$bhaL)K$jH9K)ML`!Sy4{xq~}|c&_K=w4Dww z;1PPS#?*CgT_qJ|HJ{HWE)x|H{-=t~N_q&-edZCCctt;=i}||iEx6O5=pFXUWdG-n z2i&--AuWNJM+Qje-|(;$YkGc`Ku@MW5V8{9X)jyZ&Xv{In(s#wKO%(;y^g*y8`N_4Z>UY7YTeUhDj~|vibrJRT?tGJ^{(q+eVfE$0fvwf z!NDs*6qj)hyTx~u6;@verBDNp=wdy(`9#VR%l=^nsT*@?XI_*uqrclNXzC-X{ya#9JzmtZ%?RbB#00!%U<44-*Y>hQ$ zw&qi#yOZlQk6V9DMhuq|jgP5@IAr6>(r`;tiIXb12lZ8kUoS1*I_a})9B01}<>z(? zFY!{~z{&>OLwZmALxd?;a3iSmW?oKr$yTmazmIL=HNem&Ph;cd0%q~LeT1u+4zEIp zh(#!rIQ3c*x@>Q5TcZ44?#oFO(%(Bu*dPPi%g5b)IfMav`b^Lumwq+(A2FCj5eCW@ z=8FWa!JH$*CPYFNy~3=)8B8D?(I8xsYdXUB7-d3&nkyr}VWr@ib~tV;aL3zdh-n`O z24}_f)6YQ|{G*#aBb`ifU9OzbUS?(B#aoKkae zyr;lI`gAtotLv+%M~ZfewMAUZb6+oxdl9M#os@m)fR>1JttreER>FyZ=ah0o)HhR7 z{Obh9rYTVjPW0_d0&c9e^XhM4vjpY&(Hhx#>PoOp^>K(}lJd{dV$ff^D3CduR=3@m ziAHT1@ACTU9j#5k0h$85y3$M~XI^n7Qafujn5P;cs!(;a$s~pU>xa%POH_8>|%M?;EOR0QpQhwsJ9up-iNc*L5;Q|t_Yl_kY=FJJO zymE{*^qJ}{c}n6p)=?L$O1as7i1%@8!Qb%V{s9s-5U3>txei`qJT8mA`Mml@_a{(P z;aT?#O_5xSV<((%P&Stt?9XQ%*3#B{1`yrH?~mP)Q(23Ezs0k&;-2vZNtGBN^rL1$ zu53)|fn;Cu39^(?8B1rZm^D#7Gxf}8EGIxoeHJ%M>)fFU!D*BUf>CjoPqUqk>5UU6 z2;a9v+E^b)Kf8$LV0=N!y%${$6`PhzYu;Q*2h=@6^syGPCuEk|I)|ai|J}S!TxJSH z^)B*{$>Kf-afrEU%AD7F>2QL#z8q=&rD0t68is(EwOPU{YpAZ0B&C_*O3MW1 z$GgQ!-L;yAo#VUOxgy4kbv<>*!deJc9DAzG$7Dbl?B}Y6(miDCKZY%=S4-!G zFAhlbLFc!3u}nP^kKPbR40g2;EU&>Jx7NtD2M3uaq_xQ7o4Y9vYz(M-rYARzvgfq> zgZ&4xJE7nassUq}SU+9c48&IR{Mf^$Ex@n_=_@$rC=-tK0LF;|ZSi8Yip9!=G?e?e z^%s7qDm&e~&NUF&YGt|w@W^a1X z6V-;qc){45=yo`||CZKB)Lg&2RiF!d5SRU}MSB%6@)uwc&dbRMg|)oXtB@T8aXc&c zB6-?3NE$+6HfIWVDHbW!@T}UU>a0T|Gd5x?7OV8}!VApc_sIio>CAd_IZ&P=_I9?~F5pdd!B zn_!{Ao*_XDIo(ZR)ktO_@#FYCK$CYdeOkaO4j)s@N#{IAH+uiOidVh*yLLY|Gfc7k z!+v*FR=tDO?rO>$_&DQ*?8m(ugtw9P@(R`xe7?CQX}@1^+w{CR_zq5tqLKXH#(sAn z9M}*P${mJbIF3(%R&HrFJd=50S=}FGh)ZN6pQJzI(fSIDOCU5n7GppzlcWi<%os@K zp11z`1rD0t(3wJEc~|)0MoQxuG4_n$w}@`ejrkOz6tViq{Gtn}6%p)s_#_FSXmz6n zObICT91k0noJ*IW?XSe7F+zh-k}haGkw9c3G@b&*vG40&SiFum{`I$M_@=TVWK%^A zMZ4TGnrt~wX{J!G*DW^-1g$2>#!G-S#OeuH3W~TNDo_I+y%|g^t`I>*-wGQ>4fQ9u zSD>}&7~>49+7N}Qn(j+O6#IIVO~ton4ZhuL#^^z7W z2&foAXzZPl{yP-=7C1N+gHP*k9mNDcnHT0^;kMAj67cLk6-hCL>4nATm!n%FLCn+Qzu+X0jIVdIj#70x)T{2bWU{YCTDHt{x zKt`PqOVo!UJw$O`Y<)qt^1^8xDBFx-;%tDLmvz7Q74)N_Q zn{lj^!(|Q_a9AeGAO;Op*qxM@8_#n_thBf+R_ZU+oNW}yPM4W(-q#YEm(f+Jso`j7 zq<~e^78W+g{uA)*lo9Ad)5S*>Vf^{p;Ga(BPVVowvABpA;ujbZR3u85GP=^>eVq&e zDXf^kkN}cLMsC!wCer zUFKyKxsrTiS~>8hYO*)#OCX^f)jdd{wx+zjy9Aawf_6hIUsV;rR^L=K0(c{6&WkP*-E;*JWSV;CEFb@&qOW>tUR8nCJ}cui##kA(0x3VtTCe2r*(IAiAaYyM)6i z_CGp^S7>Jpd^8uuw1#*23{9-x9g<|mS+##z6SrvhtJC7C>?ZBqY-3h`T>E+Fb&qLM z>$xwTr2X=|9d9$^ZFh0=t;+4-mGwXN=^sSoZH^JA*6^1H?J!uTJ70ThtG+s9l(L+5 zWDCFf^ER@3g8x4%De_?vx1i7sMqkiBtWaL4`rl|` zAp9N*D*62Q=k+YMVyfl&xunx`X=fKnY{bA`2dpA0V0|gU0AVi8}WY`8*dOpLeY!wEfOE43_!WVR#MD;r$dB4&!(UHm^YX=5c z@U5(`axgF9k$?NDU$`&D&i6$sNHYhI3_LsfgSnX}M`vMVSFK@qrNuKshC=z!lpssS z*k!q#F>1iF@o&Ylp<_Tg_I5znO#e2<|EQiCfRy<7S#NTHfn7`;R?5FEVXU;X;)vWxMp

6R<(D~i&WW1nF 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

F{XffQ)C4{M`XPj#H{Edt+4Rd;V9b;J+^> z>zAW!hd__lS{f_RCCMsiS6$lxm0<|M3b}*TQ&WSO0r%|=%rG=OJRDS95i?{=OrYuY z@vz+H&*KPt9pCLHYv`SvlF|ZfnE8TJ8wYK_p=E735Ue@YL0>ZacGN;6XwF z>=;oJ>!h^_DqAqC@m4C8F){0?m9!Gs0@Pbm@YYC?W5r5SGB-=dhT>8frFo?s9isE( z`@ZjSL3IP8h3EH@{^k^dY?{I_8Q9yYRLgT>OUUWdtSbeNgg?P)iA*W+TRyS0Y3oUa7;^S#s-Y5NeKL@cLx~{HX{(X3K>BUoo!jRL#^P;8Z z&h&@pwEu!_ldbp5w7)v`3Q*m-O4&b7J32wV`olPUGH`CVY%j@CVcplKNvL*TrmXD; zS6Y{$2~njDT_EPiZq~lmG+Z=MV%WpE?R9myS#h_rmVw?{Cp`vZR$$O#)^rB$INv`~z6gi|hGNl>|^MO;RuX;i2 z*n)Mbbd{QntoKlb7N>_6B!wqkN^`eMwavgv#i|X zn;;wbpO#rX0_=$C{TNZ>O4&L^8#eFazHn{8e)2v>uVCZJ&CTd+BV4sE>C~8<+r9r= z!?OKTfk&~jE>U5c#G|86sP-oq=oz+A-b$jpWeE=GrYmw`*PnXqeq>SL!rHJZwQ{~v zH}==wl&g-psExa`jlHm*ytaqDtL?(lLd5tx+n+|hZ|Xa0si4!0OV!j%csY-Hfm8Lg zJs6KOPR%BvhKv zRhlu-=ZbzQ??vCT_&DVvVq4s-L?v4YatEA%N*e|F-7eopL9{9T_wzZ8ZGXR)G0;VC zo$l|ORgLWlBEkBsP1FPjNBZxw7?R2>X;-;W(JE8S^zLTxxG;lEAzl`E+vTEQ$t5$g z?uJg`_Ix4`mC?DWfg6yq-{$g5PdO{X=M7yGZc+w2Es4~u?h8u9>JFOS=u{`6wS3?q ziCDOP8L~8TMDe7O{RyRba&rAlD0e98Pus57NtTlxGlg52QnI(e8S-IkVXdEFo~X$+j?@9HX&F?KwsGKc9tJgMB6J zMCb_y&^KM9&AFHX_yb8{a+52aN8;~_C@~Ud&Jk*@(sRmTfz%W(kQ70oq)IoC(nPrm zM$s9@wbFJ#+~L|`C#FD2vu}^g+14^-n~#GXJ;eJDKc#m@Bp?JrO~a&s&bBT}#9+C} z1RLAi69;pY-f{lMzOL@hMt)v?`wOofaha)OWxVz^!CF(Hh7Q8L`80;6&h`XXJryM)uB5PS##hoIKJ;Qbt`H zM6D^*ScSi5DUkK5(r8+oE47Q5ZP>7D?Pa%R{%K?`bTZej6@jT!XmGr|Ku-g3rVeaCJB}%`~R|W}Y%2%;fsZwDz)PbubN|)*v&d?n2 zbm-cc&}m1>QYW7s!W-gbtV)30Cshpk;-NU!r3_#dlhDP-wRO8z0Y}e&@YBsI65zLfxa)c6Zquym$+x-PV1xe}BG+ zth2M1dAxq;U=<`KJ`KPF`UaQ_7NouH4*7xZkLWR?1_i%frggjq&+2>R%jX5(&YLHT zAq4+5H#hgveTI0BaBllTxI*ZUw29G!QlvVw5KKxiWq6?c?Lw=@hrv&JPd@+UIz1iU z|6%H^g4%4juKfVT9ZGO3?(SN=XmJQqT!Xt8cXu!D?(Q0-xJz+&*FW!n^vz^4Im=P9 z@3q(3>w=k!y}owXR_ke6c|^3n^!D?I2!Yr}8whTbITd~J1_w&6PK&V)A5Zws2XE#9 zGN^y&;)IJ)s)Pk$K`9LyIvsPaBDlDR@h`7!t^b9oupJZ1a6Aee)pToyYs110)S}CK zn`boy@`_L;WD@j^AsCx{`Kc;M8_rB|&7AgU02cm%_tM+&k@T16{qs9%C+1nF2zaZE zegX$@0EPTe`ruHt>{ob(8d{DXkdfUGqKiCM{j8QslpwykEv-yjQoviYlk-zciLt)H zF)Sp}3PBe!ypIA+wRPfBIZ|x2l1`y!DmE~6a4~h5GkJtlbEsKkq-Cktb>7sF;Kre< z=>3RZ%njA$zcGAUtau#>&M1Y2emG@tfInP5;Bo!QB3rh;k-edj9T^UTX_AR~dNQz0 z#~fF~99Prquy_PAxBE)dGV0`&Vq;?P@#SUpeE~!o(Nax?R70>H9nx@5^MHM{#{SSX zD>l<(bq8UO4|rs65$3Cn+{;7A*v`2)PTRgyuAJLUz}xx`0YShYx-8W5m*X+*-yN_c z)tj^y|A$V8_ZxsdpYqtxz)z1n&Yn@v7EXO3~LqX3&fD%*pv~bdqj-YRu){9uetYNmdRa0s$#9 z;x|Pc5s7A%Ce$UNV<50_4(<9Y?ES#znAWsOhGzRHMfPcY-yH-$dwA37*lN&q=$-Q` zp0!mMfT%n|ff6pGs?xRcPHXBXG3`I9zqE(h14{V?m=nn)N)jScl@D;0^*Z*<_&I#DwzrGh9_ah*L`*P~ov|m0cgB7#*V&3&TRCYCPAVdV728x>}FZx^4~@Nz+5P zy&5f;Ji&Y2ob;D@P}p|zFQK|p=rgK=bT>agKf}VpN^-+!{GRMk+pl(}@=07llv7bj z5Hg|_6cps-1|~NIAoh32{S=LX@IntkNVz+$$WToRnwmjDB7Ey&StC{=oa(b&Jp>SReYo2U_%6 zcsLA&Qu#zHa8QPP?~YYOU2s`xmV~@37Bxd12TxrSE3`7+&&)&3>|MmMhaeMAbyH8# z`t@@5YzB_AxP1i5$vW>nAsY1d)P+vn788Fv)nGrTqF+8`ZsKJS_yfH5&fH?$g1|C6>-@aP zyr6^_a^b}F%Gk<+WHJPXyM)6Lb_dBM?NqKGJvD{}HI6RtjV3h)MOE&-y)mK?2yyhZ zI$X~VKS$C`=ZMr@kA+9ynhUqtAA@V;Z_U3fs(#&|t7kF@HI$3(uRYA*%7s;(2(Sew z8l`3(fljcxqJQFXm;PRp`1W((BnFytB!CiA`6PY=xlgsl99WNUeTXD%eUi1uF zmmObFeS6hER-lSFbN*;ScL1q274ELrV;Oe1s;8HN#Cy%dM;)9+yurnTyJ;_<`P zj=e)5eI;GK+wNimRA0ArO0f0=G@{U4ISv=lg_V~>#f~OO>GTj@{{{)Qx{H~Uj@;6rcK0?_@D3Q|F-Ql9AnxQ z=J*!<_qaP=_}Fg6ZYUt12{OOdkP_f1d5QQvjgnd=&&8tPHk;1LRoU>mvH8>BaOa~h zSq@%P7)5`1?ej{!X{NTeTRt7nnoQ-ULSSxuWVSp#9d$?kYmEiFPEz`0#00geMk10i zb3~5EAo1$j>ed>6kKL_ju+d>p=~vLqlYn`iD~T-TYKd?w!(SIoWDd0I@;zn4qdd6+b*AR>j;|FN=L4NTy=U8 z2%LhXNVKccwvfMz6~s-L;RduC42F}S1hv7bl%dLUG9x0(svO}UWs6lO8@J=(TNDAy zAS;7XDu`e&{TlHVI$Y)MOOv3%r(}7`$5u4(dccwV&G-L$E@bS-((xI3C5%q4m=QS&+d81NOe zf|5?-_c8|h$#prc)p})GzBZ<~aNLy6_w>#k+RAvN3$y6Iq;}LvPol>ZCi$xNc)bqf zZo%Rn?*7`?_&g@gdvwKrKNu+U-nx7ZGJO6@bb?#Eyb(&a;`5LTvBa6|J^pL@@Alby zd*;ph*Xt^b*B1(w&MW9-Sr=X+gCF+it6Wz&aLC6HrV|A+hu@*V*2>BXqKHSJ$RlI{ zb}+E!{kpbALO=km8cY$}8wjOUIx#i%aU?CmUr%;N8V2kQ_$*4*_jKRwd0*>+J&miY zs|ycDY}5VtUr;TS)FS}c-6<3WYy?(`<^7lX^)08g&YS7a!m)Fxp+qcqwmy2ZnpGnb zgeaLBIoPj2;+8G<>mT!EO>9im!V)*t@ImK?%7>iNbm5TwZQ>y8*iC?mq+xj>tGHSg zHN6s!+Mpdk+6noa)^j`uo>jQ}%Q5cfmU+(SYm4ud;71^j+>R3F?6-D~e6LyKjzoLC zlIa9}-a!!qONie`$G za43bjv`b;GtztxJWNi6hHgkk~3+;>6FBfjVcAf6OG(|3vC1d#x`4&l|gdlTg?#cwh zMbEWGxKZ$XVH-(5MqN>~D7EW@v+4_S1UPYy{Tgx10rAJSsSbQwWqomRbyNSrpqDykYwQUig4Ay`?xKMnW;)Wuk>oU@!SZ@F~vxDo+( zj-#feTlEy^WIe);tGHJwk?d#;e*WQ-d91MRbWS))k-!%dUK50_9QmDQj+RO`{VSNF zP@Uo4Dj8D#VBx~w^&O^9%05zji8`%~?bcmz`w@Sg5XQgv1J?$a>ZNJHWelPwV>IzC2NQ#fMU5jWHj`A)YFVVpHKXrTgJR zn>l|{L;{G@0ZaQFd_Hjlk-YD<@-${Ofkn=TAg^a#{_+O;;tuNaiU*LJi;207gqz-=!TCyG2!gDt$uA!hJL5r7Clf#8JpM| zE6Z6q1eho&!vD?|&ZRT!cE2S0J?Ad@-msAR-Ze0gel~D8N1JyG*0k4_cT5EI=f}pn zmomaOKK9%EOoIl#j$}6Hf`GzO*u`4PqtN>KK|3Vs&lcEo;UF!M1E6rXH0K0q0WZvV zDEOraC^WFN_o)jcEqlP;SG#JFV66aTh87N(#m~8?(S70{aJTvX@vqnOj~T{~GR!Rb zbPt~5Q!S!n`Nb&wnp6uIV@2T{fJfVfBprdTU_J^RumqlG?ln z8+2#*0|4-q4ZOYa_A>=92#rL01h~&()*oTU-*uQ*eWTzoRxrc&`$P8m`4Kj`evNFl z$JpAzEuAFvorj)b{2B!65~Tl0ka`7%coD*R63`B|kB#}p^G5+8+1wCdWi8rl`8|D( z8n1X}X$>9YR1}?}cdS}+bZaHDDrNO=;kDcb-ZY*CKijytO*!7B4Q)2gO{!U$dyT{y zLvB7Mjoh_{h9)P&%eMuvrteFl9OP-&cC^6FWgWEg&DK+_qe`tS2MAtN<>H7r=WXg;ovWq}UC&Q|XB zR{r+!4E0kJ#y)<{!G$%9D(#wqpOHhxO&&II6;P`6%NR677t21b6-%DeJ{lDxM)yDv zEhk$C%*Eg5Kd-l(%v0y~2JuVy_5~cg@}=dbgo%wHK4n0|`Ip2rJ+oy)_KrydH-r#9 zkbctcpIhTBSXgF@EhC4_f!C(i@1#^LwZ!b&%6*Y&i&nK?S+|MQJ&VLjdJ_)Dli?~0 z;H#5PziL%|D-&b#3jq3BFSEZY(fmF-IG=XTqnO0ki<5)x-Pc#}Igp(5 zxx@0Yv!MA4RyDXH9kM?B-85Km@a88h&E$3|F#oCy{>EGA3STZJHmsH*yEvIam3UfmEvlxka zVBIiK-(ap-KhKPH)ZoYVb*9H0b>f<$?<)Rax!A@UZ=9i}+m)4r1KS7mSv6b%Y*BA?I z3-OY!udOXw>;;M^t)wHu`4n|D1pd{MwzR`PrmT!n(c0~j6N)yze-;`FKgTx*CMt1K z^%$b zxTDv#)c6@#6qfk?>8@+5%%|t+VWnd#XeeoE>e}5q=D<`|z8<4H*5g8dmfr+1Xh_Lc+v^(xMwo`)mcIZ^LhcCOR7u3diya&uZe=2CD2uY#A!Jl4Y?cc7yjV+b?VRaKLZy ztKUM`jHOnMr4VNZzN{O_-Pt?s(ISd06L72(2+W2QG!Mlz?NZCk7zd>}s4%))XZON@wI9Tq>L0Wz5ogLrap?67V3kOh| zDI!p!WP*v>Tg|r|8KPvtw zQDCvFf_?#o(O{6B1t+v8XsuQTEs#U#S?dtJvx9XHwX08vb+}tFL)57gb?xSv7+93? z`a+a-G^&n34{v3ZCu5ENgX8-~5dpPl1P1WKU-IyenH9QQO1oQ&d-7|)V4yT1OnROE z<=U3tW^G62e#1m=9OXiD%!ZYUJM{*uS5f@t#pqfHFVpLNk~ZM@I`|vS`MnlB2KC{I zoRCohZTX7Jo_{%rw!<(}Wak^6_xEbTET~aiK(BCde@piSt zo}mbvJah!L5$ot+xrlZtCMJFJ?bc2(0vxfht@ z%d%Y(p{4g>iw5843jRFx8ZS_I1`Y-7gfOL!p$#Zj7_XggY^(|+w8M07KX-MZi`-r2 zw%A;THNHSv{)(rnE9*m&67uE=6un_%S*sl`gO$NKlZVQLO@!a-B&ZIqG+X;!(t+i{ zjaD+?R76d@TZW0Y;~`%XJJV#gKERy9HYbiQlTi>+t;|?T=@-W;H9NL6ApmQ@EJp6a zMZ=nRv)AZ=FnS z#I-nbf2^epBVe8TjnZp%x|(al;N(rkUwheZcA9&+3!oG@1BbEwkxO|_n-J`?;byJm z2~aaOC9Am0)To2U5p>H$BLR8!xJCNU|I@^N{H6OB8`JBlmxARP)A1knb$c~Iz^NE- zETNaNfXORx=iy?CJ9K$+DCQ_OpOw z*?ZC1lmdl{nF4%ZAwjSN9i|K{p*MG8Q+=yqrgkn-0r!&n_S9h4mD+Hml4j3Czr0Zp z8Osm#w0t@lm3=Cin}nb9DhZ9s0p|PCm4`X{U||lx=l97q5#IOtNBy@;rO&yo_w6=6 z-jCJDG4rizS^kw2g72~;E@p#|lq0IbVU!R8!8!VMp{nVvx%IKRmG+1n#Kp>qw!t<* zEki0+;{s=xg+xqfOhqg(d^=`O?8CJ^aiJd!$_J>(d3fpBY8orrscQJ8H9Kb!lH;=OEG2!t zYvP%O8;yJq(pEW_*IAJmXv+~3q!Y+zVk+nU0BKKL=a0J4?1zo66=CP+RwTo;BDAKr0wc>I z%Z`J8WD7M0OHzjp{cBkH#w>?KlHmLv8Ie@(q01&vED=>9;nXCdjzmHK#T@(`ul{yK zKwhmFRD(OhGd7E`n#Ek|yqvzCA4Y4~liPG>OK|S~(0|;i?oB1omXo8G<1|RJ>mhE)a5DoCg_ad3ky5?d|#b z-zu%>=;@(tJUsX;oSdBI=H~jMg+7EK(latTdgz}$#G^5s$HK?QCnhGw#tt0Wb3S=@HOTy~X z7|_>Ev#QQ5ElabYBe4BJ1nNU=+J#%o4_(WbLYPq#o41hOqs14I?T^NED3Exw5b!O; z5mAiFYnF_~u-_egnu%F<5Sz#2Gm8q#rKE{+{;l+{^xOf)rcRvrN0Jm#kuZ`Z6Y>x0Tn^I z7L}1LbF_ln!yV5;i-x%gm6;i3u^AE^E^cite2s(BGws*pyK-T^xrV!$fxBU&hxyK`9>zC8%=(}IH8L9i6B{>4 zioe0Nz&{81tNw=1FU}gyspb-1M#QahVyu<(gpd$(aYCo^Y(Xg@k84d^XzSYiWN#1| zE5)Xcnx592UNK=!Rp!r)aeiKsIj)prNTZ^}8DQB8GxJed16uF!PZq2=6T+}`oEMu! zY59=1q%r!Rp&T2LF|M*aQ6W=^x3NVU3r2pj#IVOPV?ncuy zHz2?VI48pvRJ(!1xr5+gmrznQ)!?nMNUha-Npj7iV;X2^f*6aTLVl_vr&J7?1n$cs z+JvmmsVfmvu(g7}C99QdT2)a;F46r&@?iHDW#-h1LdylW?K>JKC)oIg4h4pD2?96| zD;5x>I`eEQMr7GBTZx4`^y@Z{_Zm$<;lqWYcPh ztbnOF`;j;>alkQOJ7p5O#B*?*r42}>PI8bKSI9}~)2yW;@Si1H$ZTSzcJ$B2IQxx( z8vqNX-6@dMyV<;94#PqYpWz6Lis0DqDN)5Pqb3zTWP_%Phf=Uyso$_*?F8Zj`DVgK zYSz26&F_fMwQ311ZrPr}+YUgEJKQz^qh_otC|bg}fiqTrGyPBRH3FvspmCW}*l=yg zqv2FSoleWin#O}dwnV-&p)X&C14%kSpYK1BYh(R9+_YRym9M$b!J^|wJR}%-ouBGI z<*3i4C?3sD&SnsQk%*Gd6!5eU@qA8CQeYjCgagadcrtlxcW6jTx)ERDVR9*zw*Ke{ zcU>RPEZsj4bNa~dzf4j%{U@?%JYp9Y#KZD&t2sK0)hzP0$=jFumnhR?@BvYN3cTX; z%l)a4kkHoilv@nSo=23=`{}RG>sUV!Cjse`N?^}ZSy|cE(=bo>1m)J-0naa=`-RZk z@;84d3Af8lVgiDf3%8!vqr{iUo|lRq2yHMF@ptZ@6c#(_d;p@EN*QNXqhv5XFzV>G zlSw*5v0#>=kUv<9342UZ5HWVZn0+x7uxLiDIUFhw;ekUGX|wj4eG$B@A=BMtiWZ#ZJ#0iS+{`92Z8e)K zg0Lyh!KnnWRK)2PR(N6x9l3X7oGf8}>mPHgALGpIk5)$3@$Mw=CKd;)XI)j(pEDm2+c zF9}u3J}r2=!fZ)PAo)_2GgsBU3*$ZJp(lc>o{e~+#y73}$456+T9@c(buO;`1g=+RGUbFXR9 z$Gx+`{@K}e!3-7&Mr<%Adzm8hjCJq83Z8f-P;wx()(9-oD$7K(c_mVJ#LF6%Os3hV{+h9ncmK-G(Apt@8|4(Q^YO?xG zGr{BI{dB-y8CO?V2sXjd(GgPY%x-3Q6ELzn&`5=%ZDee`yR*})Ldn6=(AwHsR~Ikw z?Ox>tF0{13Se2szS&kvfg&LlM>06m@^&;eSNt`!i)T)Iy%e4e-=(c*MSTScKUil)) z?PAg$D5xTSRO1@}%vs(&hdhQ-EDw|p9S_|x3Xl5pgRc4I6*D)X=}@f7Gz=xkDi)ak z*_;0;ydKU_7TaWHb0~xn5$lb{aG;Y^f+#G)6yc{_CV$|t(Dn|fW; z=}uIYv8l(%HP6nl%+0~YJ^s61F?v-cs*^{g5v)?>HsNG@@5j{Q_n+omKN?lSr3Mq( z4v&p-P$kQ>ulZod%Cr%mo{oZo!p99rvnD<)`~F*65^Heb*yxh?Lm$}c!Nl7O zEzN(od=B=QEHZsJ4Y?*r`44soK%`dT2(dq%l9AwyE@l9XI|sX7WWxcl$7v$axq2GE z2-Z;I53|)_LMGr0U5Txe3q+NXweAV`fd#I>MvfBRo!`qK@Si2a_)=mVupdSnW2-b{=`25jO87N)h_1HY2$h^`l^5?L7tfvb`h?@ zeKvKtrZF^f+lqzLAU(4Na6S*=3~bL2WXP%{7u>abedewU)dC}jUlHm8mpOLO@G1s1 zlCpN-_FLE_l#@DIi|PQ(y%2}wnt|Ceu;U^CqvId=-X(|ImG=i<@D*g^AtoSzJ8gG< z`SR$Rts~r|-!`6c7?Do237se~W)NLfSAxHe-)s;(m9AwNAJBljKiS;l--8)&in&*@(bL>p2lB9v>Xr((8 zh%ssijV55wD1%Wr-zVFo9P&pOj(a7l^k4`vz4vj#=6lOy+9aMUA`C9)GxC99xzTNY zp1iC4PwXkoKr6#D3=qt%cHf;A^NIK`c+INL!&Sn?1uRl}^tA*J}oV*GInjHAF>@(u003b?&&rA;ATi2WS=Z*QA*u`1Wsdwws}^%c$2)U(K-c?&fJ zjYWB99?}b2qWN)u5nohKYWJC{&V-H`LZQxN*x`B(baQ(g7~N(&f<1#4+{%wjix{0jXUc)|eGl|2265k+yFo@PV{Y7DD!A!9QOjgPIbPye{MvHA5 z;hu$hKwuo-%+Jv#$iEzy=y@?;H&5D?c7MZhZ`W0yfKNjwJCj_9udJk=S_qb7H^Dg$ zsHaqfib*GI%fR^&Eub8ppK{rCC(`|Rw$<~tu6o)1qB{X^w@2gsH_ox0i76r}{8CZr z&$j*EQtX2FWmEs9baQ78_m%C(gn&nbotcs}H>mjDdRhTb)^hcP%7sIZtv7l4h6?7dXx49m-^LWbvosMnOJ^aoj+NnweV(H}#zLKn+~R>jj5 ztz+!!d+@TVCtV2e?O-D=QRx>WKXg-&>!VoSyTK7zYbY9AppJf^%EkS1bBoaHt3b!A zfCgdzu9Kw`M`B_DQ?!dE;#2GB99UIta~#E~-7l}jGDeMSO`l&SaYgm_<>(+6&B**< zZh5cY-#7rDT;cX!Sd%`i z?CU=(i8>c7-E7`=DGc?o$jUuQ&0Dtld>I?f18012+0;Bh+l1~maD9JwC6dN5Rq3!w zDuw+jd1BsE;|Gno_SyW3IN&nH4cyXozOm_Q_qwqre3|0s<;C#zzv=6~!0c^b3!cW7 zmWGxNY$PA@71`x}I!DsvA}Wx-r~#pT_zFxtZtRFtsXb!mVUnF_!mNP;fa>+b34>Feuj zV`C#NEv%dY%QCS>$DGvwuw>25 zA#E9Jbx>B5u&A=A=rCV5 zKV0?`&4W}e(7~$8qlG4W6Ng7q7O!kX0oJaR%#*Up87(HyD!v@mu!?|D={ei3gkTel zh*W>H%Rgy>1gWt)<7|TlygMp3*G3`FS|PJPusrJ|(v}fNRgnaxvFFxd7k0xgJcAFM zLw8&wcU(hg=xS3_ss{kizI-+tFB@CUtZ#`KYBIOSyy|cb$*tOaxrH(JQ#C3+9>-t zq!ClfcgP8#jwK7~Zd!?r0c{0peEVg94yg8lbNO*Zt}A*NUl{J;(uYe;Gpnl!YpM&z zG^#_5U6)6t@vz&5rP9GdqX7w+l?9C#22c_Jw)#&#KSUaw4ejel-@M;*3Sy-%CrprXK zl711To`EOz3t;sVT3?oRgwCp%Q)(iQ(@n_F)Jgn&B`5ZeF$d3Fmwjy27L0=L!t^`G zh$<=wD$0l|YO<-D9F_nWsVisAE8b|2F>4UP`6oGH=H;(htU@H-9$a)EG^ACx!?$4N zKkf%OpUv%NC&EdVNZU)d{29*Dzc6(kOfgKg0^7`)TQ;{|S!=!{DY$PZ8A528(+xhp z)5KSKE8f`v;!1I3a)vKRT{8Ixh^D8EAG0q9>mfm7J2Q(szd07QIGiuOM)nw7uZDFI zBgr7IVZymNhAZxeEv9GQPE7JuTLd%xVdF7R%WOD{4n`%{qdk;e_?E6gxfEUmj8~(m zZQVZC>%l=K7Bhek(l8Bip{~HZo6+qoS6GInVAe9{#;fvV4THtcdv-IV0l5~;;$2k_ z8S_PvI2sTDI+iYa6E69crQxe=zd(_?eJkz2xSB;eaOe+%2^nyS1W9!b{l*EBf{Anv z(=cDOm;|EcFZ5fTL$)??@0$53>@NyS`&)HKpLY-D9U^VwSexV8oenRwn9{9qKZRb4 zUvIKMoo*W_U}7Y?*Lu1y;a~CQ^H z|A-OXhG@QnNpwHo$8na6C~I5OS=%gM1d?om7fPE8DXv@U<=1E(=_#L{U}QWW?xGHL za64kmTcC7_Pk7-6wc9n^a@6kA-Qnj)m{ETZ_(OEwv2{)w76TOpMetzlu_5;zWM+17 zw$f13^YOyd^E@eC7hdCYZvDUXvA|^~%!A|m%e@zlcT7Db#rZ$SG{@`0?&H9YB6PmdYgNUTLP-~sVOP^3)r9M=%WMnpgZuteij2)kd_MI$ z`+U%K)p~kj^63wu@-s~FzMx`n{khumZ?lqf(9cM}$kb%+`Eg8of66&Y%BgdgF;`Rw z7qqZ7)KFFQFm?1$*!@-$Tvz2=++vL`wkBskXm11i1T;a>dC9tcu^JosDR=btaREhb z4u#&^_ik`yVE$-ePU_tJX7OMATs`*k>KaUiSC2-CE->H4E#daIo?|uv`0$DbQ=vIIn(F{vvqV)EONQ?$B`Iw+h(jHbCbJ>mvQTz`J_48o_Ss2S{IGaE zut$yn@}H)*XQa{+BX<#V2n{MbTD!@K6XsPlbVSNfYb-1#X(S~lB?G?VG&VMIar4A? z@TY2OS|B%ai+Q03&#tew*ES|)x?Rkt&p99>+Sm+EDnOifl3arG6DlS7SmZ&mK_jlT z38W⁡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�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ⅇ?72LO2wq(ruR6)e=c-H##qv*$j71tg#4C##m)|s4SrA?7CVToXbMk(Gg8|KW z@AeuVIi1)^4lK!>L)>A#^p{xv$BDt;(hX=BFhJ+-%tbH`7?-@j_-3 z?yp}2o?|@E;thN@1Rt{0M34TYRDKckT5K~N$_d$A?AU#`7`#zp^oyX{Nb>1jXg3UCfz zt>N#UytxFqvQv{K9svdQ~O5wuij}d+CpS0|C`7W8Xa~&cpN+h(3=e284 zp*KZSq+WSWLJ7^fmQ`b{$i^9xS{2K>7b=R>ttJa#5t~zS+`CN?P^od{86B&@Y?kaI z5y6TDkA%@C8eR0@L91NeaI+r+!Cl~F)%Xv@u|IdKcDM)|*t3InBAD^NMJPMy_W^?@ zvaegDZo+-&(R&B?!oidlh8`%W#ZqDF?{bH*`@&kyJ?JbLN#YTV-dFB8dLxh)`%z@Z z^KrJ9$2t*qifOcbEI`>@9L#c{d;YAOzYuzq9Jtyqmr%9h9zwzRNCX2iseqI*T6ot@oQmijh)RbeZt65>7Q{{T0)0bsfgsgAyFcAU!233w3%S+Z<4i<|1zKw=J3WX@}b?^kR!c z<43ok418e5yyHz3vS2k2onBhr7@CG;(<(VS;v16nK+_Jp5Nl+_#l`m#VhYN@zj=)b zCsJipp}5j!DctOf)%bw9D)4p({5r{^ZI3jHR%fgEY)_fvp&q|(#zBDiq1U7_4OUnH z;BaJ1kDVm%v?gh+D3brGYG5Yhqd`Q%7*j|+P=Ue*Vo2?P;L1i+k;mOybZp@vk7}uX zd>VkoNYBc84HD(jlUPc|14p-0AmFOV?02p>tXz00mbY|w|L!^~47_R{cl+`2W!n=7 zXxQ`J3&s%rxnG(j`g@vtEIh~qV#!BJ+}w( z=?c;R&t_ke!^Ht(p6pJ~ZPw*CuO$*)poK0J-F#l(jr6W5 zx*20W|G{t75ca%wzm+x=HbKt=T3a@tX#GK%%Wk$C>UtQTG2*fR9d=i#ED*3Bix8jV zHN9P~)zBTXaG95B`t$X{hLs4onP|=C`2r>!52_{}3xr_~SOg?t4jZrx6=1S}$Hc9- zfD2Oo`rfxq+OMX(KT1%}>bfl^FY`h|f0TA)tYqL`|iofpo5zIw~)VWWUDOZbX1 zn!=_7`_4l32K`sOOX;@~D&0|XluiSE5c@N1Y>^n&kbA#1nbcr~`ltFD93-eT^`w4T zhX1#Dn6c(|={nZa*k4!gvj&}Y4ZR${t!C~~6*G+dZCJkn(%+*+Kl*pm$ceU-A-aCr0%XdN%atC zR2r8z?F8w{tK5jQ3CT`bKR#Zoi#C}D3^YouPCe|szLrE6j> zM(8#VuY>9j6P*Hg`%9VerONovWS1uou<>cCbh0ZW>ld#lPyRg?Ixkftepj?JLt&FvcTAX*AC z;Ha%zo^PKdUHW*M`D)H4ndhOc@nW-!44U9$hHIoOltFG9V%tr&|E|c_F&Yb*e6;53 z!)4AQ{P}o0z>oVDMc6tYa$~yd-7C72*RI8y(`$}WyA@?bK`_SVepG~OYp6eGCA778 zJ=w5o#}!sQ7{W|#kSdUBijcd&sUQn&8%sk1KBmC)Cz%6tMbl~VKH*XEYl_QB5HcrY z>R$48P7FwJ4BbV#IJ+8qSW=e^u<4$XQ2yY~%!xjUTK35;)>9f@)=PcF_d|wEB@JKn zJ$*>Tm3j;=1L6)7Mjgb_PLMJ~Q3nkE4Ky~ZYip%%Gcz+2AmL^4D;$;ZBfB1xMs{}g z@zL+A^~0(L282GVbp{VS2#I3<{yYFpk^n$(RW4mjD8q9HwA(z4C7fFkp>ImOQ2irz z=`;rfNB>8H9{3!IZ}M~9GHD@qsS~y1SdVMAt)ruwck}f}=>93Nd%u1bzUeyak0$qb zn%O*4n)(LF$7^e99332ii8DP;HUV6}WS<}ln>t&xLc}ARwrjy_SZ@Ovb>hp@Z^>2$ zK{V?%4;&J7Yxyrx=?WIgR`PfPN);i2?z@sAC{e`0ku|9bkZpVU7PC~RNG_2GXU<7U zwIBOYtZ?~|e74C`z0}K>E8k*p(ZYTDjMrpU=FRG}S6%l(m)Ksw$+x{Z&+9O+5!Qk0@Bowy7UcVJWh+YBoHkJFdKA+2V6gR)1%Wla3VZ4C<-y^qeVZh=# zb&3sJzTYoil*WGZi&RKO0sqYwBH<8E6)IX`)7EemC_IkUM!R@k z&ttY+yO>$WfjPaK>Dbx~!JS5$66 z%#+^eiQ&lX7?G60PSlp20g36kzjvRQ&!5M`q&v%A7mKCmPoX_wn8k(S>r+*k%7!ne zz0p`GSBq&Wgk4tUZZy+F<#|g8f@{UI`8l#o8KrvJa;W_F2aZav9sBa`wp8|ACFpTY z%ovYbh0cx_PKP0YOEfY{L7q(%lOQZ8Hg_YL+x0+NK~(j6%qdarfYRPWmRXRFRxa}K z7-ysM@z@exNtemEuYfeCo$ZD#9f*eOP&!mO&osgkOr@P`bsAA^#6^zIS#+Wh&PDBj zm>*?e;BDu0z*E(d7jhY2=gh9oT6qSR&}KNPbkH|{aj;i_w)sFkrn0sj=7EnOCMM?3 zgJRPf8IQqaWw1}G`3BW6ig4JKuE}BYD{=}N3TuCOi9MMTeV7+tCJWJ!A-B{<>k4TE?q1;K#3A$$3OQtI66)NQLUapr<0zUsjI8Y zgo|vIsGK@UQ_qpmOkD`^16jqmNa+5Db_b}?#?HgI+J^4ShMt=tV*#0A%F&U@GGW8q zJfN-dU}I%vXRT#m1cI{~>l*4CU9}mt!5OlZs~5aN;i|4&1gx@YqfX7atbDSy`pSGg zD<>^IM(EMQHXVi}K9D7IcOhq8VpRhtzOg}q8QB5}z^!mb7czVz$?eX2$1-A~A#OA& zS|xzkkneD@7HDVdml={^#MOe=314jKljAVyL+NeXo!vw9re-VL9RS{1x@+0|Y8pH| zs>EL4J}7er+-|2gl;~zV_uB{|YI#tn?cNL=d=su;VN(@soqqRq);JaHLcwDtcibQq z0oTC`+ZiF}KL+&i1i|Zfr5GQN{-oI&bKGrjSjv78wVEEGu2doSo}JcM!jI2yPA<33 zQpVPX4D4fSPmzgpu4tO{-296By2enu0xB4^ZwYWt{4|AkPhfNk-`7&pJa)|STJqjB zo^|>v>DMpS`7dUtOaKmz^DGP-5;#x}waC`9pcGBhMJ598Nt8()OcVe z^36&A5W6pRe`@};gSGiNLiiB+S|D3l4K~)>P`7*$)W{z`YwFC|iyO6W7eE7&xt-SX z07g}(9?DaL`h;wpAgQ+!Q)YYM!5anNCu8oLqpw3Pnm1?OWY0TB1Z_5PuCf*trR*3r zx44S$FG=a4V9frfVP9&~C#h94ubWMhE1u}UN$BhVqKLXOa3Z2E1-H@Bfa>&It#n!2 z?UV#&hxdL{i>eKrAa#c|i;EDB4oYh=EMOyzhVOpbBK8V|aWNGb@X_}&(Z%uoRIh*h z++{?Tx8&^UPx{t`A52h!SLE4;51~6s$yY~1t~@xI>$4AwV&HWsHy3w9W%A6oNT3W5t_wYf{T!d$F~o>BC<&c3f}?D_fFaC2j6Bh|r0rQ=(Ib@mHp{p0k|xzEe- zj6PuG&K%~8<;!w86FD&>H*GCpXy;6=-NT<&3DzVPgl8IWZFicjOCT-z}yvSo+){)j^2xbZk94ZPY zI(DC?vLPlv<(Uz(xGY1sK2)9Y?L7q+^I{4NTU4T-K(BxDQvxJhA2y5?qco*!v1++y zY)lr+Enlpzj;IeA4I@pPw1R?D=yRjci{G(Xi5~g%T$6OZ%h{LRR+VKdFE3v|G|fy-)3qy>CD=$vp3lXyrJE8 zB|Epk))2lD++y4p@1WpoyL7dPS_xkKSWkN4*JtyWH?+&|ZuD%%)P1%0M9YQ^I1H9> za%blAd0wnN9kJhq{5F8ut}nC z9rW>nf6AKW5(Rz%ljZA56(@q4kmLHYXOx;LhsSCOPO`2Mp8@dSg-fZk2EH5BhRoP@ z_|*f3*;pPu>zWcKuFGc$zj^tB%WLj_-9N$CekQI=X2xtU78kiXllv3@^|mKWRPNL0 z2pVSdP<t3b}{l8iV%!y}`W?A*ai9>XE@_XRT787LF+^?e0R@UwuQW)Oi1)5WrCN zNg!Ueb+hcrOmvUvL8cGAaptl`-x%<0G4sBtQw^W2Sjovy)GGM4UBp?-a|#cwHgR^C z&&3t;3?Hl;fxAgwB1dWIWHrfKHEQHqu@_i5MC(dPit{IHwqsWe&d@ZSQ~~Qd8JAaV zTj_ilx)C37e92Bghb~kcqM|^i93>A$Pz-A)8f?*}O~`8Dt`nmFXndXe;q_~0uu3c? zo{nGzTa`y{C^nh@_%6EdEqVO9b!*#3ZT$l64Arn#B8h(gd0#eZ=AccQRmN#2#hEyf}gY)85llXhi-BS-CR9VeiH8@nm5wu-H;k4j27rCf#<|LsZ4&CjYc zC3+)F7$n~h!^~%-BWFDtAp@|i+v0=n*5K*(LQfD~N)I`6cFiemQpHlCTruv*HgJHQpk&0;fSu|<< z2ypzu&q_}(qDW5!Q>sm##prq+I-wka!MTU3(K|M+B^jco1LTM@C3kK-2WZkFpE;D~ zkAo;Pds({H=RQd!ttF{8Z#nDMPTKJHBLJRXzUd1=*7|%TiouE2J&|*fGI3Dl`Q?lLkKhYqmywEtIC>+CoesDpyB~?HYxMascujI6?1{ zY8KaWtg99bOSeOpYS_pZvK<(u0ndL&>Ne2EKRrBtlKcDCG>8ApMR?{b)aN5?na+aV zxPyORLYy;&M{jVy;Oz15UNun`tlvuv(#*=~lV122zZB~y6)Ub42r$#;yF6XI8C}jO zbpI|IT)=4zXA@E&TEV4Q7US*M3n5DdKjl>`CPIl>^U5h(AI`#2EQN@}NqxiizCb0l zMCpkxtS5CAY%Y(3DdJ}9Sbc)58|_)GsG$@NV0?L|N(_I=uExRokGn!iG4LbpN|AL0r)!!Sr=Y%Y*?uVBVvALgf@5<-zf^IZwt#%oGWp z$SxnikPpMf)FVrfap{xnm(*m*IQ;$!r@<6ny53&?DJaDdig^u_{1Gx^(+aMqON(6x zCCEx?D*;L7fW!k3`U1LIwp|5pUcR3lfLAnd0B=dXGWOUX-;f9)gnf1=Pt3~9Yyd)B zfK~1==+$Z^3|r&9c7InFVX1->3hTjgiej;vI&-(R_m5%ouPJ#`1zc$1D*)%3*(&A0k)%Ddld;aEc-uJe?_g_!KE( zBIXU*`| zdl=Cds4Bt7Eb+(sG{$M9?{;A8ba)UYT3@R(QQx`=Ru*?QS9Wk+TEyxh>08@)Uety7ee%f5V_cw!9xi6d#=}1ee0wIqF za;UFQ8q^_y$G3l@QD0XU#;?V$BKF9H@7JhRMa{O;DbHp|MJ*3va!}-0zIv4ufj_`U z7lEJO9c!bMvGfNQ`(@t|x!cqZ3F5YMB!4{C0HZ2Gy4w1?+ky&4UFcvU#|@EiuCX1E zUkO*RZ>7T$i^lQMqVlLIJ?sxsjNCu-U!rN5VYN7zN3pttkh9qNXzD9*;t~F8h&`kn z7@=|%mQOm(gX!dbgO z>Xy_9En!_<#88E(^k>zroFsPpB_$e4Ff{uEL#oREual0w;MQT-h~)Pxm)a@E zad$5b7Q)ie*u(lQ9HF>oJwX`A7Bc5ikw7;;0mjeaLJHITD&8H%DdG-W;h(7=8C`5s z_z5mY^j+|f!hjY3LV8zo6-C3b- zoHB;*lATH|XB`p6IBX1UTeV#6nSdH1Uq`(>sMCXr((-GuUa}4v`0HHkU4o@3ce(v* z%DKr)BAbbwb7-gyI^QG8$fUshtq6$CIv6xkr^1MwTE#v@7d7Cjx=?VJBa?+bVh^X< z8veF>l?*X`X790i@CnRhxxY}g=CitIlj+-@tJ1rCe?yi2z;|f_;_1RB#2U72DWVgJ zM``smH#Y*T84b_~<)p3^UnGiJcPY{`RZrDMESpczEqaCQ`Y2nh>}?@9(2nvPVyr!% zwiah(VU>E;|Mne}#n}#n5qvXio)_2G8G-5ZW$ATf$g2dY6b3EoSBBd$hIT~p_O1B{ zDcfPcpI}poD{(p4BM$ALnx}tHP2*u|XgL^;ALD{dJxHl$N3VT)jMzLLC~G3G)PZSZ z&}{zulLJ(@e361N!y!^~qe`8JDLqKeU~gXEP|wuZP*2OyR7*?GSj$kyc#1LmTZ=~- za@itR=4w_Zb%sR`S#@1m8Lpwr+R)MRsv9%mag}zdK>e&4pP}-!(sPMP{4Z z8ng@ziARaY5`(Z@>=k~%@RhEY+fiJBwXR61$b3+!1>LO3V=di!F#`pQxlRwI}XTYneOb&OuN|F(R;eR3~MA}XAU zW7s%z1N9ghb#Tv7@#!0^STO)R!@yHjpt6AaW)n-@Y~v`7TtbX4NwU%@l9*rW-J{MH z8s0N#4wm6#@yw3iQAD7*$Yxevx;!DwCwR+{yeYf3ns`3wMgzC-BV9XY>cSOIwh=sJ zYJxO(`lcw&09y1G+az}B`J2?`QF%Gb4su{tLL28SaQXtDud2Q7d=J0#*2F)MAKTks zG(H{_6SRJIBV%%^eC*rI$K?yo%VSkNmigOtfqbU*GqaU-y~S;aGmdS0S6b9S<$6W{ z&mO6mYtXN#x-?d$m)nVNH^q~~elCrL@qXK!QzhW12Va4L4QNmy^Vv!G{!R|&bBq}7 z1Hw_i8&$F$gujDdCNDc)RbCqfMRakiue>pEDvGRZCf{vR&UNy>bwefSOLU=1b4CWY zh%(abvkiZ0tp5NvxRJRaSS5!1qw4)1Z>uA+vZ~-R8a;FR> z%^KBKs!9RjgH~Lnn?8^gmsVlM4bpTYbb8v3abm>CQrI$i22AwdQt6WK%bG+avPc== z{=By*q_Z&O7y%AUZ6!FYGSU#+wC_1qmsD76u;!S%mTs5FhH31=z8Y%n_|?K&c{9(L zE2*<(OB=IZd@CzEyW32qFb)QyoqJ8z*5wVnTg3UAy=ao&mmSahr%dTs@E?l`}^vXEs>nsW-o&lZj2AO%<+h-*=; zFGk?n#ZmiPV5P0qLk%-*N@s1Fe{F*rwSF|6UxUJT>RR*Kbkk-Xmi6PAS|zNP850~L zC3dRxcrWqLHlu``EepCEbhS(ywJx1(^aRYF{L$EYsQ012cEGj$5^MU6wT)}bD~{iZ zc%V<0xk4&B1>H5eI^}#!q@tCuSq5!roBt1EUl|Z(*LJJGP(wFJhcpP%(k&niAxJCI z-7Vcnmm)QU3@D&5Nw>uGy?1V@e*dIa3U2RURQeooVK2`ELKkaMvV5is)DY<<@y5mR50X&a-OV z()}H-Q0RAuI-e||jIM3h7qjY|=RTEwzkcpkKFHE7=rXJR@C@(MfX!pStW7y5f!zd! zB>%67jG}k=?D2t2Ch7Upe^~Q_<+&20Fo;BG6+exBZME(uPmoXM9{rjA!qBa{M)YS6 zPAxOz+mfdbe;dkiA35_AIy^guCMTI!u~NHVh+AX&PA^x&Ih#jcV*2XYsn)YEVP9m! zbQs|;>?&F2CX@H=zq{91jt!_ahCBHTR$KB9*zcQqiqEB~c`Yxmm+PqN>1kVW%;(r| znJK+^xEmtinb{=vL94`D$uGZvyQ6k5TE|cu`$-wXxn7X)Kv~GclRo8RdHnjY4wPu< zO92Gq$rGZ6uqJ0!jM^g|KjNm3#6<;o2#(rkCGqBD;hus8$G_bDywGQA9c#yP_H3=5 z{Hooz_Y)c)&3<}jt}QV)y|p<|IgzcPkHF`*|4}%@Z)5wUwm*!eHf1S#uL?Tix!bS4 zG_N~P+H5%TfJRxQQw$A5-|VmTOUB93i4JY-JI|vqo^9poI*gT3X>lSayM(%$p5F!q z%4M5H=FDwzm0|avMSPgXHyUX>f&^*Z9`(`5$t;YUKD-ZJ!Nz@yc=D@a^7c>#y*F!V zdO%+&hCBSOJuJ?wB5??7KRiHLm0UZ>(Cqo@kv2M`B(^pAfRwc}35>fZSg)0Pk{^~g z8!*Mev8c~RwJ)s$%@f{DboXUS*0oAx?d`EXvyD>^FI*>itCGE9dR)>fBU4gtqG>qQ zUiR`zL(u$Tm$nBkOK&Z#Q)br0a0ZrA_mM&CV`i=_oQ=jKuTXJtR8CB1#=I5@pr-Vg zRn26nN6u`S4}`BCD-Y})H*UTRe7db+&qoH?v8t=BCHgToIT_A4?t)RkmBZgN0&`9UP3Y0;fOnO-i12JCNT6>}M&YSbn2>@@C%V zF)xL%)mFEDY+tUh)3@sPthp}pY>G8%G%mlxUTa8t^n*x4C*gj_6o1u2C>Q8K^4RHe zZ?x~`e3}@K+Vhx!NnOJITOm{LGfIBX<3u;BTt)<0a0df2(BvBcdQYhEf4Kof zBXdu&8p~K&D{GkDPU?2dRd4d(c1_Wmg`me;4X)kKw9m$Et(E1iig1J`n;lgWiSl#Y zxU{plNnGr#()Kx?CdJ$ zxb&(~tf*9`$Z^D4lhS@?G7}v)nM^o+>SvnnRI2Xtm8;T-h~wYCk28PAWM;Y+Zf90* z+cIZymz8pqm2#DRHZs)@*3mL)#Vh&jZtO$pslvfwOFC$4V*Ttj(=q#}a`{n~wIZ(P zU-lX+&vph?f2Mua(bd-+hb8FfK5r=ZzqyLhGB?qE&cZ?x={n@Il~z;Zfo;(us~=lC zC7e2?qc<_}b3%B6(*+hBRwZa1VEpE}0}Yj-ruOsahT6tI?#e#B?$fVZCB6mlLadIy zq4t2+$YlTfw0y0}PZP=%mA*xp=a+39>C^hm&qBpAwtvZZI52I0l+WO(77N!MsuR1O zL5#9}{n2Rr{oKQs*;A#c+_FHs%fQ$;TxFS0+EFANN~M!Yg=6;Sb+&9#kjkq^nq#Y8 z6N)*7Op2F;uWzID%Y@l#4ASQF<^vvOgsM2{Y~v_%sQ9L*iWxK6g{0p;r*!R`^ko_f z-dl}dJfFq;AwS^OE=ZMLtEbi#tG#5x^dz6}ea*v^pYJptR`pu{q`H*xwDs$Z<7lDv zvE4$+fk#-esaob7_xYfDiCFa0OdMBT)*hNaa@3e0qY)e|9A-@$Moet;wJNKW1+%`T zc_0~%zA_X+?XmRm5f7+EVM#^vzYmT{x&H-6c6R{N>Cw(kO7Jw9+PD69;bx!B zOkMFh-;%p6n)74bXDC))9km*eg)b1)y2o}0A&+V3xo%|Dm-|c z6Vq|TC9~92MuoF0XC*Ct_24ryE*NBX<6 zxJ;HJgWxdPw9;TAo<%#Y2glJhvK7Kl2do7?peqY~$QC0oJmp5uP%~hw#6M?ujGZl2 zpsll*8WDUn_N4dcT->Imr2H@Yk59U^a%>0OJuSqvwrbLLZmO1|CZ?!)@ld{!MhG!*Byx(DSZVpMQ{|N2N}d*ejm#F17w&pDGT80uDPc zydN`I9BD?$zs4(8Ee6A59ETp|h|e6{oV@JZocgAXt=0At=TjN!sh|5KOl?;XHUPRg3Nt0V-O=#1H~`<_0Gj|ozGYy}LTU78E%M>!9;@c0ao$E6dwNhXUY=umOj`D@}Q zQ<18$ZKrNOAoak#?B+6~VxOF>($UBeT(!=m{xMdcZ3UOtQANe)8+X!T)6vr6m{cZ# zM_`nUD*x??{@M63M^?j(%Q}kh#dIGYEr+;=_c?Pqv&1H&{DShw#Ncnz^lgQ68r7@b zWcdb_mxBdgNHn=i-1*aNY&Dcn`A$;o4q9E$k`*{Vem54kInT#>Or9NZ8}fBySmLa? zq4>Lrh|4}C#p)ZS)L!#w%EzonzTY&v;-9>{IKluu@hc{E@zZ)uAYtlpc*IKQIm;eN zD|PjSxR0Jj>^L>FSA%ABex3)U9GQP=+vZ5-w+jJ(_w-EPkdBwt)(Z7=^phHRsuy)` zya3$TNK}@D(&DKd_^I?ViZszABe$$PIG;gh&_=J*O0s|6 zYU2XG&5nrKN<-0AXf3ajuT(zgB3eyyS+kFw-2U?0b-=7fo1aPF-tzIUh8O7h2H4_n zkKrcXW7`6#%w&)VHBE1`2v+Pg62b@{{39K`DcC}u^-Hek%qoU&E+tHrtkP91Y z4$;J3J>@@_FZ+oYxCA)eXBeV3MdZoD%?FBrw3YzJ|Ju{q@lv|aEDuaM@xRCU8a#Ko z$sxiol3NKCkCm_44QOL7bdTUY;X2~lDtFhj5}SO#uWY5gwO{M*RQai{p`ojoKYjkP zeOt9W>v7<(RDJTdxjr3R>83}03PZe^l7926C0059zw2N3X^01m zy=RU8Ah5jpRVhKtqaUl7R`Pl^R4L1~Kl*jNG?mXFe5E*kcXN|o!x=>IQz-I?MVjKZ z!PzgX+*dI*4l}iYuS}TR0B#_rf--wQgEH0AM!*`}-D%&!nIrUNPF1&V1dSK$NuIOQ zTqEi7Wf#8G_4Ub*wyW(=5;qn^fcir)?E%`}8K<_JF;(8A_d(TT1PWbyJ3C!r1mjim zj7vw}pA{CFL(+cYq~n>)DXY?)8y2$vaPDeoz>?2`xt6f(QJR)+%rBbelX#ZlJLZ4H>pcq(z<(7PJ`NQ z_37()YC+%r!p9o2T?52MpuxY9MINIQ^sdUG`CJ2*0aE|TTlMID0f99MMOHu(l7sc? zo|8bsQ@O4P$1g4`y?Q1-7BjUD7buHQ=)0?ef`Zyx@uF>gfR5B*cpr0m^W#U+;4U$> zG1%LDwW2O&iD46gLPirmYclKQDKpESMpldQ*Se15|BWA2yO+gyLcqvhn8-F}CJi!_p!8*@S)nlz`W9G}GJxl&%NX$cW47~z|2b2Nt@domSHy**pR`<=qRLhga z2Ym9EyPv(J(BVi+M*XW;KsXmg*ioP`FDKf{ctCk-j38sLyJb(hlSLRu+YX$|)wsKV zocYBl|Grk|qiC9qip9zRPn+;(W4+76?Y7TG{-eL#GSm&emZiE#ki*oh=SJ>+z#^FNX*te+?gj`^k1dzK_9ckgPfRl@W{a(MkIE^= z(ehjTw$@ofQvCrzKAeYjgcohH664-0HA#6Sl|GU#05j?Ow_Qcc#d2(7#{(hz#LpME zoVm=PJCX?IN@&rLa&iO$dut$rod69S;2sM2`ADJUzaYIG=}-S}D}|xSjm&g@U}6o_ z0n2u|Dn-^<4AFc^C*(vm44R~_X0omif!ot#bDQkfm)H7mn1GOwkn_aU)azJn^G<0` zTwgp?44>3@o>aXwUsEzDWAYC0wdM)~v3}pr+rkh*e6KiCyA`}F9934*QGOC(^lQnq z$Tm{o#pm?B4EELNOKf(pdapk11_5dIn2=ddH7+yN;TMVw(8WrLANQTi>+CUTal|<` z`CT7&60>L;6AiFPRobx~LU%IEX7&ybnKyQgrt7nL>biQ!99>1`go9HaHq)voD1M?Qv*V=GQ$c^I zjxTDaOu9bYw%z@08|eXRA1mPIybVAn)QLqtO_I0+5=I7Q{2#v8R#&5i%6z;1tjh@* zrU-k6jEQy~KSTH_zF9st*^s7}PI5HyUV^ZBfD9;kWB(AOQ54Kl3;ZAiS#zAJ>*8AX z+Z_;CyJN3-`knwIsao*%_|Q|&wmY`G+a6I7NJfzTfhdPJT~v;)2}bM-m85a-+J~zXh|?KL@C%n zIeTV&fg!jB8Up4QF7PqFa?97hYz%)oWtIEpoV||~)KgL-{+Ss1m5XJ4O}s~3|DJ73 z7t82C8zKWiEeh6drqN>u%!>H>`T|D^HhyTh1>k~CZv8iXbt=ePVg|$2^3~z&)?kje<_v%>yNDC@cetX{#1e6m79AQ z#qHE`_7gdy2eM1cVJGz6JBQhNmr&#lfHrr25OV0;d7E(s?o>)v$I)QQpG1JyW78wZ zkT}Hs;1<%55MqL9C3-J>*4j1X8msLBefZwgUGU8Lufz$Pkb`fGaimD=opdC&Qq=n? zj{KT34{0jzwUYhnsQhR5E{qef_ zw|B=t&Kl=F4$tyF771WJ{c}P8Ue$fq+HhSd5Dj>vJdw*Z#nAJcNx7G+li1&D1!L^} zzYB~^sNf$Z5*d$3yH|)oRPT_!jfW;Hyrlnk?g)G$@5dGB@I#2oyPu@IS9z`CPFbP#ywcgP89CQhSEPyq)>kv=HIL+sIh4s9R4cXbJJ#Rb664z^;zRTY6)B^l%_ zVx*;I_J{%iXIBy=7+8^&IlQv^`tRo)1GkDYkVB;ha4PHFW}g$hC%a$LIQ1;~-yguc z4=@qDzwFbm2FXt2z=iEJvXa@Gof@8FG0C>Ef|Rqe^sOf)(#<0qC<0@8EdNy7VP7$VK4O#W#)` zSF~Q0j#|r0!ePpdpDQYyCQF~gT81V6?hX$R->IOsVrkI~i*95|Jdc#Qhb%N5Hh%zN zUS8AIYjCN3QT{WgXmGw>v#-lmMED5?Wf5bs;;{!;r{fw&-OH6=EO%}q^B@ca+}*34 ziauqUfQu!d_q5QzyvINC;2k!6i6hPSM{*Df$^sdPU^7QQK)3+u@fMiY2nSqyt1Y(W z5&>t3nUiB{BgwI8h2pv8$-N1JF&!qn6h9k?ox!0E7SX7-wjK084$ zhn-l2zyhmoI!x$ke^dh=3k%-R<3Pah#1q@3Zv^utDp84V$WPlbp%nvV zchVM5bd|tbkTuJ>69lBcU_>RLFCZUshcg0z@$EnfMa56_{s%&&7ft~!dEVH6w(bamDv5KaWS=PNo1=q z1va}mTF|w0=*6`;gTmE8g`FP*=y11=Z2yr`)}14&aHyq|KdUu7xts%f`! z$WvAB5!)xd3R*t1ZqvIfy~|Zf4@~sXo1d=|R=}q=od32vI?QvVSnI-jf^&9Fh(XH= zILR0x1n9ZqG}eyEXSWMX3CybWs3Ti|sk|2SmIH1MV=m685X z0?E>Sa!x;qLI@~}LhD~&bwwyMyM7m(l5Uhn-)+0QCHTSHeAwy5Zr2%xMWC1X5!6kJ z9VMVH>0Wa!Gj0;1iVL_i$s@3ATb{n8`v~Ya%xm>7E3=M&=%-R}Il8w4d_cvsfb=hT@4~8X#>-KOm?;uJPPiR}!8cR?kShbnb_*oEAx(G(ok z{q%_lGIMWGn-2&&oPHbEkdp8{wSIdH_HYW&t5g}hn^&yKBSoCNPLM#~`%_joe-*FW zGE#sC;CN)83<4dm2n#6o-jE|>)K`p>^b`$AQL@+p4H<)#2UcHSSblpK3{@9hBf(rb z(E15dgCxPEtl;HhDE_(fi&+q=ZjUqCW-Uq~B8x$&^hy{Fw0n(UpRlBmflhw>MO%Ob z?dyLtMJml3=THK~?X{gwfAp3Bz>)e1p zo&>$AuUus1IUO5;%Xu+Pj{W{EMli`Z`O0ZL3R$h<@Q4LYAT*PkMnJb28)zdh!>+d7Tong5H_HY)_=eYs z05)o&0e$1eB9K+6m}&wYYT(`91p*zi*rLkH%I4--+ZtJ-gG#AAdL8I}lnCZ-5Topr zuR^K_u0eqPGP4KVXy<;`S-vvHG4eoQI1_rgW^y}9Jp{NVm*sw@j0b$`4 zWx&P#`W))op4MLo?tW=0_OoHjs-A@_-nQ%G9&%IphXHMOAVkXz6NGIlo@yy5KsYW3 zIV(u|djLZ>296Ohc+}670P7h+bY$m45oFfPZtLGDH!&zlux)}P4p`;d0I90Hu8#b< ztg5C%kyi9AS1ZmHvG2VzdnczpNuG8KpaW3{#2do9mO28depgyRbBeC6fFvsD%3PFk z@$>l>DXs17#;pY?%u>h3#!wtkGpT)_mdbT-Q}7!RV~0QbNr0dTH|PK5lwk$~S~gz0T7|yB$b2B zP3aA%nOfW{HZeLcc*~Z1W1h|3MW2*&2e^8_#bAu(S0>HgUw_&6?pe>7oVjHoi=ew_ zII=Ya^iaS*q;Xel{w5!qrRYdqXY^hUHPx zAL1w9!mx;8SD?H=#ZqNyy*Z!h@nGA(pZWj?9qtFq(uvv)W-H#`cia@R-U9d{B>Vmq zbD`9B1QA@epx)_Bl~yMnnUY5KKdNVQNX1LS34=M#=1Ry=Xhp3hS2eU%Z z65o4+tOj963Qj|u(zf`%{Ans@cC_)fFZ=UU3u0GwxBDrxiKFEo?R)$3rPk+NZ1wDU zmv6q>^cs^vR~h+}cP69JwStjdniq?}^FBV>-UETu+l!}Zmw%AB0zc0F{$vO%btZ!j zzd3Xc=ZDlpkBskw(uWZ_0#AJdlb#+GgwJ=`#k)R!k29}?NJWrd0q=ww@0eA+e3aAL z<(A%=7$w5*yTXk^0#Ds}X}$e%M-(o|$+ewcTEiTCoRmqWyZ%0>AI(KP;O`#69JDbF z&ND#ooF|%br7wk4+{`w(^~F8-1L~6H8l>l66d_Z*==wXL6F~LED%;~H5FDtn8~WwX zG#Qf60}^RGJ|kK?s3oihM2Y*3ur5Fv6tVn9F_F&k*#F{iT>Z%&NYH&7F9z?|(YqPR z>1GhjcSYbn$w8%a&s1G`;I$M^zT6MQQqZmwEloiX~_d5Nj;%^lO_Yj3f`qQK=>E(b%K;_F0lm=M4q~$vT9+(01 zQ#6+Qvc!x{FRwU?4N_Isxa(#au~cRun*XH#&m?LpN4(4rCpZv%%sUKGF61gkjqdE_ zyoiJ>+u+pqe6P{>WeEu!5KAC^nE)Aj@e@#DVldp-q*D{zQU-DIVWKq`ztLAS$hybK z^G7>fZ3z57g0u@2>OkxJ)-3Ov43(*5N`m56RX5R*c*ru9^_Rl810`gLK?;#g zO>06M$B|_a6_7|uiGsJzp?Co+pJ~v#t`S;J3fQyc!uc6_-$rDvIxrzfn!^hRvIz+QlR`Fh-9_jKxBBf|(l2 z`fJa#;M3QHR)rfDPLJvxkja|2OFsdN^^y+If$~WTLO57TPrYXWtt#ffJE~J zM%=v}8Td%Be z`AFuEB25r4nUnUZXMpK7z{>LR-sZQCxQEQ8kWQ>INZMr&Z@^a|xf_5hOm9HOYoaE) zGK6~N)Jrq1=-4iYKlHhbnYP)1=r|9UWPwE!P`)F@cVk-Mz#YWy^8T@e1=MdC| zGQ3=m${D0>C?tFZwqY-m%)h>{{*KbHL+sp5s02(Dtbls@bfkFto%k_;*k#)nU1W10 zMT-qRuW>ZF$<@KMj}8k-m>3C;0Ihg62_vop*XH|Bd`XH@ZYq|FL+}j}lu+g%b9H&{ z%?}gnL1!6xEhyuF4;sBJn#Yzn)xD;mo`XnWd7J}ge*xXXJ!>Z|v;a}d;j9MoaxHcM zfgA?nE-2Ude^;1E5T);Dj=pyk89j2jk*gsKzek|>&Sk*Lj%ZRAEW|XozKu$K)Kqa!$}8J`w{}Uo3eIO z?rI)ZqG8V(n! zE72|<8%uwuQEt?rRT`=zyel^pG8{z9Q%{Z>+3?5MYwmeFUzte@)gVPosH?1c>v2;a zIUM7W)4~7-mPFviQsj6X@G*;=ht|Y}l;+K-s;GDd5DsL+4Zv*n`bj-HHjaKOU~ZmL zT6gvs;FboboAoQZ$>o~5^pMu$Wa9MFaJa}2dhZU@VpgDslgksQs<4PPyfl!Ji;e}D zyUmv8Y9NS@qrXscpiCY=^{q&)VSkSnbZt6#+0o9F=-#-GKNBWxW=Cs&TL^rvhF(R| zplAE6_9sE~%rKoFQN@v6_s))uA;Y6MD&(?&%KKRWC{gQbY6f_FKSWi@MdJ0$VrL2& zznwY+sqQXw=hA!PD39!qcu;SC;Nkqha}~DvdKzCQ*Ytpw=|O)8($&IRKdp0|Q@M5oL3umkJ-1|yHI2Yqko43lrdAYF zQP%D$r@9^1h6n5jp&xDLUiGc-iO+p7B1f`DUMYFUPqFG!f|heHerd zH^4Xju^au5j@QoM#(mrbp!&vptDNX>M_+kNim_0z6r^n3Rsrx)<`1#uqKZpF)!3rZk>(BRh8CN zIo|+=wl7OQIW@IHW`wG*$_ZnX%l)A=MuHzPzIEM2cPm{uov#oJqch_D)r zDb7)m8j*dIrXvV`M%ZldUXADXOhIqrqub?)DWmaLfF1H(AJl-oKosM3^pN4YO5RV{N%bYdh8rCQ& z$2)}9(jD)nIy?gpGUtEtokE`a*V@ac{~TE~5rSlJq035yrS|E3C4%Ff(Ws060E2^# zFV-2l0oeq){|v(!{a@#p(?~fyQ8c0yM?h3ETZKbgz^5{^0Ah{>_<4 z!MMNxA#WZp3-5$;j%b>$$<*Z;O%RL9WwZ+romf%J5Yr35?`45d8%;7gO zmcudBwzZ;dJL14G!lQ_wd6LYtMTh)_%Mm?q^+GG!$0Yn>t?QX~z;B|kHKfsRr>t&l zNW^VjZNL_pTJUouLOX&?fC#K_xIG_6u;bf0((GR&BcSBxh>rZy)bwGn^MpP@Nls@d z;Bqs4Q)?_Z$pOrHxy5I$koi+97rpIUsj~duc}5}F42P^JJf_DXScg2h`+~Kr4i zK4C3x?+0sRr*@Jp&43nX;~I;##FHw5IT=!M4&r0%fVc}b!6(y=DQ>)QSzvdO8y!M- zqM1v|sSA5k=xrY+qf5{6(crMi543GZF5=5kOkcE}p^`oz;)9TY1Vb6bqj1s@WXkgX zS9~_S)?1*7m)G{H`o$$|aLL0-05^Q3wHb#+{E*&llLewh{YKe{B8;&VsNQR<)hvpL z(UoFNB!xJ)S&P^(B*yb(#NvVHN`4o#ZM^uLsFdgpA|Bg1`3UAas(yuj)+n*JR55Ie zp+FrK`zYv2j*vMHyAz8h_NY@tS4W40Vo>Xa3?n;e4Tykl^XvW*E%9E6<2Up7YNfQj zIz)+JcUS^rv!?+oy8ysYAQKxAACc0^9#xF&k}+V%&bLCfdLy|2uny}}C}VM+qhYN* z5TImu>Nr580+N&3D*K0clTG!TpnL!x;Ip!gDErl>{Nl{_A%`c6EtgMv;q{Wg0U$az^-iBxDY=Uc^71ckZc%tq*(eA)%=_R|J1ny186{+$ z^k9iuS&Z}Pxj46L{V!cB<=I}yxh0ZD0b~su5;a)h1kWVH7@h&gbO3UP5h$V&TiV3% zXmXFlK(k^~2AEwWLhI8=QO|t93CW)!)B=4|(>5rlCXRZI^ij|`${ryqE(EweTDTkK z8ywcpgJ=Yi!&iv6&pclU3}kntp$!6z%dCK?PHkpA8v!cZ8%M7mQa$BC(Nt<2GM-it z^ww4hAtB*fVSUeiVSkGZMifp@wn2HFuFQ-b(qr}+ZnCT?PYBCeRoMN8Ef2#3&A-&p zFM(~IV-t)*?@56S85wIPlk$+(PWQ!xz-uPgu9dHr->HrrQxNH+1xf(TRgG06v@T=W zJO>r`K1o=ag3Ib2q@R2?5s{9J5-J7FEeO`iOc3dYfzEDN$X4D+B&{!|*kcY)kT_MZ zm>t796GFZF;rGb@hG6a1fK%4_co0^2K=utGr==r_!d2F9PPt$nmGM_PT)Gdc&= zh10}DlPFua0NT856@Qe}x|VKunASH-Y8t4fbO+952ES{^1|MMR;QG^8=OnOu$xMtn zDbz?`jdfEjH_%^~xkGKE4C!4Ma9Yzfx!eBE9U%MZWkY(QO)soXiM!ku;rrzUjmLMF z7MA?anIAuYJP?-1u2u3VjUmWuNT~O(W3x>icgvf=4H&x7b3!jaTQM&FC%gUnL zhdNNBFjEYrTYPY^#AILywzsc*gx1e|nQ*t@r{UEWFvtC>{pIW7I&}|FSM2Nsy!mm{ z?Dk`_IOrw{v3C3Spx7JR{~BCF`N7yRI19`ZgP;0&pDcUWM;kmOD#y%WytzYlz8wTxeN1ZdBrpj+uMQ$##SPvT& zIqpMSSWS`Y5>3v#clDrk(;=8(3R+gNCidnnC@7eyRcf(pm;q2-%x#}yY!o6U!eFF6 z#OoDaghD9YLVk>mlT+?$n2Y;`hf)GW@Oj}xMI=oR?K>ri-+!!8hFgIEKr zDo}m-70;1ch3Iww^v52OdT3IVJ<6?h59@!?nZayvYy6L5O^up7%hqkWoVxWdZ*GBP zz9t5Bi*_-bOx!!&=TCIylA_%mZFWkMw)?R454!+p&Ll9gO=YFUs@@Z9gTw`rU^@N+ z2eC`3RJU+}exjOLp!j;))DAIt$d`Bb&AGVNX`$SyEeqckdB*sCm>Dnb!L#Dv$VeEJ z)@d*>jJ1zQZ5EW5z-Na>w|{7e38xsIo|;M`S6c8?Ud{{`A7A@GXcJ&f6C)oY3<3^A z>E3AzFaOzb7h`v#zwvmZetyX)YP2KUIy!i?X|Mz)E1^U;s6eXA{pBeP>KTH4 z&+8=q$-Z=U*ry0D5vyV9SxnceYQ3rm1KkRf8?Za6hH)_zjAao$;)*{2Qw$}S$dO={ zf0p0qi9dT5tw(uqn=%42|KLVDc&Pf@ncs3ZmaCQEM82p4;?Nmo-4IC7MR4X1ZZ$33 z@T4V27*w}m3wB_&&-Lkfcz94zGcPs5UN3QaI|keD+-ua^ujHm^R{y)}5jMYc1R8Udcx?JICr*jYAKN~)s>@N}6J(q}Hs zGv6mM0Lxry%vj06>VXjDlnZ1#y0US5PtM&&BFm*xGJ` z1{INjYU|HUUP4=wS6NHlK|ZB=7waufTUK)IQb4AYhoG}DUszwatmmA@S3#X8pGCiT zk61q238bGup^Hd2dUG~O9Ou#$co*PZI#KX6DdxXPT~^a8UETb-(T2VCN$Yp!pS z)KdxfMdAEs@v2N1uYtSs>O*)nkoU_UWh-}d)sU9sHh3uWeLMm~wq$vc@*y*G`^`98 zM1%p7nlN;;G5~it8x|TQFrnP1Q76S0Z4h&Q`GuekJ>36rQKrm!QRUKKlB;jD?ee?R zawMu3w49t4M=tEF0Sg9grZ*xCfFwwqzgw$cIsD`Tvk$R4)@!>;tG)JzL9eak+^M_wJZ?Be(zke;$zm zP3U~Ym6#T>?UQD+Q$)Cyzo}qwD`4!bOyFWk0GMr$2@EG%^Q@{+n-O}X-7 zGvNfiCC>a8?=mi+D-X^egg6*JCp<1BaiOEBnFVxGlF^{Y!5c>j4>fCbP_V7bbI0Li z)Kq1TZZb1nx+*k`T6-d7N*q66)-(fj$ve8BS~F)F)sa{| zt#s|5X zocf;svf9$0gh5Lgk5>`#!VCPE8tfAGe&@SzR1g{?rLN%&#VrvAFi}dgs-}@&=+Kh&aH+f-mcy_4=|@LL5fSL#cB&aX;n35XiZ{>P z?)oz>q7aY_aS$MOu_eFBN1r&aPY6Ef$zLO8nFv`PlMOoS6z$rOnK^r-3C)QEXkiB+ z9gfY;;s`g@)TB8Q4DW#`(>_-$&szWY-FtrlM%)E89;l2gPWKRG-D~u-lV_5CdR08Y^ebBvG(# z0;m*@lz8_m9G5!7*(fD!WuBqmVI@!-VGj}Ik(l~U@QaD5X*8usO=xV7*_+=*pvrlMFf~b(O$+`VXYkj05iT^LCro84 zlZJ8k>UdCf2c8Ef(-GT*_$9g%KZ>*OUVqmlAt8lG;P%OfC@jTU~itr(@^ zrMNXRR+5qvA+DTUT=LNO!`P1zEG}4xk1oFvPfM0=2sNQ=W^$Km?iqp;At`)$WV3`r z(m^Mn3d>(qPdP!iLJvSG>hBRGa_A@sH>AW4cXo5hiNvE3`+*=6*=BQYDUPc}!uMwY z@yzsF0FUwTyER8vS?<4o4vM9{1t<>>SB@g2qBn38$Z{~;{Kc-X=jTl>#_WE6W>8Aa z(GXc;6L5H=I)So5H_xCtVuaf~apTP~!q!_G@H~bP4+Bp=EXWs( zCd3=SJT#X%n}35I?0U9awoL7|uSbEKt-L@fK`R5)PR050-AW?Z-bG+b7&e)!PPooE z)M?)8H8*Mc0vY`u;C$5t7r+`9N^O0Mhd|*s4Z4jqapK<4EQ@oUy?>S$KMt)ekLo8KalL zT9P@k_jSpccN+y_%|9{{oK124#F&(4B7lFKsj(w}E_|`QI*{^P56O$xw1Vd-utbTy zIUa;QGqYuS1Dgmb*!@+7QBEZ!!-TzOKCz`k*mm~Q5D|+{D=va%+o3>t`-d?uK3+>< z>&#aw7*8INQ2{b3l?748J-z$oc)xpBRr74Howf_S=<}-@!FUg=3Sqs|LTj`b!5jC9 z5HtBCt$1^X_Dr|2S3ycKW^jgdn_b$}$FmG_wiKZP6UUPk0pMU-@4!YQ`PCn}0{sVe2HH3 z;bAT=4>i_@l*e}rVg%VzwjCO81Abq`NrBVdl@XTBUCip zM`2=fu5|*~m8~4tc?byibDbkJ?Zq;KOhceshweP{#W1vAs9%E_sFDb^(MVip4*#sf2Iz< z!jBsAX9qd#BiqZdv%=y_?{D{`n+l=QkrKlfQhJ(?m)AJ~15`02)qsafe5krwfSWGyp{sxsrw-F1&#xZ4QNMi-o{9&^gb4L2ac`6ss{xV$-vNGq_5}~fI04AgwdTG4-U^Y9+&VQJXRbXA zKu7<~F25vVkg8^z83B1#53JfA@F#R?clPA-^ai&AM8no&Qwj9T7rnyHHjgd8Ju)I$ zTW@EFQ{xa)w%y$vOqCnQR(wOO0gp=%vlC~YmxMs2w@TMUIWp3G*4Ov*-b}~2=TUF zFp6u57qJn%LCJ6%>j_s?+;UyA8j}UJPf8&JiE}DoJpMjZew&a`pecuHwA}eURAR-Z z@5kU`Y5?ZMF(B;@e-Ot1Az;{L|#UcS#7u@p5%+gCby z>Bs@)FJBVXE8E&kyQaNQz#LBsVw>$Gi?U|1D58$Lb*{<^e_q?!S0L3JuXRw&M=#j9jkahxmq?#|+Kcv4(^wrN#cdOJZ z6*|A~8HH^LX!utx`uSwl(=OaE=rW>xV|j?fPe2dbIJf^)kzPz=A_O%pJzYx&+Kl^I zn)F_>rbD@Fa->m(@XvYho`&olVQboc)FflMN+OF4lSMIlWea!9(mjxGb_BOeYIecqt3vp%>DS#tN{Sgc& zQ7KI&1m7&dpyJ$Af&^=KfIdbsjAoJZ;rjh6WUlKgdkPy8)mIu<{_2cAvN(!jIFh5J zO#szY$`h0JQ~Qfu;pH!%Lh2#RAt?}pu)G9&bSEV?O^dUVN%?yC9EP z!xmkqzZTHh)S0_p7jE0y-p)_TDOo7j#eE|`5#m-rD{%NR$9ow|L?N|eHH*3~mV@_{EVR)cwNT98G>6A{>bb7U_2{kgh_&=<@1yEdF z7cKZfAi>==xHj$tNPrF++}+(RcoG~Mm&PG9!Ce}+;O-Dy1Hl6X3p$tY|KH4;s+p-b zHB&{UtGYUUyKkR;&f06QwReAVa9mC<5Yj!l`7R3-Sg%ym0Ffd^EVKtTK!7}A~xkInS#0tDA-etqi` zjS6~*F^+H&Rotsb`oJjSFWve{LKcbir z9+}8qFI7$B!8f!q8cB;}sQ8DGI`d~|HQqFOqX0(^2A$eA@t4qK#&k8&D#O-!w_fgf z29Ecj)E03<0Gr+er)|eY0q14`m*Wy&+lOZrFrWCQ)YkyWOqvZ6E#K-sWr+RvyO!!| zmAj7sE42Yq*VT=La;^0ViD|&v+uH%O4w%HqOXHt4-r9rZs@|w-pqo5^uSum<*Qk8Ya}Pg0ADR>qtEdK%NM^=2>GM zO$Iq10kT619)s5%-B_-#-;BDDs^)F==S{=xdsR1Jw%=b9kas{3(ko z=e>8JOYmt3P-P<%r7%LKz&hTe#)4Akzx)p_Z3^Yexnm$vaun_zYU`XQy~i2Iyue1 zV)O?V`~f=wnrc9d2$mA=feH-Z&eT8QXq*Wl%x*LxWkoL^zu%l>NK-{88`U9QS6jQ4 zr4Ont_{SPR^Vpb;k%{X8J8Z_T%?3h92aEIbS`-mN=zY}thHxPH*@)`0q{V)vM7Q$K z4W5vQKX3AjrKg`f2|yX}qnXF7TbB8U+&6aLvx9hFevDAUo1nSp2u^)IV*x&O(`mX3 z-TSpF55QFlW+@9fUp!G?eZ?%~#kB(_s^b`V93f561o~cHUJCJ1sq*QZ%9=1O5C$TK z1&TepF8WptJm)lBYBppxf4%+qrzZ~zZ1WlYCjGphsTnh{Z_Z$FVD54ZQ*I9FP1!TC z`+-pgbUJZ{(KC=QOG?1c5k=)$9yj0(?CyvI?hvzHY#j2fkTX$9VU_-@=QB>E&Kw)i zh)^T1yaSBX3^_g{nd2?Jeb0!o2oau@G*>tAEB}*#3U*%YbDapa!{F1yC1BQpOM{BF zfWjBNX0IyV5W;c_1XxzmgP$tHwq-zQ^x)evk}+>GE|P*T?qPAT_6P|+yYFGm&CM>K z>+z*L%}$>sb}PO%4|l-I`FLH;Vt8Uv2J>&ia5U5-&4iWPE;*BExfk7^GilR5^&lc|7DK}CoaqA&HT zJM8o|YT*W*l}esnd-%Aa%B-Y}%y+ein4V2v*Mgs##xyUgUGw88gkcp=z;^9KVP~*n zdHe}PFa=11?fm6+%2E5cRe%i}Hl#WtOuB%F7K&2xm1t%u*SjVkzJ2MtU!mm5t=BwT z*}qKnhedoGuEYbYovY;xwkbwLEWPVjSl(aTAD2SClSVq{V2 z;H)Ufm4JCa#(6e7@S#uMIe-^ENkZ!NR21+1+F1$|vu90Yd4OskSB$Yho;wWGGX z^I#6IFB~?k06SA`x8JeX9bmv>iVS=F3B1KV8ZbdSfhrLIC&`_{S45Q>rlvG~{M-GT zR{@9m>o8e6w72s`WLYS{jWrkDu+3`?W}$L8H`#v=m@oDz{iXq;80Z^S@cp$-Ulif9 zccvK|qBlO&fEPH$M}W)mYR79%Q`$mPP$56PT6Pw!>S?)nUY^88 zMmCE8Q}{K5f{JP$N)1S#$zrx-n*6Sv{%Cn?w-|6_jsbHeFW$vPeAKeEWWY2D`#A68 z6ncFLluJX2`7#!5T(%4&8M)UI#dCkJ7gS3%E;|N-VeL_Z{2hkXCf@dAmm2w z9%N1aQV?QiZoVAVqzXN_9Z*Yu_Nv9gfhr|1MDxXd^FR0BfU0>sR*Za~{}MCvtiBF{ zv%Zvox`E$^510pjS##Z?=)lybdc{YmWFP7ev{p%)=;-LZPG7(j4>+Nf{E@K>TUwT? zHvx}LSpLRx@GI_B0AJ0(N3&&q45CB5_bY8odFD&Dw( zU_N?u+Q=Db|^7bF2FCBp)cR-JN^Z|@DlsB}0RG(pb zH|F5zs5H8YzU=tR42y+hfFTT*V%KlLn8RT;%OYg8(O?6Fs-L~YtJ<%~h_*BUSF7qQ zk5Luzv6O~g@zO@w*ZKFCA@Y-xQ5^uUus^BScLAWv?t!krjW;%Bs>Jl{Art05=Dt5& zAoK^YsyUy#zg>O=2JuLKDK~2O9nTUzW!O;dA`JQHy3vK;4yy-7TW=U`^ltEGI=?zW zbP{Q^ca+T@LQXvwUH^-vB*e{)3;LO^=238J4(N=9SE|a(+28ylRFtXi4Fh+MMA0OX_ z*GAz+*cRDlC?I^rD?(FNqvi{d*#boC3JE-MO-M4_c##eZ)d+#aId(^ARE$d4orntY zu1agZZ+M%Gn>tVP&(F^zNb?Lozao)gVP@tUK;Ne6t@uVA`giCF(BmUg6RvaU)!Xz( zlin0;41K42#WcUMzV&Nmb~YrtE4}`xG`RH&K>Q}ofZd(8kyJv&fPl(5`9(v4yEi}; z!`)xco@;-))NUFfs0drfGPngihZVYYFC~ob>zNLSLLRzsL)9NbIsFmBt4Fy<|GQ5F zR(g|fT|WT~g4&aPFFEzv3$l7WU!V3#FR&=ix?bUG;5yxwW+>g`0Z6j}6MHxT3dZXOuk?+^YER*E@+c^bahMuF^pVD~e7wEpMrUCq~% zmDZ1(b{&8T`mVUomEC=djqlt3By^MnIrN%QtIN)+CC&~n^mCmr;OMx>$5o~mA!_66 z@x%A)Z@3vgkNrBiN1+l)b{=jT{#@~>JOfDdulDzV(Kp;)+Q$r^LKYJA&ee2cA+i}~ ze8@M#2_c78%G6Bi$ZowOfB-d8Ka45-Dy*(gTgW|N$V7WVVjN$cfaO3TOQ0qZIAtd# z!i#k=Nw%^wdZ~cu#}x&p-C{geZ|4js+RI4hi46`Pa4zGa4fIGtNqNJIF?mfNOZ#6{1u`M=QkweB+)s~x0Wt28-}>DeUsi zFMh4!40~Dck*k8br9WG7TVL{%=6>So9@`7DD~RR#a}YO+m6hO}!o}>i6Ny<6NxGS7 z13gHsQt28eb#28ad(?DWQnwGPT#dMc`b6T8j^n_AQ>+V@TSZ()<%VpCW48@yBI z4K$buXh61K^GR};%K-=VR~&v1W#a`lDD=-ege@zejDI6h;8Gx@yy7IH*G6`l8%n|efPO`ppfar#q1{tH}P#lp8uDS@j9BjOuZqKwBbX++E z(EU^Q&WhKvLCD-K~$m;$Nm{`e)StCnbjX`FiVe(s#FM+BG80e z%AhrwmnHc5M1U%xgN&M5CdCI3f{6oZpcOHG${C=|QDOUP$1O7t)USvD60e3M>e;+h zppY7P;+~tyZx6NnO*4<2N(J|ybPNv+Io8Y z{UlC-_1utzpg(fy_7d3pfq}0E9K?3Xoc^2Wy1Kf*GXryhOFUocvgQB~pi1UHY=%>Z zC0!M82A~G&efSV#%5FVB@C`dkkC}8PflkJy6AcaR@r?Qjx3RpqcrT#mKsU9V1SqzZ z|IxIz{`Jhui)=dsdnGZ40Zwu=!6{tBv0*rDA0p8jTzzioRCqNRk5ojtUGzTQD zY0NzYhw3!X3Xeme-!?iYum>yVif7}MK6Bqs;h%0^k=cC4J6BHUDJUz0NIe(A|GlG? zYk-VlXC(FM()fx0o=@@DSmrCH54kt#mo-r;=<<(mR7U`Wr>>?Z&g2O&8r|zBzklbx z*~dP_BYytfE)gyOm-SAL--)XbMI#^`2B6OFXnIn7JOF!w`0eLSHi3LHJ5sIWnN@qf zqBwW9m>0mhXpf{3TGyp#PaRo~t%Ze!xAzU;3=wtT#c==l?*(+q6=Xi$0%mQ1yas;u zM^3>d37@=$=XOCrZy4u9nwSgNnb_YzL#2P$$bUc9^MmL2-r~^zu7UqN90o=HTb=rM zS^WFdJo3Nm^?$sog}@?#E)FPPk@fwr|5rl_*Lc3?djVYZu$JKYKK_sQGYfDzTW4gS zNBaNavzY~i^~zc4P&YoW$^R~}e;*9F{=Jd^(^D-b|2Nk&B-)~C{O=3Pm5UaV+VB5g z-=k+hhR*n}_^7XCrH}Hf?tkAf_v`Y z|8ytBxm~;PfnUP#v87i9U0dvbeqoLH*z=Qz>a124|MT;5|I2ssfB(F{>BdRB%(6TG zufIEo{zIM9C;dCU|hK?Vtgcsc8H%6rhS7v4s3xkw|c>>v5*Hc^BNsM|584pnh_UUdj07M z_yZyY(AKpANy9kxw>7I8oz!oqfBa<*0H0aU1hmci;Zq9n-CXzWLP0n?l+z_J90+2( zcAQhUE;dPHd#*z38V=m&8^9r`x+Cu-@^=pa!oxM@ahL!Sj--gN!vA5 ze)CATfNP2^;fi#X&y;h6`ynooGT`SPe%#<+8I8MyD_4@4{5TA1%|c&wC>2YO32`sQn(cv4Zud9abaXQBxd1ZYu`^d+w;j+o-RkOz#43Cf9nxL&Q8zAH%U$6}p?)`_Ug+q#G9S39HMgYU3qMZImFXtEaaUKNPBL=<9E zeirk%+1R9}>ee8E4tiD}zrbGUbY9240`vZaS>)MdY1C-JM}_Oz2_2x9Zc zs;psPtZHu7fQVlTp~l@~$KBlDfOB()(BA`Bbb{-h%GP(k1KwI=i~nUMGGcbl`Lfsc$zk;-)we zx3v5VI_aZ}6UTT(268;lT(o_R9=t*P^y|3sll#U|6}8aa`3vWcwx_YwkQnskYw;_# z?Cc4*KiZwsAb;B+hN$d;zik$ty?=DqT_&6If5(X)%MF{tM)(#uuk|=Tv2@_DYkLpX zKmKI!U-R14F0A9pF6tABIwoXO_Asmu+QI$Qth}hQyi!en!j?hIYyE91kyoVdez;m? z0rDPl{O;}s#^?E7*Qwe#4VtEX6FS$nBNCRvBpl+?_{;J-lQ{OneM;cw4dl+m`O)oZ z(OB$sUIDw~F5IeBz{1|KrEi zhN8RmtBd>P3V>;z|43fgcs2ajNO--@@8fl);cD8+;vC%1W}x!>cy=(Q)Qe-$SG^v^ zotQJhwC~Z^L89{Mt$3$@py!WsI08naGk|2Kh*X z^GH?6;F=auB{SMjYhkgg`ihcFydPDDjaEBLb0hPWdR`+LDbrexI*nF6xA5u@ovx(#ciE z|8(odTdkyT6rDPhyNI$)RMs);O2zo&mlTjgkSjQLDCgO#wo<9Vi8dA!m)V}<5ywQy z8~B@O46&nPscoWh?$_9!n6Ur&aq#0e;>SV zhmY5eyqsPA1X|x@W}kQz`MK9~`T6_b0}Zekmb#VxSWDj$3!OabJ9mf&4%^nxRPq*O zZ!3#%7o;z=S+!hFWK9Q8Z>Q5>2Vkz5KUal52q?xSjP0w{>_EJl$4RSJiAo*y}gLTOyK=tu;xSGv^ z)X~>iUJaWARWsg$woQwEPxO?k_k^PE-5ejOmgg3V#hi9jZO!^C(^_6#>fezrO`wWa zP%Jkad@{Jx(mQI(uL-q&e>9wKb0=&{om5zn@1Jbn;kt6w&rLoS7lR_2Pg>!=(%oO5 zvQM|mEoP-|6}$Y?ZFzyam5+RpT*lG7N8mU=6mxk^McZ2{Q~L^EKium)%*nDQ?+WBt zS?(NH!BQmjYOFuDKY(uuKi&DwyBS5t%|n?vhL%t>em$yXF7?i`#(u!-6%2sNXC))> zudW>AjSTn>kUc*aC!c%%0(}+i966k9S)J^d9YW@aXMcdn42j%fj(30$-N)ckK2Xhy=0F*+OjjMoUgd0+zo;{V2 z8dXV|0_+feE%v^C2e-b5cYcR=A^a$_(?5XyQ)uL~2qAA!10ObK0|sV2+9PebF< zOe~aY+68%I_fO95&L7RMV{a)bJsm-Fpbsf&^2=d9I`;0G_U@MM=DOQgiA#>iDVCdw zh{{e2yR`-MPCBi=ebWUBL>#*HCTFlrVosxKhke-22((1#C?C!6R9HHP;K@m#Jdi4AO|hcEFGB%}KpaVPdWZjxZA)>efBvSWyn45kPf(w&c8BE&GOc>N}P# zRH$q8_2|+2d?sn?^0ZI{xQ+{^*k|u)-#?L`BTOZ3FE!J)xEy4|xg;4Lz7Uj}1=Rd< z7HRoOB5|z79NGVwkz)e&qm2pk%UReoexzQ+kKzdF7aUeNyrp$XtQOp(ICeBMQ}IG+ zfELWRl&92~i3*;E8+~;gA*pZkh$%2hI8JN%Gqe~Z@0h&rvNDTdT>eGj&0C+=!r?^-W?fr*;crR=7Xarl&QQrpt9n#T($s9>s48@;|sh8|Ky0c<_5T zfb~(THnqq>qY$n`TP}qMjjfHv3S!?86&cZ-4N4zAp_~&l z5Q}+e@4ht&`0x<$A>hHRbQ1c7XyLN~&*#5+SJ|SXACHe#>RRn5Dpp&sQI_k9tS3%# z#Qa*E`$jo%Cu$Wc>+EGi>T#?|kDQNxJ02g}pB&qs9NHf2FLmVCHRM;cG?%oQ`nei@ z_})-zPtTvpb2i!+||&uNKHXPKVRAGB9gH(J1^w7*W%Fcu$Y?eLr(=6 z);sKPJxAm$F3~7^D({Wg&N%}M3;Bb%485R%MzXPn8pY7qb+}~5=k>$uzlaEF5+tYL zLEl}taL``%5}*gHef~jAu#4^c;$57?M&j<(UqhqZne7^3!%WI?k5;->D+TzXnn`+t zMW)!|iL7hWv_TGtYF#UhGoBP>@db?x*Ei`}6)pv41;skI`|7L}6Qe*OYWk{T`c4w9QXNxQr+O}?&ex)e#2D7i{*M@Oesv@12lYibay6C? zZi9map>?v|v{HH|LuQoAj78^;FuL~^NIH3>Xy&f+-CJ%d7?22`yb6k2eoGTbnBKk7 zI~{+jFWUEO7Q(q(f4=!@&H~Zsy}EB9vH?T4Ivet_6?bNj2D=bFX;nF+=MjarnFx1W z_PRt9?@nCYIKFMcU&4;IU98Ny>2M1az z-(UUUR9Y~T;3viB)_c>&HH{GCx_4zO_rCsZ;(~CmJCRiKm~_OzKaq@R;oSX^I#UdE zpxU;3lHCYbOicf1`@2SI-r=DENm-0k=g&N3|L{=A@`n>~=EI28FVHG{c$|FZ=>J493_pl~-ZrM9+MfV_Oi80C_VGqBff5rR43%un$V z<%P=>3dGCzcd*_?R-cJomI8%l0$W^(bXdpjs=`z7^~*IX1N&+nV#B8afKh1~{gwv> z>fIj!0CEjzqn>+K4p4tm#DjJpd#dUfet5jn`=UqIKrFX`=V+%=t)OmhtFG?q?k-n{ zX0W7ZaUpMC@|h*Ew7wx(1)Gk)tstx8VDgexP0|p_8h4(hT=6};w_M>Zl=VPx_D5ZN zXH`vSRbEwNhjv)~#U+l5?NQ21&RW=7$F8`}C-+aElJ<1yd-Px4>A#f5LDRB7I<_Yx zaaa|e<&YibsHJT6@;qK!h5M!2iMD!SvSeA>RGWY8bX1Y91>TJB<3~Xc&$*ADPQM-2 z=cf%~@yaTOsw#&o%TZEOUJggSDCql=NNcQ`SJX79Z?MM_)dE3ZWdN@Q`D=G|3cHVl z^evn2?fxEUvYC`=ailh2%%rfTRv2`6jFs+Ws^bS3 z`575$$?3}Jf;H(mC#ca-X7Mm?gK3PpHZWiM(;yfi=6#i*=*A!#2BzlWpZYQQ<8E>V z=KlD7d9&gaH*@SIWGE1BUY0%D+_x(`QD4-ZRS9yc3evlON1g{&Ss)yCQ|E$Yomu?4 z0sqc3(BTr4Fi=U8nqmuOK!g*dyhrcT;2+YJH?c(aWn=x$m|$b-75I)>P!I>L^+jO# zMC!h4ktpR5nkjBCy{;S7f-sqWkw?&uo83#!lp>wXVk(a)b4g(^K@pc*DHBoGMnY*Z zY4BK)sJJhiz7>mMI38-zCoQW`I+4j}1JUoTYcd>?|4oC)kuxTq_Fkt=-`@yTS7UZ7 zMmJ0w)0U=m>)QG@pc-RwNEX%aO!m($4@)-~1|8!_MX~)NY+#Gn)HLn(=lCN#T2bpuXJ?#SB=z zHUlCBqNV}V43wdfj*^NYO8oP-gLw$aXR7La!*cj`!u!<`n%tR4%-eLX57GVa%e|xK z8)b=4VPOTb9GUV#5HN2lT85!JI!BV}bnWvkH6_J*$=l_q|U1iYBb`DxDO#0y}> zL-b5CNM@!916O)Yfd&l96J>$h9+Lsl*O%90aGByx6;cQGTgz-|eGyx|FQKB+-U^z^ z+MdeS4LK)dxLveC>SPT1xHFF(g+{h`wGrtZk5;vT2k-eNY0EnaXTE%ac04j#_N?uG z5odQMC37Grv!fO(tjEf2$eLosC#q zu_|;Lymz7sfLJsB&|ue>F~{~}aNllWIK^HYq^!L7^K#)2`Z>C#Hp~Qy;y^RIg?S_P z<)7X5@1G?oF2Z{{R0b7_8R(&Rh&Ln`p+S!-vfMQ|M5nj@PE`ZPp6EAQ?xJGAR4u2E zRxK-DRqLDRT}NDNcAow3-6V(5|VYf+g=qTvZzTgY2bpJfzb zq;2wN1=o@;wt^ki+S;gY9!otPEiD~AA`k})GbPa-1ZToz0Y0WC&Qw5&po?!#;hKf7!Q6+l*@@f^Vt|il-n{{*prB3kwTC_7hCapAQ`|xu1?n0H7HI3xcG`~|fezJ^JqBry`^1LnW zpYWUjMWlD^q)G2-8NY(iHb?~Nl~v_paI@pZN?N@Pf#Fo?y=k&c zYry8*VeOcQ-8$kpc9HoYeCeh)xQ*wBt}2XwLVvwKTkBd5t}W$#=2I*GLn84hKLA~3 z0)cKCM*e4gGeyeO-Y)A`OF=Ce{wWPVor$vwK7WQ-a3GUem#kAc7RRX-E4b%JxFnb$ zYb`CQ(F!ZzP7xtD$u3PZvP9n(Z*v}7Qc_w{9(OM-E9|rl9?<&XX?jI^LrZf`4o*%) zra;A=&7Z#dn0zhx^fjftsIjD|v8Jd<)eQ=Ld?fm!Cv5_$9q%$(-cVWIFwuYde2=C+ zg<4$G+S*an+EFFb#}QBw{J=9%vB6{ZUa#y(P+BbPpjG4$;Cz<#N;Api?d{P^>8)8f z(PfwLm^sm*P$X$C$TyHhC|)Z8k&t^WFGWKP`8>$5<)$#xIfM7p$x-IFvE0<^jY46F zQ%61w?D<#Jq=z9H7Ioi)@`hjU0;Fr7=sZ)`lb%)QrRsIy4N7?dIs5*!j~E zApeWbxa$~OBmg0y44IdH>tpZ<1q%z~#sSpJrfh>~B1$q!x0!YeqTEvAHyBZR zdY@dnZoMK0UZF}ngaZr-GUS`xQao=$fukfDhO7Bcx8+z=;VmR`_+b~QMiF&(vC-yN zo3Q18__i-2kGd@ge!-9%P&Q!FtrmlC*m+99T&Wd$ZU$4fvXfB7D7(BhDcE5=@9L(k zr;z$__q2(w@ETz0J^;pVbYrK(zMsu>EO3&{53n60E0t4lo?7RM`@AQXG9``Y(Ur(n zcda4sr)WI;Lh>>YSFj2(;U=^&M;!W{zmTGx+W;0;7MhRZr0 zH;>}COoo!~SLl#jOb2#CLd9NEb5_*O41ZWxul45KMr>@RO`}{Z4xOp0K-RZU@#3Bk z=rMS#3-1L7^e{IfkLWM=OI?qL=q=t|yL}A|lRkob9;?BCGKk$&crl546Gfa9$W9h= z96dj@mlh;EUi|HSKI_>iL&NS}k%2JP_0*)W?33yarx%PB-|KZoNL{fpORt5_P(Yan zL2d1ixZW1@z6Q;F!yG@W8=z{*utxP<5K(ydZ&^gXSiL0o>Df%&9K(j#iEKei;y_8^&{tp8 zSq7KSKf{l~D6fQXNcW}Qt5+5)!{f2v^I`)X7>KW~;U+zmC&z}O2Q94!ey!ww zeq?vjc+lMrqlDi?0t-KQCKZ~U$y-_*e(x3U+mt!km)PMB=|+{7(Xm=2XfdiP*uF(- zAh=VA^6v{>gB2H{%t$($&QcByc`OK2bXx8wg8%GJD5RxwxjS`ZEM)0+?=p^N30e~Y zLw6Ph*ORMF9#=xqcR7tb{vCEz zBU>^dBYIfRcXrb+)g)h;Kc|g6Z$1`l%ZF|AK|SACAN1*G(^M`A?PC6eJSu=spf0U@?8z8 zyZJfz!ATe~A@wz+y26N`2BQ3PfUpK+1WM~m#XYq~dLTt0ZIs-`lpDFYOIaYQl92M) zIJfd%(eEpkqE$e@q>`@_MS*|Avl9-uCCJ(ZB(kBZBi7O(dd#XLfYv{X_+~~qXL2UK zNu%lZM7U5~+AccG+#v6z9Z9Gy|3mTHmkzj67y#8;v^ChBj>;^zB2u5HnJkY1@4e>Y zF_Jxc6Z2p*`YPrOz067U>~jy2x<{l{jLm!EbiBrtmPk{6`>VX(YN4%(cc8T!F6dcO ze+PKGl%;84a%^Dm!)zD6BMuC|hJ=Vg1x!5wn;6B*EdX1XkP@cr_M2BTV!@-+d8(uL zr}z3LwJ!|AJ#4kMR3ID@P7Z@K+~$yF`i72vmDyq_ z92OO2H5CCQ#Ux%_Lo!} z^Ya_>9Wxb!^tFt&b?QJ>wX_CCRLq>%T3R8j!%{nz-)n2%+Lm;F>(y4<;$*oZB=kYg zw{w6O&TJcj-rS zM41r0W(*dx{<{B)*l!DIM&+$uTG8co$`JLt%-GTAZM+Tc_&OR%juu`=mq1}EyqJ)c zwIWL|$jOwLN;xz#JcMd(9bQ`(tgHK_oShpp-13dW;9*gYG+xsnhb$r-Wr=ol^Q@)K zDRpA!tl7dFB8uaN9CV@r)s_Om4pm}Fj?#q`I0}98?t>f-TXDfd^wzjj z93tbi<@IgxX%I<$dOKWP4%(xF*LnmT5n=a_7y$#+Uu1bwu%xjVK#;w+^gJ7^8sJzdez1I|JKpff`va?&Vb&D+cMYpGCpbB_*R3Wn_Xbw8f~Djm+9Zwp1aWzR5Qxq*8b* zBm+}%2^Rdab)H-hidsGDP=alC=(5^JD!B6Gf&~qzMO=LMM3T&Lim5k-juR`UNaZCF zGCXV6laVYT-PO}a2Z+8fb45~pGp^8=fY#xUtCbg*w~=e+W;hpgsvQERbz81BVLhuKmFfH1t9D=(%3uT1WO4D5C}xr zq)H;gW$ow3*y_RicGBFOW`z}SX-1clg(=-txVgUa$BMn}ne|ytUkm`K8Zihfx9lT(3d{`)LEYZK9w>vsmT3a-lK{zZA zKHe+;y*M>FGdBt1-5B&e-7Z4LVV)=Nu^J~1UTGbNRnWl%dgH8(Z0 zNQa)2lSxpJoXzrANNue&do)UT!YMd|6X!fa71EvaR%M!~vFF&$XN9U$l=&w52ld6q zh=~rW18y+`UewR-pk|-EwtFsaV&T)eBRP#kZ4FlSMimEi#%Hjk->L(&dP9#f!h@0kcpdRV z`nJ{78zYK|raT7^3D4L$v`a@Q1^eq{zir8-anMt4mnZ3jAShO@t%CNH5#J)krIoBRW}1bW1|)cR&YuUY0DPXO2 zU|82Dy9*nG&qKZR-4yG2G9W0Qv>Q$;KRo?wU1GvJ#p>9VdC&J_zhD&(HHE&3HFQdD zU-UvEN{ZABnCKovT8xDC*oT@r=n@flN#moS+#k(mVXp+ad0;LvLU`Zw&16ULL|ga) zN+sI!Cn1aPTw*&9Wa(6Q!8{ZlGXlF3hK}3!e7w;+cUKDL?$QmbgoYlBPa$EUwS$v6gZ^1iQaR zo}NaYnkNFd3zA}y=Y$)y=?aF1->;65^|}-ufFNp+$s~(HvPj4(c=r|xduhqY+M_QW zkM2su^WB&XT|XlVBMZzpsVS_W{o&*F)uT~*Aei1d0RwzNVs~)t4E1pdy}XRvBTNXi zd<{(pe)?QpcMdXaJ_59Q3hdXcHsLClSi z<8X|6k!%~!Wq*la)P8-5dLbZPwBH@=Lnp4(tMMNj-XWi_lviHb84!S=r>{ z!>HC^X^DK5$>ZzhHlx&mLrm|U#k?a~W;P}Ql;#&OrUaSy@Eb6UXh;_r=EzX>T2xR% z!HX`6fMEAMg8@!n4q3dQ_{mUX%8bl?yi_d-8lq6;r z{fny$1j0LasMSw}_XU(QcCay~-xdgLQ;5XE3-KhXHui5R4P`Ml%cHNe z_OF+}S>ov@#DgjKbsbv4u_tZ1ys2y-wo>@QgRRuEV`sA{exGd*kiz{7E#88>zvRAg z)%cKiK%$+L89kt$NMf00VUQPzsC*&z*`qy>J>QUhOQ=2`Uy6xR{hB%;P0m5Wm)FSf zUM9xA*k5{mXz;V1h8YZ#oFL8E??==Y8BRY5)LfRL1`=xYYg+xP%t(toi*eDIKn;9IjY| z8mmjyfM|uC3>^{5t@LxUa6`aK`Inc;l?+SF-8xYy@OYL@5MJ#dLnu!HaQJ-JHKT-N zKr+E03aa)BB|q7BvMDutb#HGodYdHh;3(f{TTu8fuN(Jhb@eR%QLvXNuq3r3Cvl*g zot9V-5t{9AFAe~T1iw^%Rc2=c!1Be55yUi_U0eHIXG?eyc&+ zA5_xCF;j##E7@gvkWB8>cDAhU)MljU+!Qz*BdFMJI$Uq7ZatpH{c*e;S#V6ZW^eaH z8PsvHnvvHw8)O+wCpJ_g+z02MOG-F_J6bkceksO3bToCIRW z8zTBv@Y-09uk$Mx8EAAO?!DU|cUocHMcvKv^3Nm(%s4|MLy+C~=e1I%RT8g#@qlAw zu_H_N?a$sZbeZ#R7^5^0wXr0;qa+R}i;0jRk)W+Xo)iRwBxNPy;yI>-gp$x?_ewI~&K zMNUYF)Z?)PAfBCT^Nj5gjr{DE#+I%PBfsc!Pw`G2qy4OzoE!!XJwA5RpAI5A#1o4Uk{QYa*!kTwG#>f^hr!(DT4a7eBNS$vdu8(411EwFgUQ_O1$ zW=J)`)!Ptl%f|IgXLA1sHGYWDW2LjxQ0Oq@N9H%1{bl3$;rmrfcgqqY8IG@?;g@fq zP|WG#b(N%80JjvR2|?_TyAKGDrU^#Y)Yyd;c4Eivbd;6bH&`Bq2gjCh#(7jBL3$Pr zkc?N_FRL*ySZP2=2E8|9fT%45b+*>M*xg3 z?fLmmGBSpKGLG)*KIZ0f>gICp>ZFBDKQl5`N{3xEEwwZ)y*xF&2+P&LAWKugu}M%| zK>rn~A)->s4GC7v#yN3v`Hh~T(xAcO@q@edm_k~KRev^%Ljxc`^V{`U7$uzZ%6vO;Ee|wRzdSGcV_G_n z9{Ny`(5JFTU&+d_V_(>5^lZOdQz0-gDP zDO}%8gQ;mhmj1|ADn@>9>)P(7Vm&-f8%BX<0dT?Os)L95(ID5=h|C_Y_l-u6TNt0=RTSzX$zKKkekcj%6mnhK_m~B*S%4&oAf(ivdg&K? zJTC-|Piz5?bh#N2PfONtG0yT7f=ftV67mESf)5JqwHT zS5)Y3B206Jb{R%^-(kd1%7J02lGq@dhJrIno6f*@pmy!YOr`YZ~ zoJ{Uaz^g7@KLR9Rzs7Q!%{K3U&M8bg>o)kT0(roEg>0v#ROc~LI7j7IyPkPc&t&bi+T=OXU!gDPgget49sO-I+M;MLw|4P(?cj3ZLEM^a z4-4rHLMIae+D~F>G+2VH)55Qh&0+#o6PtfTgK3n7Msl<9M`I(fq=EB&Jdrx)C-F~{ z2OHH&dHr)Vg>$enEHNl?x)^VQ_6ub2ms+AsJ=n@E;UQZ7&w2i4mO<4>_HI4X5`~d|nsXD(6_~-Ckzu!C_+-8)46gYJva3kIfYhZw- zi@_LwvdGXK627zZP1ljl!J%X*2frVTll zCl>J-8^W`O$f)U=iWs#aU+)!1CeiCN6hV33<+*-Q1xpuQfCZ_&=r@uP=@xnLa`&yULH&dy0edbi+?6mf<&@HqkLTu{m2=jAF7%7fz+T#kp@B-PF% z{MjF@t(2-TfUm(&)iXc_RtYVgfrup{acJ{kEI7HPpro#@176VrfH_So+med0s>&!0 zO-qID#40xQGGgl| z75V}WYi;772k7i9oQ0)3G4TgIW0jJ;Zf2IRg8V^Cef_0=1HF|6y|op+aCYVc!vDtA zTfapackSM^z|b*tcg)ZTNHa7dB{3k?_>kX&Q+Z@M8Ys;bXm_~(45L@8&Fvz40cV-5}Qd>W?A26xiu=E#Tx3M(wNTU5LQ$zd9-+3#M7zSE-Hj?eRH*TXPPY1bhP#?*}ZC?|OZbwZVT1 zvpgLlHNVnC80$|1c1YrK9*-8EU#~=368-avjH7t6GA>(-J2!SiK!^pdF+qH$89JK$ zj@7bs(fK6WnhsX1hIYsTYi?~B`pB0r(fMf$g>V|usb9tQ*T=U{(vv{vbdwYvdv`x$8*o-2tjIJME`A7I48@p6ln)ZTIEo2e9 zYd!%#Yl|k|1;vTFsHj?5u~HL*l2Wa=9i#s1N_wmNZb=HAc$IOt<^7-7 z@e3g#+m}|C707GTxt90qUox^1GGGlZ-tEF~&b8vE&jGC=mIa_-`UKtQ)T4xl2e$kB z_WJLm7h}~_O;N43zOO!N=W=W(s=`Wgj)YL$z?fD45SX$@NfD-GSvr|&!zH|<qz z+)6pI#?G1`f{`a-I};UZWuALJvvXe_u(j61S(>q2-Zf`J|keF)4TG&-;+-3GaD*=vlm zb`aVAL89y;WKV`;6?`b&2oTdQn964~1DsA>??fw1o&K^}ikYxKBW*a#QKv>yiAx2j zE$Bzbxg-OCbd911)2T6}CJHeI$&O(8+?>q9z4D3&Hr%U@)H|w;XPrY9sq=oUf!0h%PA-EvDm3|#o@Y`_B&I(2<@r0YdR<~LzVa1?4O$fsk@_JQHu-QsqV^4 zk|P8rQ^vOa3y2A0X&8-2K2SzQD2URUsbIIAQ3=LqMp?K#6|+2qx@U%hq^s_kLd}D) z7#1rg`^F$D1{tA}HU{xt3c{VE18WC63FpW_zEjKasp%L86}rF?;TZUtnpvRP|19kN zcWuv>;~nzypFi)IJMS2lQ#pz{R8OGTLuCa?r29MD=LNLAX53R86(u9FsH7Re;y8+B zD|ygGkFv3cek6e-X;$&K!U_-7H$Fb0wTxc4_F^J{Vuyqc#3~Ms%@gx%Qd!ko)X7k- znlTeUzc2oNg7d36YBz2%rVYz7Fw6G|$&ExQMetva+{(1HbYkhti>Z7xtKhG$8 zit{TQP>&)fQ`MLHn#!nmkFavETa}DsHk<7?Hk#(CjCOQ= z1ye{W&@ywWM*D54@oKx6DY=WAJ-gorlA;B&rmx1` zx~{2&nTGGR%<6a?7fOm!A|osH=N4Ty+Z{Dk<`yTATK?_h+1r;ke*cI*>CB|+qzdqH zrKhk3p*&Bd%X;aV0>do4ya@~lb{G#B#LXwbTf<*o0j?~sswl6l-Y|coZ^Uu%j<>~$ zPoNS3q_DF;Y&FvsgA8Wy+9MwoC^i+~q9=a;h0 z=-?M;`zW=+9bK)R6bi{X588ZSZo2wadrDR-qtO?+ys*X@Tu9(290j1UAjD8O4o#8;k2?5fWi4(<66>p8z0&i74_x(8oo9P?pQ9fKoCQIby%G_SrpuFjT z#MrC$ounda9(n1d+L1D=foG*LWy;e!UwL9ahf=$LNx$l({sT54&V5h&;ZF?DLo-T>LXJEVORUu0=9%k8T2*jnJVUfe^`hD7{|J!qzaVWnK$?m7cgyPTaH=29FxF zRdl^LTbnlE1WYRpT;k6bPi3VR#MzvRY=S+5xOV4km9oAZ65EuEM47N;j5Ch)QXaWH zVkQ;W5OGjYG~*jfdP~^?OSwM21YMilcG}fr)<_!G+Glzut!aU zV!3xGOTMiT%rtC8f<82=W@e>H9F+4q)R!R?LB&>iW<*AnvwkuQFx$+ahCPL7k>oN@ zDP`ddZ1LxqtHPltSqUqp8bIQgMq4EqdQi0~3>-&O#O{QrZ6GiYh`b;O*2Ko3B5RVL zQk;5_UKNe>nN|H#6;PF0ZIHMCt1$RXHD0GDX*d$mkb&lE5d6J2Z5*rK@e}WUG?(qyi| zY32h%u|NeeZ*xg;HTvy?EGthP@pXYNS3}R7blHphKr2M-5q%x{KL{^Pyt^9vAr}O5 zH|d}C?mxD|paz22tTw@bKMy4FjRaXQK09bWZC!X!Y3E-lvDu?LkrJX?MqBCSlaXV> zK^ig+Y>JGmI=CQ` zL7O_YcL$%C5o2acRQUQ_2S&A}OQui`$hf!qSq=^C^5TYBdOVwQWgg5e02*ZQ zmm#<6@XQ;8JGdhh2nxD|P$RCtoYc)05N1g-b@ zO@0}C{#?sQfA>1(z)f!8j1|kKdpg|IzEMoV6vNV%a|5XDB!G<&UHXzTe>U z)9s9p7X083#TXw`l}#P^NW!6A+gK6gt9_E{`M_Wt2hL{T!N#0=d#)gSoDt9@ctXh> zmN5Hr?ip@V?Y!Pyl~R_%54mSQjV<|VcoR@{EEZh zzYAq_@FOojzYlu+*HlCmy=MCD=1mnN?6_+4PyV818OPD{h)_c-Rs}k}Rx~7;7rWKD z4ZS>2Zw-SBQzBX*^Tu98Z5l&b;cLJ;y*!Bh(>AKYii)Xu(^wZ^owF0-ruT+)T6EWh z=(Tg_f(uhfw0RO<;x8HHq^cG~U)V~7o*QwQv%UskfU~vh*3dyy7MO5BL?`nqMtelb zDWZ9%0*HXFdU!Z_vTJLe~LIE5ePAz`ptAoC!z8lOmM4k;O_l?CreG3Kjqa(3TrX60DZd^zslUP z{;0~ET;si)1PELPQ%D5j*9YN+1tWPConT+wLKa}hnUSb#NYG(o4RgzJ1~4t=O^ zgdH$2EU+<>NvX#%cAykdLW#}Xs^|d;NzTl?VYXo~23iGOehuMDxL4woJ<9?Zw+XE; zq#l}=gZ)|idG)h0+hiCst&x%?ARHQBtNdPYB*;&hSSMQhRRQpbCFF#%$ArWw7@sOR zG5Yn3cL_$g7*u8U=nKj=X8^T81#woP3$H!Z#sWszJ>C*&U28->P*z7m>qBrTPS?Lf z!GpegeA&G1Pb4kaLW@NLQ8f>lOn810M)-Nf0@N3lpV&T!mGPx$aaQNO#`h7~=H#Je zw1iQ7Zi`H<-0CJC#`jFt#Qh;j! zJIS}gQy)^d;K1h2e(;bdH@_SHqseDOcv>7P;HFmxp!8Z0J=*nxBxiF@|37`l{(t+9 zmjp}*9xA^~MucGrD0jkJvnI8d#)pH1e0a6zQr{1yg?#xO0FEF3Q!h!3!Pl6CySIfg zr5I`EFp|hM@5;BY)GS)C72HDj1}f6wU_=ADo4 zCZq_eGDNS>G^)PP;RBHn8xG0Ra>a2x0Xuh)yhJXM72CG-^9gK8aOy7w{ zp{fP?5_3uqp|C|JKxwt&$($V)#_?m?CUoV!L=s-HUa(Cc-78-*JQv~fNM(BGG-xq3 zBg1(nwM>oCD7{_iMo|k*lXu~i^P=fXsb~f3x$+y@VwGYYo;{5=GjM{2auT`qE}K%J zl9JNlv*}zqYWgl$b56#kN_NBS^{=UPDSCwgdIB`FnkqMmmQ}uLGHwb&-sa-&+EM}9 z0hT&g9H69utW)xAruff9JjJ^M-3zZz)HAFMSUUv8XXT{*!c!ZWQA(Wedy8L??>!d{ z|Aujr*}=wj}4!lJ3(i+a4O20jU|Q z5k8WF>sF0ibHduv6}ZmqN%naDUiKS+_TtO=VlWXz!`R9Fo)JxG`ni1)i9~l|{1_6hbN!j;enMp0c#Yu#mne})=dkAa#G6-K% z1C@rqy3&5RS7mue4Ct6~2q&xf_5EM=$E5Gy)#F(Csg>^k5mIxO!7s_T;gFDdCD6A8 z)9(u>`Kg;HRZK7j81rQ&o5F~RVo*cSOlleCOW|`2@dAkn4)yjVJTA4-m+kYlsoY5y zHp1uc7n5H4*Bko*vJ*2ymF7~jx{YyDG!pbAp_Qd*&m8wr>GgY~4r|UIDgYBN^hJ7r z5OQE+e!uhV=*r5_Ljbw@8;dlOQ0SLQX*6(^^cV56=e&w5nh6@y%Fnsla@YT0+xnhiNX_3S^0eNNZl~xnZwscdu=G zyyygXtT}oaPZ~CiS|Ky9(FW4Jcq2@HHdAi^L;K4-s2jy&=gLTqi+BA-!?dNVGwAiT z2*!uQi~6YssHSv9xZ4G}8EKPa3CoL;q^gh(iR4dyM&(2MQ3?5^@k8!`M|*7m=3MOi za2%Xh*Th*8^SAGwwjzUjlRjb&Kby{`WniLbJZ7Rjr(=+L&VrE-!Y@`UObK4JZ&wI9%gbIp%vXzXQuY|sTCqUwox&+r{* zf)|3KL7q4PpT~^ELpNq5CBB`^LuhS?F(I1oRAS-edfE9{EYRDb}4IBSF5K)-Ms zpgkRXXcvIwMg+5EexI{g66{BqhD^R8seipcLIP8q8Q&+Q(>UTcYl?s{GZsSj> z4&dFLdz_MN9|C!3bxHKUf#fzTK;>!qq~Q_zYzePsoO_Ap6aN z%lnv{++nC`*CnA0-pALd16Kk0{sLX_jJ;yFhoer4P{!oQdB{%i<~wBysnHv&#(BPHTndiBt{Y-&8-cJ+Hv_~eA&FzVkrZv-rlxxKj z%zTI~_(ci=s%n#j7?YSgDj-A5o9*mKVC7BF1-k~M14YGxS<{y)gSle8b{(|?_gT-e zT&>nZU3>PEm?5(PPFnB0kxaGa6SsGw6?GT0kE0h?DzS4v8@}GLcvlX?+%jHjCv8?6WiIF? zDO$xxc_!jjIhct_R!}n~#1f5$3yi3Q?-g1g()6rEgenXXu{iMl{5oi|)VFk0W#8?g z6@U)GSwZLdN&iB*{`?6pf~U{hf0A7DA;_YU@V2BLXd;NcCX6Hc9Ft9#OP|OG;ce2Y z)c;cDSGnQPl&Qm6-EZsm;XPxS{RQ^=wgYoQl&yP)RT}||X2-_4{K(vS(1NolY|u(P z`?F-0N`@7dhEY%qwp6G*Tx4NKYeBM-*4?V?0ZIB`u9)l{c;Z3OvwH*9&LU zydPuX5oO<@#MzbuOue{EtJ4{`_pSgl!p1`l10F*@i9Kt=wJ2eWh=A^el$y&vNQA!i3z}#D2Ih*J`1kLtDFjWzIfw)gzPA>rKHw$2-6C| ze$e8`R$uCKn|~y`lK*W@Ki}WzXu8}i5;qYkfzZ2osB6O>4lH`;iA*BVSRPz;I%O{W&8<)>GP3r6Xz*vPde{gKV$NiEg4=jTPo3pdKpoa!& zc74Qk%J_moICFIclKKYsjSO+$&Jg#c_Oi2I)7PK$&h)L&sC-?$gzglK4lWOn{W2cz zJ|jgD+t$zG-{bQ813_S}5JsR@^l%l&Ti*vjgoG+mwH%6)wqk7bSZKL$@`2IHcnGveC{BI_Jh`#QcpX(S!&ASX+?!Fm z1@@0P4gg#y$xS)6PJh_@o%NCBw)}+0i&2*H0KNRgbbi`Q%%a9+OWNvjYmYW3R0}%v z!B*;Wkj|Pai7K`#hE^__P8ybOoO?@UgUtp?7GQ?Fa5GuTC|`;S-8L%-k=}dtsgOO8 z+A_vz2B|sC>MsEQ~(F zDnhrf5*njDv;gA>YDSyFh>y~cAyN^`HwZz;RgoDb0k-No_Fx}XdI*YavpqJXcEhhRK)kYPm2CA41cgIe zUBd>z7?iV~{9uC)LHRK3mtpS4BO)#zm_CeO;m;zl6j1|S;`QE4@_PRp#Xbm5pqBu! zrN!^G11f`@Ly-j~kR`G{VC_)M|08GjG{vtBMRdS2)&%;D`FlLLe7L6Q`_M-0-}0(I zP=$jNtQj>nru+{=QWKwrT?o>d*27KJsDm;bG&FLbC`kJ0ggKRJ7Nk1X=eH^T4UJF7 zCQGHA7;utb0es-#_XW>VW4A}(H)bII3}L)9b)X|HE@3%0Q;KYBXvhmge4c{a2OZej zmB_r%q&Lkby7)F=U|Qu~m0D>qm!eTY5TwbUO;oBE>^t%DA)K*gao>PkwcF^YcSR#w zQx+rB`SY?p^gNi`Fot;$RB_;LH@=y@zt?~rOGh&-U0!-G8XM)9xoGP*HyYW+Z_idp z6WQ{-%NA#Lsmnlj5;~cLrD%hVA_p~Qi>$27IC5sj2Bg};Bq~^7x>1}6-BD#&Qy6uz z9(%&)zRQDzy2#!us-Oznt@VpXg&+fYi00G9{KxLB9<0@p)p~!_3Z#@7b8E8eJ4dGRwyljnXOuPXenRQP{jaUgt5Dg~8zx=; ze}8Y7K2LKo@Amp}GFi;GB=EM~`*BKZ}c)aJ`6p3QvREwZBv>2**R7+YBs=D0CBnNAS$GzpJFYWmB46nwIKAcmrV}Vg8ZtyS3`7(G~CDDC*7Z6c3@hG zmNlwL%)vjnx8Eaf+1Zq)*7Yuu4a36pZtYRbGQBTVv^IYj5oKFq z(xQWv6ljK2!j9km5;g1Uh|rzvJlwCfIFxJ+tG0aHjm1LfY}VmMqtIey_Wf13l)Edz zf;#fQ#Nq!X-Kl9V{@*y`mX5AyLjv_@TyV%Cs_#&DvLfY!077}MxegGR)(IjRt{gks zbFY)JEWtK(qJt(gvS~Mmm%Uaz(3(<9Uk!93vUCb51{FKSI$}Tla4~rf14&46Bl7Uz zgjvzHWc7q1pCo)@pcqX9PnU9sj~SM)WUBV4sM@|Y0Z*JZu)NiA@@xO>0Kfv5CE*ID z9Um`F5C}~=w`?9GSAS?Um{b@DqlP$RtpoQ9PpK@myF%Tx?=J3bqTtdm)RoF5hOsem zIW%d(m{7X398dHB>?(68psOu}K!NKJAe?R>=mGf@?xqK|roh5a3n^YRyS^Q}W zf_fiq&j#0xdXTN$g*m8|q>FwL1D5i{M*@MX4ey@Yr`AxFepyijr%h9847wNVRW8wi zNG9i;TN2W3{rvpw>~MtA>>7yLBdl0L74X3HM9^i%iHkGIZTMZD-47l7))$@5DO~YYIg1o|F`d)B%xY)t zVf|zF-8J{NN7W(fhygQSov(&fRVf-q)B8vXL&lnS>p z7)?Z@ZPuQE+8oAn!7|$r-%5MtKX^-)WpF$j8UDKc=lMfc&6X&#=S~f?oO&8+uRdAk0Kx=t-CCXI8~4Jp1^SgF`H`%5AT!(>I7l%6rDwf#UAh#tXHdvA-cp8xg8dB&RtTDdBbhQYwh86vM6Z_$(#! z?=$~&gjwfC#$3#dVIihCYp)63^9;DA)}x*?fDdTo@XLZ~=mJ9jR@1Mn`2THRRsA~A z!`9b-VtTH2#O|b3a+i*7GXrGxnH^X-DQY1hC;<3**;-c>i!q^?o6&=l)JeTvpTgcU zNOLG{68jRaQ%6y`I2Vg^gc+hA6gq0!lZ?p6&vZP{EARq)>Wr_OeGIIgJx*QA!)l+) zwmOL@8?z6C&P)QRzxQ^3PH36wflH+xYag|>9iWN~w;PYG^-PnO86H8t|4I^3Bui>D}KI`P%4-O3S(h{(9g! zV#HWY?FzQG{}U*)_H?=;%j~GogT3FJ)#2*r+xV8BA(J9@gd&MEP12(dWn5j{uloDk z+03bH&20bB&{2^~bBlotw<)mY?QtyL?U;#|U>?-GBbxXnIk3I$n8ebiVP{ zsd&-b6-{BMu{RmQn^Z+5Nwwf(a>9j)qU;hik# zuUdLHOsF$d5Sj?IVF)R+oy)^C0o5w1eO=Ndz~QMU4Tjl=A|d6_i5@<7rAF*VQU#=y zaJ(I>Ow=H}gp1@3&G3p3}g+b`lSna^{bCE=+}!xEDjOZu8XUe%gqc5jMq2MtL^sas$}kC+tNSW z0wTKUUnhA|`&vB$`3vB>gbGc&)pQu z3BqI}VNPn;G_%xcec0w|06NP``(F1owVT>wd|<_@asSo_qiUM}e&Piu@RnHKlwH(x z8%R9sPM6hvZSWuJxAK3fUo}j~w-cvk`sg;}A-(}C#BRVOB+!G~0y~3E;T`+?=pNo9 z?xWz6Mv$A{TB1rL>K>a(o;}{))wUjwd8I9FEWOwVEE8^IpYToN8oQ|i-GEu17cO>_ zL5Tvh^GEOIMaQ(@Z3kM8<9Y%}qXYab5VdMgO4U3=M$KFqeB6mt9YbPMTG`p1%)G*K z7{B%eIH5$2OQ37XT2;$hib|nDxy{wRfjHf(;=a0c0ebHUMeXkIC+;w>FBf$QBDhVh zXLjAgl-F&cfv+UCVzEBX<_Q4t+1c`K__H8=Kr3>>K{m7|_5zC?k}h_oN8=vNXRTq< z;O^mLH|QJjq{*V^CrGI#y&B!4*Y4l+O7uri$_ocM4?8(;xmsm14I!*hy--hcUn4{BPLoC+SJ_BX-31|1!^eN&PMky3y>7zVWiX?jxqOe zuAA@l*_~^+DXxlG@@Cvv-)OzMT)CNy_yKQoZ_=xJeH!ccO}!pS4Ue94eG%W>NJqHy z!><@Ry6@2@eSvA`-ew?`q*j_#ZorxlMM|z2rhj}_JnnHyY`DZ!Rn1yk-xc8FL~|D& z=QPuJ`ue-^||kA4m`zuiSdrxY_q7K9>#rWAS(Y>ek;y?w@sTdfeOJn?Jih z{X3tW|52rBB=9%DkimuzM+Ob75{ipP#o(5Oe3$Q!>nR?spaGynVikiMD#0_&9EM!!Tr|mMv;bP-I=@kL@ z{?-3JLa1UnPxHWs#T4#@+m{=GZP@ioS~mBB%U%cY#AP+=Eh|Df72}7^k|=Zkyx9T% zAVu(%UT2AEi^-tWL>vq!mb`Pdk|h zr1^KgBqRB@C4?^dF;Cb;!fDcsm^7|p3vxiLQK_g;)@uy3EV?ug_m6Hp+B<^TrzXM= z85Oz8wagCQtOi`uN+br_cf|UY?RWd%1Ws=LDK-2L_WP%!N!|2y{r8_32aG_HrJXBs zYxd8NL6Tb{5g4gDJ@f>|c}XL*gh}w3TFYUYxDbU^ppJI3JC}Zr%~<9n&L!NnJVrt@ zRRxr{n}KYuoq!-3X6#s3^&nik1)uYeS60u=KKixKDd>cwE`bIMP?+aeDPap&7ms!h zP=eXh(7lwgdG)A1W>KtqcJ$~We=L@iW;&FVd_m22iD|$KvH~Ec#4^9ZdhNc zyyyvD#GBr{{vYBbPg#s~k}o_TWAwn@E1n^(=88qijmm6MHABn`-e@Rr9ZS=$1ve6P=X+N}Sfp8hzjJH919mopl=b5KK} z$j9MGnG3`3J*b)RiYylSb~@SF{C&+vmVcw)#pvqJ{7t24ob#D<+s)kn%KRz!?dc&mH}d{^tu!uI zu4~QzDZuCN&Q9mJweZoP+L{%LQRpyNr#6z(cv=-IRH&9=+cn7_qwwH_N(QMsFr$}w zA!!2JP_}>3{#&ZEpNg1DP`*w@s@o2(IHI|Xi~IFB7O%8oA`s2N!Yg58h0p)-cyYE2 zK5ce)Xz4++_G%!t9hGh#%I!*sONXEza&Qd}pRqqCA?av};HjXz6(h|%e-9v~(DDa6gnwsn0!lrM6vbk5hcZO@HT_=1rYI==gK zDb?<^ar?XHB3b)uT$0(&(LN}#r1PtmS+g*_Ut^}@Z;atSU0_b`vf>K7OHUde7=8In zNC9NS4~~WYrhW~`8R|=@i&wJm?c|D%AQH62&utJ?O!1+%`7m8{n21jyUT-Nx6etTQ zVEOqWFJft(U;edkWE)0#izfqFiKXQ^%1dJ`3qFZBAKdx<|5}W^5Ec8WOZLC+V;_TP zKI16>txymn=3N~Qru;S;-U=H@ecn;3@c<)WEx&It2k~N5y_Jl|g?D%QKoM@$Xr{gZ zqp>=<;ituKt$h!nSVhCUa-%^iMl2<+rlQ8W_n2jG%FIl|pd{J(O`z5{xa7@wN!ZYr z{RurY0xW4fq?mjW6$5o^RWDyIB-Fym+L$lr@^mzdXZelZ!S#5OCWn$nbwp1HBQ^rN zH;zS*Ja-bd8xC=_N7>1}qelgiUDd* z-Xw*pCBE0N3@G1oQ&C{Fp^MHDfiN7VSdvbiEB(pxPV4)Y8@D419OUo`-xm=mL%0V* zQ4}V|nY*gGd#I&jSDX7ckDrSZe=hq9{iEqlmvZDf>#pv`3jHTnj`fPostv~$V8tq0 z#n2k$X_HDJQLrX}*K8HUJA}<9z>Z4b?aJzAz{Ta@)cNnJ^Q(;| z0rRevjm?kR69>N={>Ck$nW78s6E&fi?Gw=xc?37w?)N6$WE)N2b_F~r&Zd5wX8Qgy z;IG53WtDFI+Kq~gD}gQ}E4yQc=*gg8_6ROEe^3r}@e!^A;LI0?LWNk*MSZFuEq-G< zzWGR@7GSceWm_XZRlvxF<7Baa6$TvK*9&e{!%*^~M8`}hM2&P^H?Q)6T^3;pjSouH zO%IM0=WiFf=?jc4Edl&%DWT9Vt1>vIzJ$+TeT}|8dip+A*YIAN7^Je=i~f5|>?gfH z`dQP$lk4=zOXZ7~f;2w4|IqGoPK@j>4JGRM{~CixYp8Kw8Qc%4f}}BL(SQ6~v9}hqxzYzrUhAv&7(&PAtAmqO z&|CPWVm4P? zt*UL-v6W1v%Nqrf2=^|xt$1|Hiz*kinl$L=B|Lf`Ad)emdqPyNBFT#|D8UQI8Gn#k z4E4mE_-VkwEnm3Zb9!XosMB`XK|ybrpr~tkPici`M^gq(c(c|^v9f_wXv3Sz=i@1` zYGbTO&@A^a66{dzmNm$OroZpe#EO2mb_|h8j9<<8u}9m3v(>t6%lM(?9*9M*kplkU zu(OT!iyZw_Cuo@qf%hkAfYgBEUcplncP6!Vn(~(fCU{0`&uewvyT>dGSh!KvK;sqE zc7){29xF0hH-7Rre&`HYMNVWmK zpju|6`^iKds?kgn-k7>4Y-x%4+g5w~m$wBJesj0>bNX=JdonouPBW9ZM(&fS&tGWO zMv;SNu^u0}ex(s@EUi!#@Z!5Qd8CPhC4eFOg_xL_jEu~S7cWFaM4roi+CN#S%AVXX zdhw!TO~Cy8tSj*1gz$oCO>M3Iaf?yiI6lXg&=;YGbVn_?e{J*e_xgC>JM`ywWfk#X z`L)Yqrd-*xPrfa;=Qj~cak(Nrx@@%tw9mX*j;QBY`wDV`xPoipLwtzl_#WkX5t4sJ zr-I17A9O>zj&KUTw;w8imbI5uTY%BGzobd|qxa}KK75JOHV#r!Tj-muc;JDN>cD4& zzx^^Bpa(Ra5`I1MfeqWr^!kbk2RpGfb{sPu^Xdy?7GXJsk%G`W_xowCxWCro**eha|&^LH%*=zKEPMXw6F&cOW5;A%?pM(&!jpF3t6(TF=LxT-j~ zuZX-%k@j$yz}KiOjxM;EF|*n3J&kzujmzF!Cp7A^2%-2q0{=-?pfV=+ij}zH?%u~N zDugYue-G5TY6mt}pej5{^;vGI9o3wcN1|}}xBoTTr82|X{BOM_tWX#s*n-0<8uEj5 zhq?~l->fqN#9!-YdB^=86PMG5FHbd5{kx79 z!wN*0=JVh~e*D_55c$&sBaM{*&hjUM>ukd@cEhAz+ZKh@o!=7K&ZQf>^&j4N*ZhyY zLBR?fUl|k$5AxzBvf&Rl9$^C97ofYaVo3n>nwTv$CAQF4fi(b=q!4p?^G>4p^ixq9 zd1X?aR)-0ks(w39saQ^VQwog~gW?2IHY1HajXDib3J>QnCs<31h>-C1hLApDv=yZ% zg-&KP3~T4!-yk#IzRJNF-W~-USgmjH*iqT@E(sVpgI~LV#eS}5wforrejM8wTAe!x z9gL{-@Tp!XU5#%h(XTMjT!6M2Rw7XpaMc0%Wl~D0%CKR>S+BPErI7-g)A-rh+2G(H z*Q?x4-=>BucRF<_g5|gAi_C$qGVLwypFY_Bo7xa_H)M8d3ndKU_u+;EW}2mqKU`n; zo}Xra6Y&50be|y+`0(KN^J+8V71JV9mEVIMTOO~)b&zM!tgagKKB#$Ko5ytX@ z-V1M9FRXh&$wQLKW+a)JjI5ra?wtRce7h*ppXS*$t`3*NCF^0QO<(9O4J84})EY`m zzxZn{`yNf%_4ko!?=B-<6Yd)U1oVjC6sJq@M(HcJ8SNLHYz6nu!HhnzyV-P~8&2>z zH{|l!8aE`DVrqClH03ru7*~XxVJi`+pfvM>^SP0H)FlGC2A@I$^h#_cvG*?)Cc zpQiuvxCkf;w|L!XOIyYbRVAd$(ATLHpfwGU^$=3TE-R7l6tQ8QM)Z;H;O;I ztHx2L?=l9=o`1Txay}*#O35_JpUsb*>Pv{W2b+ixb&9ld=sYSHMT$|yTFSE zwm5kWF-17Gn6}o!ox^i-GBR>=QG{(}W#dC#$JOQJ`KeEh`8WTzD|dhY+xsgj)6=*m zL#Fj_e=TfpWQE3kmPT9GU%YEcTMAl*)30hZ;$I64*uEn+MBPFP{JTB*_$hGnX;@a> zwCn6g*NUSzA7>I0SJ89;t?5Wv%1UQgM9mR>n;&P7PFP{YDlwmJnc9N3S^-}ZQKYkm zawQKk+hG>~2?tFKVD5Pth$si$q&5&VoisEIBtNGZ*r8TP`hs5O_4uonFkJxA%VpU- zHl1;!-;72c@$5TlGSRirz43fBMUo~Rd{Qt>lK**HoFo=wj7MGk5HSVx!+ai51i^`+FX=6j=ENW^ zf~YKS-NoPF^h#B46ytc*vP?O!$FMNKHce)J67s3jeu3ChoAt86AvBp^0(DJ%vw=fU z!rrpmMl$a600%!N>1SmkRZzDFSgE|lSMr+fY+^R3r1U>t?#KTf%`{1x);n6VrrIOb zOWuqoizK*nBv#w3CTXZI5Cc6zv>s?pnn*_Ydk%c@-^LT9(Jyu_5YvA7Ufk&d zkZ5IQ$8rO`6b)KJ4wq5Xdy)Q}rQisMnco3gMJAn^Z&WttjQK=l&}8uCJrfB>m_k;u z+Wp5d2^hK>HbFf$`jlSHA+>$792-D)~5Qs&1$9w^*a<5dDe%&-* z?$bf&dWPl%kOwU&*2Pa1S(n?eXW*)`%C@Y1HPFtI{Wp$Uby(Z#p}E*A)2;DdaT-_# z*;T5$h&vEMo6HMEPmsXEDccriE3#`pC0PNz)-X)-?q~57-NU5UZHU5YCoXZ4F~L3{ zQ~Z!bh&h*u?q1^uCm@x)q{=hu&<#65lSWvTVsQc=>QkW=3w%1V)lk0I6&$P=M@M(} zfPVYw5czc!$=2?XU*EO=`zlA)KfsJnEH!D-yH`h>HVW@kM^1V!2j>Sja7&|cn_HFJ z;UDC;H7ceWkn`8KAAWBgOwF5S#!)%Ht?|h$)<{TqFGlQvEKX8X8dM5_Hf)MGtgGsf zuPZAngM*rt?|gEdWItZO4mXBde#&-zy79hUSW6Qy|0(VJ$Kv0pRJ!WzA|vX=gsaz0 z@5$JoOW_l)0tnbsgw#Pm-I_q|r$57wpZv8^X3)AZZ*>3Ab@>z$1@&l1A!#ARlu+v% zydVXSwy8{%!f7@LfDl+nTwoNTcec<_6djGe?iiDDGL?xf4T*|NP<98%gTkt{LI zEluJN)C%tho+>l)XK`W;Mj(`9&meT4j!Fyi@$49^PNL%fI=(G~9yuUcmsJ|4dngVN z7-q7<>fmR-!&h7?CyFR1>Ji?UvE^+1t%PfD^>^z^i)9C#=&6*C`w0}5powQWp70Ix z0gZN#-dF%9gJFh5z_f<&b2C zP3xxG#o6Tq>7I+(8XfZ{#6snbTp8}eN!7-El+iEHVUY?*GLM1X&%Oj&tlLq;*+M-g z(x`A{m9c%SVjFY|>>Wxe;PYo((K>)ll`v-9EHa@+jp7XqNghy-n7pB8scD5+{Y(6M z9VVU`ruT|liofv_B>88ag@u87NRn$7u9~1Zid>RqHRyTI?!$X_(MJ-U8ZQTYf&s82 zHc5$lSnQoz{wDVV-9Ap1VdbK8aBHe}a78P@2+4BV}*X;Vh?>P!fTI!0|}eNaR>-pu>>dftK~Nb19`L&As|-z@7k6&G>=G3Z}E_%uDQ6iLZ#d;up$35C?#lj>^WB~;qY8%iH$06F&)8W0v*26LC2^3sZ{B*Yxy!%h--%0Jf z%SwfYvynBe>F#Bq@>v zKE)E2LiBQ@JsfhBMV13K0PAEahkF3`7z;Q+MON6hP4Y4Th6GLBbUXviA~+7~fMI-* za%Du1GM9H7fxo{D^3vmf~O|?5uEY zlN$|Hd%UFaAg&{Xd^Kf21$%m!a!Yyiyim#s6PxygjH7=yZR9#M?=g^=R|miU#gaU& zC>7~othC=5f09D1#;Me8gGq(*@|7-SNcNX9X3YTeecX_J-_YbD_mPhmsgVx%3~}%B zz4%%s;9@pY<3C+kT6<>*ZoDvZ|39-G?EjkWL`aJDJ{zp&jJ4{~^q2^n2uPL8p?X%f zTH7vO7?hr*X*nU0gVcvJ=^Tr?$7C*VLsE}-xN^auD)nIS`;IJw;f2VFUCE`1MsiKW z1j=wyYzp^}kDr54Ro)N~nK-;9RyT!mjDaBv^h5FnXyJKw)rQlCl|5qyHHVc8^y~4P z99q+fa3ebeI@NPD%W?zv1!z8$L-utU#(~~fF^wiE8}Q2+c?f;xB&^W9`Za#B9!h)N z0~`m)ri^ovk0K`RQpat1Q`Q~ka6)oCIZ5&FoeUf1o!yqTfJvKH$c$L%%;I|_30*X5 zUZX}74i>bed#Z%06dH0zcXtm^+*`c(m*;)XdFRY`W|+DEm>S=}zfnuU-57*@fSb$#kQE)R=FZ_nJ>CbW(<2~zaH$(Ga! z>oZjl5M`rtiwXVH2xg9@?T@>y+ zXEP|8kyxJY0jNqrs2ZX5d}TYW_?^d)$_CShQc_g9 z{E*}oQB$!@XaN9kRFYq)ce-nVs@Q>wZOTR*w-M(Xp|qa#P+yp;;(xmrTO}&~8>Pse z_E9-thDaDLQ-&~kjKNZHi$4slhw={IGIj9hd^MAFhfuFi-ppS%>$RJ4G-hXl99{b z%+=#DQ??uqgLuIx2LjVb=-=wT+R<)?K;Qp>c0&82E3znpqaO=_t^ljMs9C^_P9LMG z4hFmZW=H`b{%;tacR@G;%1-}KXe-#56V=1mlLR<15TQ=LJp1E6=el(5-OYV_{NDxY z0rawu5L>($BgC^G>To20&D$=D)Fz*cPb$76CPG>yAEYI^OwUraYV#RF6#-M1SGozm zQ1jDJ4#*Vf;7qcU_`tO7RNKP00*Vn$sEHY`&-B0|_PT7F^PlEAmgq~>Z4)tAzZG}M zr?`?ezNECm70T2r_%9%`rPItjS^csN;zfW)1g~<7H4adkP9-WG?#rzWwql;wfF}JC z=T|QYSySSvLTW5o7w-YINa&XumTNTZa^)y=sEwzy2%1izaio(Pv-OpJWk;G*18Oq& z$#tcS-YL)-G4E4PC%{d-ggz9g=BoiA!g1zYM>hhY5>6aD*AMW(ywQC(Ul>mF>Yj7P z2hZeQ8`q^c&$e#c`Rp+eOaeU1bS+deT|RxR(We-zyDwf(ls+FVX3GDL=si)!chX83 zTvz8)%XIjO3kc@eHfe{85>->Hd+OVuSvfsS$egG^GkvtH0MRMPqK$V=wN?}1;&KVJ zw*R~4Y_zdCj9GY@CdpAh7j2m5VPs!Hb{AOV!LtPze2g()0*!>Xo=$13ZrDVWw*Ag@ zycy|V7#R_aCsvM$j-3pv&K%*s_s)mU@K`UWMxs)dc~c15 zw(Ie{Z_$c9uMiwZ5*XEx&&$s**N#w5PEM?WI-ZZlM<+W2?jBd2C~m~sL#Bb&hK1X! zaCkGj=laP>`$}NPgTI}Z-DUq?L#`FsB>zVz(Li)w_9!S`!;Q0t+41=*tU2$c?&Wzl z(#wxxJ<$7Q>w0vRrTGad5~)!*)&sBp;EFn9QnC<`{250?+EUOv8rcP#)`|+P17!uV ztTvJ+%8FKeorX>5&mkl08h3M}ccc0OID`l$kWF2pt?-VqO>3tW_1U&>zk_^VI{$T= zVHBh_hwOy>00|hQrbqrgm6TK9OF@@H4=PF!K#gS0Sv4ghj|SZk{^Q?@r+xg{Qol#` ze99V)-Tdn_F@As;GgUY*x!)Yog3(|POXQ&!qv{1V4Sh&{q%<((5cuFd$Sv@3Du`fQ zp`6W%UW({P?Jl}bd2p?4_dY9LH)F@eJWHOBwdIdUOEIo~Df4c;1(hqp_wA}CY+OR` z(+$vpy$HgDJgOYf9Wtd|N|KeD(_{0&G=iDRbXap8lZtpv&pm@IN|wRt+v_N58hh!U zX{hQt1)FU*o4D8JkO|6+p6{>!NS^*UJ>~t6fZg{v%0qtna{Qao2Q2FzlK@(K^hoS? z90Sfv#Bs!C@7#DyW2DD^Cr}adpoQjR3z0X;Qa4%mrF|EsayZ|+=$6FDPjjV1(gX_F z4)d?x0c3Oz_VShAe2HepXAVw>tF@{3`GpJRaw*sc-?gOrioQ`21?#`=NkRLp^R`wV zT{6uRNgUvAU)W_ZMU4`f(wM?LY%2H_*@o-Tl@5EJAIp}iRJH)HrQXGLfQ-?8Q#<&Gy$|53SCya$U z(^i~$A(3# z%X@OMyxb*k5x(@*BUjxIILYIdwX1Q;3@$M2`1$+$DMB2-$G|ptLGy~cWvpyT z;?O)b>}!FBioyxoDy#oU9q!>JiW#ArpuzDkBh zsMxAcx+eKw>m*Ju82(99h&uSMJ-?4lhqqZpTa(C(wS#BC?o97IS+>gAi9K8A5-8x# zKhB&@xU#0%>C92zzmd!D!FGQ{5~3+bo$gz-aMswS-X+>tTU#stsN_68G0rx|t~9CS zRaSOZwZ5{lzV^^~dw5y4_{R`|X21j5FsVi>wf`8w&@L|siMMv`&`k|tw0ozcaDH}A z#KISJ?T;wXfraEqm4eBGX&9nMA{uCf1dw7q;PLNXZ(F*P(Wf=<6!sDm2zTa8A%Bz! zqyYuDyQE5rD_O#}uS!Zs6`_+-Wdi2gqmSB}VCoHpSk0Yb3B(#=U9sjz$}6qOm4(W| z`VP4QPe`Hy>pi5GFAIE=JMGa;Kg(Fd-%ymQHtv?QCP`UiQfyGZLi*fCMU1?Ly`B)~ z_piC+^P5{gAD5<_2l8$&0fErR=&1ef$;){bxn^{Xz-gn7e`ImNo1?D{S=rVP#zYu$ z8!t#2C^4dR3BE^!SMY3M=;5%0t?zfldSQJ~ttV>T%&dPIvR;vlY$BfupUS}loFK;& z9&EmT`Hm$A}vwGVQMo8z_S(V zMo*|gEdx?Ef71VTsrnBb0(I-PdEa(|->iOAuPCYvU1YlLI)|o;rN;>P8YtB0{r1E! zy7Fyw<`K(dBj$*ZN|WzPlW&=|G2$Z|+LgTXFV^|4sSs^T7d<9D@7s#z&Zf(niF&9@ z>3=BR9#X1z)McYiP%5lh!kiZzj2_lG$L*nmD{Y!0n}}rw?{H6a{olt(L1?6n^TfY=|np!CehGJccggUa*?&vN# zC*hXP2mtfYl=F2TJKuh{P7m%c4;QXDyP3(>mx=BnqA-(oHih;lrtv8-h zp!GGCHs!(9o7_H5V%Ko$k6X0o02l1@Wz~eGmJo@y-h$@kYtJ@AtAtTa(>Ud7jab@? z>CeNp8l3crKX88_X^v*NOXpohq_q~<%_bht{;pI%yGv`*6)4sI>8Yc92q{u-6Cvjy zYWThEXzL=@)~M6r0d~3dw7d4S+m$`OfrMYDmc+f$=Kr|9H&sUSGIfj8EOlFY_+LR`oE6${d=R;yvd|pV?>gXIjUsY@Z~1~1*W2`&2IF#zBp&R;zVaLN)`UI~ zumy?`mm>@)h;4sw6Shv=)f54g^UnEON$mP=J+e0Fk5EAL_h*9s{b zNTW!aNf@dIFtTlW(BhLffv@Y{ttwG(mB6Vzmx}v7HNur4lSR1qUkdUCNt^nM5N0pA z%DxRYK{#FM#%x5$=%si}1ZFYJqXlVtvnN(ys54)tHciC{}BX0K~-QFGlWv3sD@snk$ba|TGz znltvv(1Gvpkf%X0U4g$Q&B*|~)|%DnSyAg==X1PL^5!;5R|ml`JM7KYBkNmRUe<>P zblki8S$G}qoz2>@=NSe-UGA3eAHkKm;mZmzwMy-*Q`t&*R@TwP`1r&G^6J!_Mwu|& z#%qP!ZT1-V3@qAmdocXOph6K+yZSJC`>}dGsQt!y`#<1Io;`vw_&AIx&;2&N)o<1wCDsrl3qc<1Q#uzRz2v$r>~SLszFhB(yTKdmAgwMW!AjF&B^ls|`LAFM)ic*~J}C?1M3emI-+ z%1Hu(XYAJ1e`_EP5fbIQ-`-DzbQ6t;hZUr$=+v@NKu#{z43?nKlt}sREd;+K_fn4a zZF(rkxd}N*x1~JX4Bd1dH^7*#;j#NQ+mKq{JlI$|lnfDs;taTa=9qK!4#Fz~->HNH z?Nz2IoS+=LUiQ5}4*b@CUMFo+a?jKMRo>4Y6#yE2U%2P=J`Ev^2Kb#=EBdYG@lEeF zw#4GxzY&A&@#2&{m03PT-jFVT0|}L<8chvN*zN2?Vlahv6O?)Fr@oV1Cj&y4Yzc%} zUDLlD)>n!ugExwBy|Iz@Nt$4P5gMaqGWQSGZ<3;T5w6^hYZVM-sw8 zOC`h}l15dk_Ik5)owA=@qn9VMNztDsrSX*}W{Jb_bPph@>D5nUKxvUvSP%iV5olMA(QDX^dWMPm~%># z;QQd~x{OcwE=Zb+OVNCN{cqPPhp5>VqJ1!T8BwooB5Bfhi${7e7J;XF*zvvXt1 zCEK1acm5Y@4*(_!PCL3*h*PH0j*+gP#u%=ZX3~0dvJM5E>`DGTaz9vczq+^otFXwa zKF|MlA9=#)`lxf5j=}wiD)P(EDB@V?l!h>;u#A#_X?BNzwodC(iwC&PwG!rU<{5Bz zF%Zfkzm7Q6or2?pS0^1EKEXPkcJ4>Z77ayVtq(f`mvXVVT6<^uc18x)U%Za4Z5uR? zJ+pI_D_2k5+fFME5fSQb&Nk1_y^uCL7B4MF1E{ZyiOb&B?s9)6W_pc5yX*E0Bs+G| zi5;%r5ootNZ~5Z-w-(^M;kZ(dhP z8H+E)yuHS*=(5&$)$&c$lG3yiByHU&K0}QrDvei~8yQp?9#CPTSAI`+*T>YqW7FS9(*KWSi;+S&d_6Vp-5~c&9d{C^b4q+( zVq9LT%FQst1K}1@iMc9Jv?dYTW~Ud_A7#jEtNMe4_dWe-m@Yq(?)N#%q~$T!Hkm_L zYbhi>kt5~70H_9Idh`z0v?-e*Gwo=K!N4Szr@EB^j!IWN?Y?wJ~s%y2*5amjUxRO{NP6RR{l#T#r@dzS3is+Lq zAi8JyC=g}raBy{@K3Dn|%LWV%s5$As=$;CVunco9yg1*&uSC+ozNPLkBnOQ53})5V z2#2be25;(=S_EcQOp%|K@+tEzUbgkz2P}acuXH|6s))QJk9!i=rE~?g}d)N5+$fY{`okrT8ZUHc_yq3QGHk`CQ5u zKLxs=68!Q7wabQnyL3fuB22|}(&ieVh#~9>ad&uzE8?C3MGZS|cDm!n2l<cf5g zu?y4e*!ipfh;+2xA?|?5u_{_bX-@8YWqNQ~*jF2#Utr}1gw;mrx5|SN$f(`zA7ITo z)Yq(Hfl=cKDs*n7+crn}-1hUAdue6vGQzRb&$olyuTHpA09$-)rf255-;O`sk=3ZJ z!T0#=DHS4j`_XYNsQ&KY@RDJ&iT|fqP;1@w&hmsh!O6wec&wFhwjMj*pKA7p5FFS~ z0`LKrDWw_bYtGnVEK`@Sq-+r&##=l|ppb?LN^9g?mJ7sJ2|E}Quj!S;Xc(n!u(3>W zFdDV{`ypLCH%h;I{uvo)qhgMjKX5va(kf% z3h}S@xj$hOZA;`0V+xSsHTlS8h;5dKhnrcSV}8hw&Hm2F*R2y+E6af9@Bh01phb>V zttpr!>QcLaw15Bd^wNz{ zm`1i(Gc>3&L}gpBS8R`!^Hu3p6E#I>n8is-O2%|@QetvpqPlT}ic$~N&6dd)mH;~v z!5%B;Or03Vc0heJ(%nwJr1Y3z$-fWqhE;i$-jsSYwGaKvcWlbr^Z*wzIOg2Nt+uuJ^&HHN3{1mZS2iAp22Ho-Vz3oq$(&E;gAqf-OU>`g z7q+Z_n#^K<8e?dXd|2%fdqTfj`LvQz&ZIt+xKkjzvq=EbA#!P40&#^k_y^pq=f>Kx z#NwVAz`eb%my1SKC|Dgn>8|*=I(Tg#UCJp=o^7lIx_VsxjvJ3mUrb|vw`hd)HeI%I z+Mah6ZdtYph4^2tKh3YQ@Ds6(TOdqK_)^9PV-UVUN!4@p^RfjCY`m?hdm0d4O*@rE z;n_Hx^Ptxar-#sqD}S%Et*f4`lYW-V*hMxrc)S18&3yLgqSFQp>i=hAC3HRx%rNPd z_osbjJrD}JbbUCxcjefkhA^W=uSa1?bA@JVK*SjmbFT0Xqk}VB*YYp3l??Fa65-R~^jZA>XD}5HQr@B=6AqT;lsHkL zo};Gt1Pm>g5%QJfa#iE5f1HD74Z0>7u_f7STum)%qYE zP`i9P)cugU&Zu3WvxURU#>CU4Ntk~==WI@R+_mj1ZK>vDv)!F1Y{?<5Y!&+|&W-yt z6CuKO_w)TFIPc&o9H-p2?l_+nr_f;Nh^NjnZ#;`9-vzW9?N@?k0MqLUGotmF630s3 z#v2>a-=s={L1X*e-T52a*V09aT5T>?cLTeln0J!&xzRL}@8p+&JxDg}uUZH$Y_{lJ z#`cr$L=sxfxe_&)u>!=;75fkV1-+;Wi{}mEbEXNT-??C4H z>-O`9am?Xr)Tx-^)NG-3wc=DWBm~TH(zKJ)I+)1WAg!pOQ4AL`W~DcWCj4~93=j5h zp72H)55ul!csO@Wp77;_0-%!m26 z7{J%Z=F`-a^4>?7eC0wnFWOx(IHHQpy69jVGu*ix)vcG)mgL9zKTzC%U)i`K%qZn|PU62p zvUG*2)5yvzcg+{~L+0?{0?{)7f!^zfhWCFa;R3w;F19y9h0)xqj8@P<&zt*YHrwDF z3&)Gc#~qoiD~r9J?^;w>Z4Pz$3?vxl_=)<|St7rU*p~Fg;drx5pLI zqQ(N&7`@&FHFT^P);KxwEoi4H-025c_*8Z@)iurNRDgk++P3q%wjQXzny@qtsx|UQ zc0rs*2rrKcZ@!Ppa>x}?pGa=#qzs*PaHh6i*m$N3rrgcmt@Lg->VN- zqR&&_SF2FsN>u1e;{2QZi5vq}63J+4ALHMecwo)wAD253<-Eb)9#Yo;!ymII%|{T@ zIFJP&NpGS!L~G1^OS!Dyky}D2l-NYXk?e%<)Wf&6gBbUFK%IDV>};3@q0PG$-d5ST zAEjV3c&w=)`@?-m6#n=*F2s~OS`x`XtVn5~Z#rKnyHVp}0@U56@g2-=)x_^6tham` z*LQF0?m$bYAKIwJQ_(tA46?O6>q6t-qRby~d1QhWgKhH6=OaW|;N z@6Xnm`&YuCO&x00X={dW{#o0C6F^kDHOw#mc6;i6wM38hn27x6QlcQr**i< z<57`K^4Je<{*kl&m_Otb)~9cia;mfY?X|`6O)7nG3sa!TC5W2vZ8%&AmNG0^8EiSN zlonqZj#?Sn8W0r)3!MC}T(%EYPPQ!eMGplbe%nR}AbLDuGQe^K=|gsMEW{bm7=bPU zw2q|t2cYJ(`VQ)F*bc&l7*q8pRuvpKc=Mh3bZa~re;xh$Z|{)Oju>IO#B)&L6XUYA zDZ*s*gmkVQSDs9|@`)IJ!JeO1))N28;9JO(vTVTJ;5M1RHjVvnwHG`8IWr--sm$u$ z-DPJjMcgW^pNEMl1rrvLeTIL4X>Z4w4QJd=aNhcb;Kz|o<{!=rsSE;hGj@;PD>O*oLOxhCg#D4_nF~fY z{C9}(RXK0mV0PG*&tTZX_HNC!u2!G9IwjU_UJa&IvH0sc)z%E6Wn{Q+Kj+o7 zfD6)#Tm_FqASJF)Xh#POx(sr%2iG_(EG}qkld-TEGh~n%!&+P8-;%ST+jQu1yn+j=Ip`z*{XVaabNPs@9zhW?(c11pV(AY&ue%2 z)v#%&|0+>nG8&>+AJ!517%eMRAkRU3@ems5)U}W8EsB&kxbM=Ii<1twY|aA8(j>qw zF;HR4?$iKmYJmZaajrlWyswO#NHx^-q__fyx6nt!vc>JcJ>Q9AXH8s}umQ0v=4zza zt}x{)O-x?qVA6Lw)^n0z*{a#k->!TqD{x#O;wz@&|6)Iz)R%)jVQ?GddciTRwqaB5 zEHGNGAszmuuDy_rNnjSwcpcO39lGdFmWR%MP8vqTO^jin@`x~v@k4s6TQK-4slzbXi&B!+D`8V%bwAMe%C%wQ6zT8p{>3 z{wFP8JJI%5@(t_aW`5U)@wLFV$KQe16DEgyjy^TH8=#uAW;;Ld(b>XGpr1{r?)rN9 zM?S@IU&o5ZX;2Mo>g-)gzoV)h9Wz*-)nb~YfizbjGa4`=nC~yAD-G3WN*%hB;h0tadrH<@~1Sg=;5^#o@(kICGqzwt+Gi9^q*DqaS?XjAE%{(=)uiaa= z&zM9^&e>7;06_Mun=b6B8Oei!G)L7@EL4a6@Av@P!V+yEo+*zgzAt5xD(StYlq zPV!}5S)aKxQ2r*E&0RQ<4d$nv{kSs|7)@S9IFb)H2z#rG>l;7tjA&h|r*JX7m!)Go+VuuqMa%DZb z9gHh{;rQ>*+uy8{5v;(XA+;1WI2D=aDV+GCs$5^10c#rLWAJmf^`C_Jh> zSFhfvqv!sV#`S%QKxeG#<|Jxdo|^5*w9t6iH+*y`%bTW*B3XaA6VII%_d$e#d!cu9 z+5N$R#Q;frbU_?EJEMu^B@g)3HB;id@2ag2*!&|H%;va0ZlCjh{O=n_I#>V*17A^M zVyLCwew_=E1Z?ftXcQlETXVicGW0+b#jwH5Fi|)Y%)c9MxRlo)C70C6H%6f7J##WW zb96Ifgg60)HEH5=_}Boob1ojl^rt4HI;v1n3u0QR-1P;|===iLup#kkX*Vs9`Hh3K zS5NhXPsi_9K;R}A6uN5f=;X+=NTwGzU~~$+F5nNylMlSYiFmQZ^Wk7Z`K3-ZjHTOjIEr4{D|!B z92%QQ_3TFN5LOB)EQDNl_FUO9B}voL)LBt7Tj%l!N0on~V(D-j&dfL=m_!*j$rVmz z|GtZlK2Cb?nTPO`m>1(_Tm*8RD#u5RZ+74C6P?MKQ3$Z#6jBAz?qEuwY+v{|I7S&@ zc~&l%s*$*J<;oUoV(=<-CYz(?>h9MFaNJ zt0(ktlx`qxFy-b!o4QO*QiwdW`QHPqI8{84Sy-A)L*R&Mi>+!dpl+k!{d(y*%w>0s zrSs+b@>9U|L(S8_^gpnNy}tQdoyNdB7?$#(-D<- zkTjx5f#id&pw`Yjie$fg1d#X@Covcm$5y1LD`oIWc_#E=M}B3dZMY#378U_SR2q)w zOV?{-Bosd0J*okQkiAjz;pli|#;4>^;o&0Xc<*Pi*Y~CeZYEJ`ehb|GCdFQDlg*5BAxkK&6Gqz_uOVK>2@OGTwM5N=Xb%5*Z-BauVM0+Y?zH4^jt3 zl(u(Ya*-l;4!PlMG6$b)-yjp22jHOd(PjzBWr(PJ+m+nMP~XRxCF)Yj>5$LqK#|UZ z_7sjEY82KkuFCwU;0d~!>c^y5l zrWYUoP4;;1`1r!Wy;f13W(|#o1>sn>u|bWKC+b8MP%}LS>Pnv&8^QHkoDINarFIQM z)t_HkvUTEXZUzY>f=h9lZk^7^*H_v+>?q2zDc0{dd$XHAA+9?&-QdvEP}ohN-+PfU zEi-5b$N&tm+x+l$S&hwLjP~+Sn(f^eIb_aGhRs@`INu^=@I2!@X^BGFG^x8#L$@(+ z<_REn)a2GjL?VwJ|6;Hw*qb&bMg~EhXa1i|n>plzLl~YNx3dCs~yiODkFZZ7?OKg&HY++}v5v zJ~)9rF-DUaYFM)n$}n5e2;#8QBrOpiD?4A-2o1>xe33`drj6adtMUR^KJ$4!sc26= z?SVf%UEy~6oyKiE?ReF^oWX9N`FLGo>YQUF#6B|^0RC1hhPivUNjW%(_>haC~=1t()xjnAJ8_ixzLD;wlq$Bt?2)7?4~UR|2`-gXP9yAXTp=8SP7T@kT6jmnM2%FG3k+-XU)px^o+rHcwicW1@WPt zj3Q1USfmj78zeUT;g)X)di=6pl=pvvKT1vd=IpvG=D6C2e`T-QA;xq29*4SqbKaBa z0>*ul#pV_60l5+wav9qGS9attuC!f1ljm7GwqGv%5?cTim>p>tq{%>cJhx{gFMIJ4 z1aN=2enrd{nqulP>;JU}+2~YP`@S4?wGQ3P>5z)BQ5BsiceT0MT^L*R;8jZSo*Zr~ z{~t&1n;(wZR*insPs-TS;tvIH5Z$oK19i2;8n6D!2Geu@Kx~TUIKp?oE;l-2jYTJ2 z!3Z`5uuq)cC+5bSAmT2Zd9yfiXT5TYHro4I?SJo(?g>OuDZ>8}Uo5%^a$f4ggc=G6+;vnS3vR8eZ->5ZL8$9qo~!0^*O-v9_`d0l5C zvN{b>ud;6F5OFH4^pCa`d$Zx*w1j{AL?+CKjFjk(A+`;5>%pw!r?8ue=bQE46VG6S zD3Bw?S_kaM*wEHOHW$EN&`s|0Y<u?=j?hf}OkG*v0!-3SOlhth<>4c$7s%#DW1as#3OMh4OOPY+bgwnR8oSs zh;Rp3ZjJb9vp=5BEH|@<-mMu;{Cu2je!d?tdbt=xytfTj6Z$aEv!iPaW{-%`%w6Xw ze9qbOeVs2#MOr(a0OUonic83iMniZkGSX4Pw^L7=^GXhh$mc1#DyjoagV-ZXVXCGQ2 zw}=7pY?jGFP08|R)ToFaoG=$v8%CeQD`H6uMQwC{2|r1*w^4W#GZj=vUkTB56AUMw zW~lD5v!p9zH48h1f<)rlAQsP%_>Sg~6i<8g2CFS~Q z+$nTo^=A#`_+wA$&Ld_H>Whr%k!ea%w$G>D|17f}lLHsEdZJ;RV3A{9sw0CCDPU+| znwYIb^$`K~HMMIbIh(;sNXD1H<>9a92kDkp3LTfH_xpm^$6j9*{{W%gzlDnmm7o%3 zJ+Qlf@C|_r&UDT6a}L^QbQZ8j7JSBqkW)m(o}Cbz?D<(B#)SXOxe(I-w+`jJC?y*} z>kQrgX}2aM??l{M5mQJxstMx(a;DXU#FspS48dj&Ou+u|ED^vme~6n%UR`zPFt*r6 zAlamBe-yJpJAbdbuA%{iy0PC_OdV`06zrNt7uHavsbH6S0U}Jp;Y~}YwAIyH1{th_ z3~c5XJ)8M(nzQ-6R!*;dVGDA=)3~_3d!ZN*ngGK5eeT$96au13{t>wysJ-M6d$)Ec z?GBDX>EO|81D`(KOd##BzG?Qvb@~H;G=8~xlTf>chRLz9(&qE9N#EE=zTWELd}(YI z)C^f&w{;4+)wQXb*V1Mlo#IUd?yH5ni)2R?7j}D35uHO{D`&6T)@S~YWK7ep(H4T} zqsLhEq0^>Zx?cco^GA)#aOL#H(;J_!eu|7A1nrf;awV9OmTO@b;3Wh+}I zMY}WqR31n^*Fk0`jg2V6Yw@O$Fro&Gm749EmS(6}Y$cjACg8kz@@(rhkGTs7j8q4z zfBuKh6&!f>7-zS*w};+0n!L!S-zg%<{l$RNsMUY>3hMSJSL}MRv-4pfJLvwdCg`ET z=xL9r^9k-0c>7=kdlr^L6NY#9OC1giU5CQ_)&ntqjANPE#Az#;)nHv|}$n5&Z zWo_(WYb#Zae4~i&d1LA0SJc#?YvZ)Tr(I7r`cq9m-PwkkFHuW6v>f$b8b#7q;0Vk{ zsC_&?FYDkC0PTRTSWB)T?+NKN^7DB$r7ddLm)P?Vo}7~;P=;8KoyY~myB%ZIM}Q_S$-xt-xw&A4ul zXbn;ng%%%u6?9q@pCnf19mU>yD99c$MziENF5vIXqeJ3AD7bQmEGkUnJ#L&`{B@XJ z?k~+F1h?&H{x-rT-rwuhfpKT^S31dFE9b__TlkGpKMF+TxcbdVRIM0M%7ig<#Lo|3 zDR^F4aCA5KT1XqrNfL`Qi>_Sc@4Fqc{=8BLMTELOP_e>ds8WX9W4?X-UjDI1Eme7Q z)R)O=4$_Azo_X0u44q-{|zk(&{u{FxFmURi*D^a zSgT@^hR1qqP9zAI5=LosDP@Vg2BbhSF&l^gLYF=xYcfY`bVqB1E03BQ?#NTTLg%Ep zJQ55BRtfaPJ7icFw83N=XTj`Wgsv>RJ+>w7TEWU`*Cou~t#u98 zb#&n%12`zuwi?_>YA39B9JxBK*S0E5c6`i&_&>RZY1vu1Rqor8M6hn|09==JbPd@E z6)JSNHltfVxa0|^M^$Mlj9*{pYWdZ`+@{ur*Th;Ne(w)%))7%JIF1|ZuUkFHa@GV} zAVJ=ULe9YTyt!FvlRuoY4DfNNXxErXzs!{6%TyWUVlXDnJBEN7rp+6c$obkvf4d9A4Vwr5T%!gAJ5v&1N8%%3PJjwSk~m&X@ocz#Q^NQT zw~c$mI$+2s>CjvRH6@C{6p>R!jB(#1VI)F2X0bKzS`8XWtv|G9Rg>?-M+&0GtFmZ- zTC*Np31Fqkq#gA1FCV{00i+8eJy-Ly>1VbomrYe0WD9ug^;BFEybVFE=dn^-Ac{txSCN>&iOa25_mDX@woRR=;7EeOfW{v!c_<6 z8M|ugsjH-11}DPkzsxiRH@55R8dQM6z#1?Z0pBfXX=$aWX{TvxX&?V*{7@+B(5PVt zFQ)S^bnDyA*hKbuY0!2SL7LVO%3z_5NwxQYwlr!P{?blgOxNPq*5=b1soYg!djCc4wMosY9dD+l znqXuvjcbc^qkB{@T?5oSV_?RF%?(TEbJ!-~o=B^#olk}mb*v1E9U|BpqpxJL0G#zE z1jIONFo*nF-!~ADlNr8nEGnIStOp`?!xCx{#E=BgszwY`$4HxL|L1h`1N1js^7UVf zgnq0ec}bV=vE~Uh-}{h?&IC7YyT4_>^Jy%~L|@1Z;cdrS#4}kQF?)nO;YRry%7Oyp zO(=f+rNEP`h~QMkP25z){JBRyt&Q%6G4SKEcGmX(GkfOlJ2ly`v{MoCS8NFdS0Jw6 zl0J3el16wB6|FR6f)pNsjA^?VH)BtZ2sG&s573R(9CQXXKnq|~@x4iV5H*$TQzNX( zv*;?h@LBW=F<~6s*~Bs5`nYS}eXm=Kmge^~^U=SEn)U77hAi-PHs=tUo?cjnuCH)j ze&m0CcosMP*cxK^;I+!SL2zDE{88)rYvKo zKov1B)0r;QzgdT|V9oIE^`Q8fBz?);QY-VhT9(-(&yyjjCz95WiBcqVt5Ex>IZ!ql zt^`Th=crJBXT+3TPFP>LYYGF|vTE|D*=4hgl9gv#czD=(fHiBHNmXkHFJ;X-p+0V9 zPu@<7;TXh=zIG8Z7a0O(@cQsVjqF@TNSE|Bv{o1Z%d?^53Zm}p67)pM;;e*SIEg< zIwgLJ2>To!UD0|3^NI34JRf2PeeyZ$d=9+$^l}XT^R!tK6m%B(as#Ijed!rxZ@)T< zWeB|3ie3yvRCUKCoB~Po_UZj6|MPkNz?XEH_%X!&%|5VlxH5r6#vdYK7mJcqf~d2` z6&;XMwUdJruU8jE<+7s_WMv$n&-1$wf-xUv>*ydJ{K=_u*}l2U3kI8AmzlSP=mDKu z5k0m8RHK(`iSuLFm`#o6{EsGD zoN@0mT%Rp(d~Ygyj2WypX+Nd2=LR?2Wi1R1gPv2by)QH4_zqqHPVlVIpg9*v*JTD9yY;NAsgvginR~E zy0nA;v^Xe&{=sTDls4FBf0Ghts}LEAkZJbM3PyvEycar)S&0>3YM27kn%{)Gex zJrs$naU~rZ>Hp)igkgB-f&kDg@=dsvTQc&Kn6ts~)(jQS5=*3t zLhRoZI{VE_c&A6Rdq{vBs$jk_;*X=)>hk^ z@g`ioF~b?Gw!Mp2zJ{uHGBOybN)BEy#G?+JEi1~Hdvr^P7FOM=m-)!6Wr(WEk+y}>%= z#SK*2T@Ef$clD#%^4txLGDuD#B0iQ!7uQfG`dnTYv%RNTqIJ=L-!*@7eYYo_0&d?h zJU_LuKd;^z`aVb2w0m!reL_q}8AbxES|0D<0K_|5v+4cnBb zUD^DaBmXKGtc6L%C48C-KYa>)>J5d3_CAGXzu|W}qiD+d60R>@HZjZSmJ&|#)+eI% zl}ynXS9ycdJ!`ClF?TdMAaRiX5{elj6XTr7j6X$3U8*sb6h%ypgiRiDNxml%Dl}d^ z&QZ>-w89fB_F6j9+&oJGDXU=Cl0=o8)!I1oO|wNq6zhOUw#=(rt8Oo<0y{pppZy^& z%m;KlM_8#L0}v&3PJ2`d;D^_kNdc>e2WyJ3)4t;#9J#2)2#j1-tiOi#_{m?;lH`d7 z%Xe4#;{S-xJ)FiFxd*P}e*0laV@9pey*$d!@F9n`nc4#*z4boI?L+O4{cgNvZgB%yHE*~q|dfM;F zCAXfFzSt41;wY#rD!ziOxWa6y9jB_|qbD zv})ViCYi74bL3>T7pWz(G?$K%qlvA#1upR!|2O3#u^ja=>Qm63g9gzAGR*DyM2~0^ z=63$L!A{lc{~OMd{%yU@bNA^uT?OfjK9W$t_DUw%;-r#FPA<$PFz^By$&;uRfnh1o ztmh}w>!#SS1q3F_5V#W2#_}-bF}>$VHI0{@;4km91g|;pO8Zxv4`s)r)Ss)0y_0BV zFZ%@@Q=`H~L4?heKolwrf&8}f1FncB4mB`ZUA8`PtZrV^CR3c(D(YxkURz#5-2bge z$I)1cZ23s>TB@9Q-h9QpYOex2u(B`OrOtDG4_(&LmF03-Z5z*h0!TC0-cFtU9?$)c zt!wXxC}U3(iUTdbhUJx|6a2ai9((J(B5pFJ9SPJT9Jtn<0kgMwZP6*ioYnK z9e4>F@4q4Mi;7WpE)xB{>kWSO-85QS?c7@^bF6q4%42FgY#9p`8PQ@EFENGapH*lf zaSP}&?$yi4>&--|ExU6eg(A!)WV+2ok|ya`c2AC6T_9TeY`ZLn(#&{KcgT0%{w4p1 zthWqmJLF%x3rkRwSH|2M)gs6W($9qG4=^4kG8)~LzHCS9WX4kAhV+r4oI!u%2qpg zce_3%a_GEUeK2@$y2yTfxX69%iSv7%9ue|BO3CfGy`C`ec(T}d>S5g6bJ?PBM#=`w`yU)q|)|gIsli>5_ zxKxvno|2>$lBobXg`N4x61j5&w?L7vW<4f0IMJogu z_hgJwh&j;CE zehNe8x?8J2u_ew9=mM5!D<&mx39%1yIuX zK?*DVw?w@ZaS1;pN%DeqHi%XyTUNR-T)bhedw8|I9-eCPD)=Rtci2b`7TEr?Zj?At zTl2o$Ke;$Sq;&u-U7m;WmO8_$@h2DM&CBgjca|Lghwab@puW$=H29%P*X!drCFwgb zJE+y?csWIFp(-aQU~zAC_wUou5Hg2a+Wzk?ySN3 zI~&`#~{1Xn)cS38MG+}OJrVQ2a2Zs@irQD$YCzbJQp6riOP^HGOoW`Jbs*Q%J3N*jmlP*igmoyo0 z(i9|nARE1Yz(b&>+j}VD_m=Df?~3+hX10rt-q?W6%HNePrTobC0a^F$ufs z^>y##aW3+^-&5gtzxP#i{ipF87XO2%?>YWk4d4CmUrMdQ)(Fv_Tm+T=nMamrD29&i zyZuGZm}>ypl-D$^2nejUy!G@*k*&4ExE|aOxC=h+j!WgPKOH9Vlli?)eI|okVCQqE_4PmQW&2~+vI3(kP|C{ zwNV0vol(-#FF5j}#kuPh*0S7>8=yWH`dH-Z%sg=0x9909poxELNC7>h8yR zL(9>6yT$h5H!Y!09@HlUb6!@PrVliI)rcQ#pM@tNB8-g{%XAH+CL=8UY6rFeMM=>N z{WT1LhtaUVm-?O+OAUC&mmq@-H5msrU7J-x?9G16z|c^(>xx709dK7EKxE|s(S~?v zaj5g%7OjLF4mqN#Y%ETLIXd>ocqoG{fF#}{pQf6*BS6n!G4WHQF4@ENJUH``h8v-a z`wK3<7;v~lSoMgdtFwIY0aW!hF2eqho^HZabATme#C*d4|Bad@3sRk1JdTEkM!s)! z`Ru(hepk}(@H)Q6%}YuH=C-@;&CZnQ}qp+3np)F)ht_L95fw>+Wv* zP(zBO=slzwReA|(HTpdU++lSAUgK<2DP@e{^5TJ1@_5D z2v)E4928{djS%Xl%#wqIyZh9r!OmiCYtW0Nkn5DDGRn;zLzqtcW{xf;;5|YNk3w(? z_=^W=y9u}!rAZ{P+|Wx7Ez%z9B7MwsW|Qjg4B}-AWrli%`XEpP;$zrLA+-J0p*sjc z_ErxVjGM}qx~}3mW$1!3Vh0lArK6dC3zKXADjJnni@+gFQZ&);5OlfjO?jBm^E!Fs z=&F)2zZ#w}cw!T0W@&$|yLh`4%6WkleK1+h*>+#GG#JMRemqtY6D$zMlvl_id%m2a{b5@$uwk^ud%B0LDTO35)H6| zCdHL*md3t15*1Xame$)SRfpSo5uzr2nH9(Rl@O+~43X&l0GtBj>C<~uJYLf$CqHq3 zTUa`Lo@|Gn$qNEKd8wUAaD}w!mnGnXprhNUe_vW$?O0#}n-^sZddx?^RSgU46!?`+ zeX=7cp>E}mP&`M|551{dC27Y9B#}BhU3fuBLw-}@bR1Gn1U?`8$Nl#ZZ}q<&;^#n~ z=I7T;vnfkYa&<0v7UnEtqk;V?M@w>bH=DZ+El($gIk{bYQq6)&zsNDF#1d!_sH>Z~ zl$vD&T1Ckra>E2Tfp&OtYCYOSnusDI@om^hpWq?NXfx z#NlMsQqk!IU8ck+39JNVCUI&kxxj8Y)l%Yih6H+QXK=kcS%cPN(`#PReh%#{BnQzs;cp!Lq>yGbDpSuDMnxpoU==JyZ z1Z8qUw7-S<;+#LLiCDvT5*)Gmxl4HpV3$*P;~e8ffjXlHd{d2~x-IDg2=hq!{UX(Z zC8~kaU`?GMVGBeN<`D#!z*jTGeAWh8+)~;y;bAqV+%MjD zZoCan&(;#77JY!*UqmXt2n%TSif#Aw_fis#?n(e6(ajx6O>DPnBBO07bHyYbhk`7f zL{R=3U^qrbS9p@z*eCD~-O&+rr;2B87`J+a@@-(o z1OU@r3tU%EpH{d#u^N8v();>3$xH2;__FZ?OZtB^we7j^;mm9?^hAPhaJ8{;r2E_f zn>9DR$L~9T;}Ot%OaHGhzqohF&5J1Zag$1U3X{ybJM|jhA*si%TL=+N!00FQV;3}y z(>~(yH-i4)9Mpv`MvrnV#qez(+7m6oX28XCTwTqPgp6i!foM#_G*H%aL3F^C1*wehlz6u8KNhnqSPH9{7 z6Dz6qjc~;M_R#7IAMFg!%u2O2*quW`i#%`aLY>{?%1`)ztE?kIscGe|&(y(!{f6>2 z_}p5fGBh{v*}VP66Rg@Zw)ezYVx}&Lx}=Cva=XZb&`=o@w8-K(>ONb&l)0mL7!s7V z((-^PX&9pyu^ZSVe@|0=P$r!cLyT*~au-ZSdq*1^aBgw`sAS7xrv>urZ zRp^P5BBTa+uu4EyNj*}7e(7}iEMJOjxq6uf1FBZ}93N`DWbquKD|}Lq;j~!Boq5)f zEk|aBGN?ngP{Xp``D?u7?>YKF6!?bziYD@R2;R+t&BhLp-DmE|rvNNo+N<|ibqg8y z2o!}<;yH{7ftj+T!$hau!3>!VnU17WXHS)(t<5kM%wpK(2=xJMr>o|xN`7P61L4lo z&oO|J2$!KFQFua1z^Y8iauRgabo-283hB3DUvan6T#4# zqJL8_Gq423XpH3~m?9(S0kz@+@VN?lrmB#Fo+V>Aec=5oR3g`+mpG#{z5eN(aBKsP zzbCUh5A#U<607-F1aU81w8(ZX;9tp}4o@r#IUiCXan+V4zmikeSq&hL3R?SeB}|W^ zWBhMAUmlRSXVIDzRs3Gi`)8NM1%c!3?vV+KT~B=j>6Loi6-F_-6zP_Z?gPH^2SzBS z*jxH%N8DJapY~tB-G0Ys!Z~J%ee{x2%b~b~^eG~L&%(KnaP00Dk^YTB+zkS?JcrSN z{f_(;x8oCz3WaQI*){cG9pVryY*F+Ea%s3YP|o_mO@g2C`2kG7c*o5eoKZkO3YYhJ z;Lok`F$lT<@Rxia$Cg3%g66IN;Em{h1KCEXWx?o+!I?$R+~+M@_J`QEKKzEC10Ft= z|Ngt8{`+wM5aPjVsHjl)tgsTa2phdF92-CC9|-7cG#NPl%vnrQKH?P}eqI5us=3r{j&xtWO~5S(f=>og{rgGlPk{97VA@-rGUO=mw-Er?n(deI!W zO*cwdm@IP17|~teD2Dc}ridl=R%*M`ag^jI-y206iy@5jlRR2e&lbBD38+KDuHIT6 zf}uwNfC}L%0%s+&hAmaH>_{@YxuTa1RgT9l1y;@HK(<~k-)Y*`E$0YE34^t5Sc5}J z$7!uTIYr{DaK^ZWw%1Y6=95{539>=E1y`dh3i|A*o*WfiIK-I}hT}B#1L69GbFs z=$b1JLXbWR;QG=Ox8QNRy2p+`5QyIRUxqJ>1XdlLv#VtnwhqMS5T$Fw%hu=5?t<-4 zN7*axLhqO7#qN;uyfeXX3ob-+dwSgQhCI?2b#WJd&Ik784drum8qPJM4DnICSeVGr zXcTIHi>Vevqd_B?L6}`{?uXHK+s-QP#3Iz&dx)79>h%dOTxW<2U|4If%r%&Kq z;X|Xvj8IbbE5HJw1GaW^Pn37S~ zx1VGO(&N)j;7ukj{M-=^TlVulm5{wf;nYCYJe5Cy*Bn94Bj_}Lh_g!g4&&4=WWUZZ zVzhgFqUN^9e?PawZM-AtL2^aQ@#SMFy6o)9pw(lqBr2cO?ubB1dEXhQ^T_TUYvGQ- z!kPECa>;=z!ST6Id-wkTdns=A1Kc{8f$3-gCy>WWF}eLqTsjdU5(`tC0$gIsnG*Cn zamJP5w|*a&C9gTmg!T3c6Lc2As86v1f*Ee}DP?%dMfFuSTHpwjV$>>H%>?s<_pV^v z=!FXG+p+W@MvS%zo0PzoEVV#n2!DcO|IWGPC_&_MFQy!iHF8+Rd)^WeYwI`3VmS+! z5@s%Ne?h&L1|VzLRz>>^DND@Co#6~Ew6D~;RoyKdiz&W=5J>E}B8(9) z#I@lxP61V@My4=y>JE$WAY%|o1IG)ZOLma3&%th;KZ zMeGDh<(S6}lpTt;tkMcJp-UCD+F#wW=8YD3N%)#Z4xI`0fQ1t_sE@XSxoa0qaM0t1 z-MC1tFc`xD4IDVGB5?6yyG0WR*Dtz(pMq?IAdwr`h|k7bh62zjvEetRmNbx51<@(i z^uih~0YW8llSnNAg6QG`Hl6{jBM$+R=whk_Q))u=P~&_Y@OT<^84=Z{KF2d2uZDto)H%awQc_vYTkEgdVX8+I^fXY~PY%UNg7)>OHS zEo*{VYvv8w%z(n4ylJaF!Wj9&6b4+;3|AF~ZRrput&!knJrCRTTxb}ZC1lo7xOsvE zJYH(j=x(FYAZ@0+p?;K%2p|kHg#x{i^dmB*Ai*f~yZ~q2hfwv$S2Yh<I)H6BL4Lf}QxS~+?L^D!B6i|xCb`#S<{fyc8X*qJCk2nYNkkSj3 z>Phxr@5?=TAmp7m<`Yx$ex`M zR(+Q04#=aOn9l!?;Wlw7$m7%sXcIM(scWi~-z~vJ9tA7TDib(?I*XL4**{BJ z-)|b=h8wQ1TaQ4Z8Q9nBz7#?ljz4II=cTtDphSAh7_1MMtVD2K-s|^OnX@Q56jfNo zG!`}{fjs@kRsaxdHvDxt&w%LC##g^#1?~D*fb-CsW1@Hl*{>4vvciVla6PJ?YxA?KZa`1qb4K*6f<~bxzagH z!Ki^@)I4K|i6d*zynGtgysp<;MaK?A6fb5{XLDxQNPp_~#bd}YK3yIwSl@Cs8X~4p zVVSjo1KkxUj?I*{fN;N8?pmuyTf-?;ZrW%-lUi&Yr??zUWJ9KVTaM{XnK7bl%-<0J zczCl6)w#tgy>go*oWcT02@YILnF>CasKHVVIudAYi$-|7w#IF^0*!yI6SV?<8YhXE zazO`%PL}K(&Dq8zRwmZ%s)mWqW2*s;2B}?HgVW>*^@oegz9{IIYXS2S;cx;2C{+<) zX}*0g+$C~_PExUCk7A}N?XRfTG1aX5TZC>fwCPR|!z9?fIYu#VX%B{-_Zv<4!0BxPv`x~QU2WT_TU!57 zV@tcRX7RxB^|GE{_{fFpXWjL@5u)p7N6Xc5dhfu zkMyS*jc!peMoEe&YP@JkDssJSpyark>=LD*S%ShREawQU^&r_%RT(W=OfiPM;Slwf z2pZh!BIw_+^k#_&>8xb#31Rrw0E`N;{c3=+7!roHiqRAzF%2f$a(BiEwKK*FGQ;0p zXs-Ar6fCR?Ia8|m&|ufEppYw0POvY2lg{RE3V|*{L}#84n+vJ zQwl3}$0yK&v3Y_YI#z%XC-%bdm3?3Vc&AhlQM@ot(1snX2cPIf=4S8)Ju^jsY~#gT z(CSCvgLaN7zE2NdfpU{Z+bk&lz}CwdevX;3{zUuWbQ|(``;H;s^MkOrG3ehc>fJIr z32qTPJ15=>!E>(rXKPQ^gE&Jrsxc<_kI%rADSoG2@PmN+$A=`dNe&0;4%`2)R{@fP z{y3+rS>8=j+GI{-Z`BmTweuzf1SDf7>|#af%Bl?05q^P@E;jFBp!imSRh#MIc0+kv zw-^F!Ll`{6@&1KXrE~lsN{g%o>oc6EbmTq)ZSP5!7YW-i6)y;wmkMf)Hy}bhETo{? zWfHOLF1~+K_p4goQQo8C8(iKF@ zwP8&;kV!Oa`e>ZBDD<6pzu4ow3a%-Zj!-x)7okNjLIc^ZF*m=!;sq_090Ybn%l4=b zQy3+TSVE363^b9U;x(T}O(Q*eEDn5|japVX#VbrnAvYIp!Lk{7%Cv)6%M}~WYZY15 zI`+-FYW5pH2gDE_=@KyLiPK$H{xC{#Xu>e2Y~ng|-(EjNEUdRbj$gdr=lc6Rus~iD z{(?962ICzM2R9cVd(Zt{ic0O@gx-Ko_r8RX$P0IE`Zn`P>|csy6H<3xCH8o8R>dtO zC{@wtPLtobeh>8A^P~fjXY9=(0`aUEySlncx16o+67LmS$$3}g{T%#K zhi_WF$vQ+#6FEj+i!Y9bk=w<*0ZI%ON5X9i zi46sRM*p9q_}5rbW}b%u1gt4GC#Ij&G+MLlKfmV~rH^z$ONBl4S~?)3VWV7;zwS!~ zlOx*1_fWs14K!>vW1YXRC8q|8lAdD6e)@v`(!%N?gz4o{q=W$^aNnfbBxZxDOlFNZ zz=SwOKP--^TQ=Eka>(D41>Zau(XBn5lfiJ_kZ!?~J$`OFdgN?>S&;lb1NnF3;ZR=R z9>Km(Q(44hTo*|zY+cNv%^b>HFiS^J;h%Ew%>I*0MN%kFdKfsQr#lK8wYk~(r8I^!x`V`$LsmkbuJG^H>{&6F2SajP{MWatk9$xPAVO@=Be zgie2&LIH&hb=P!b(bn_YjbMx;D08 zkQO`qvK9_LUBBq{#M9#3OB4ip2>gMt|+roG}v#ZSn(-aPtU=!B+1iPtJ zZ4p2&g#wajz6F6$D1-G8{8)3p-JBmKUBt`OMS48;p*!qf%S0wqC9x|==J!+AW{Lw~ z(-xVH%e76pk+U@uRwx1u`_VcjBNv!&L}v4Dn|DdWSNGxrC(PIoMRWHc4e!R?V3c3B1;T8iz`70zUOYhzw3cL%*4P;02 z(5~JiHktIEW!}Qkya~V4cM%vas zef-!CpXhMg-uT$*Kk&a=9&zY=-)VY(Jm&ZJyQ>uHc%IMd;(0k=(cS-AY{G>_`Ijo$ zHA;;MsZ3*NxYoEX-`>G00Hjk^si0j5)YeH^I61ETUAv@iXJ5w6jT;kIHZ{rJB)?+k zTV85!Z<(pHf8SWSR@>liTWP0LXJ*r2SD9*NGgCh`x$qs8vB9)XZ?RL`a$}eqojr@c zRFb2m5mN_*oRap|Uq)+yh+};1- zvTojVKC{xR95M&ZIOQtrGp`O@vTxLmp(z|UXIo-2Pm*z9KSc40Q7qJ?H$%$MVgN*h zMh8g%AVnY0mqxu6_vtG<7Dv2 zP|C;e(*h8ZkS|9$GdxTJIb|r6RPcTiBx?igV&$^F%l@lr-e|o_DKQI(%-f^DpACYc z^C%ybhR}vIHoU2RdYe%j5$w~$5Uz7XsCXTkR!b>;u;s*hc#Mh(o?;={KAw|Xdxl9d z7bdAWwlMG%Ia6DE^2D#R_ihH$ixB21YJEFh;6(L7gsF;!@P$%&eA%L^4fQw{TPb`9 zR3^5j@2d0elk_=`a>~t0at@cI!=@F?J=sq!dUF1ze&k`cA)*s3DegjC*Wk1z= zhwrHU82FFx+U9F#!hhaCb_ii+T&^8>j0y^4Mdn&_056BnI8%CPyLKcbd=MLBSu|b- zFPXgz)H6AV&-Z1dWC#C;ZBn2O7myOMIU4O}Q~z1Ahw}1v>a&q@G|Ds}j`C0~6y{i3 zIk*|bDUwTbs1g1KazvQ-iyPiaH3w+-01cvq5Et(~|$`os!PlElDaMLJRvMD~kaL9+a!ezRZ6(`eHH zrZYljN`uYBy`Z8%oYpxaw5&uu1oSvN1WfINOHf@SH1N@yc#&*+6QQHJ>1=3U6NcD{L5DhGws$ti{4pV9Oy*D`AX%#}59Kda8p4Ey^#lzar(`&2 znM5-LSNZx2u(N zxidx*+xl>#m(57^+VoldEkE-yca$b4-fro z*=ybttJ)hR;WghHQ`F=t?SGipWn~QV(k3EMLsL1A^9EX#_h0&gzN0 zMqwBdi^OZG^)sZlVMNqIfcQ=s6+$Wjf38Hmm z9j|MXor_e*&0<^8TPTZO70qlzN|YFUpQvWP@ybf50`KYbStI5xsDtR+zCY~`4LpEeYDU#jO?qLhD4UjBM#TrlSV?-DaDq`rrT@^)i)o)UjR~d3c zr+5`#syz^8vTrOXDS9C|-2ie5r@5h|FeqOmvPhh&#+qP=w17*#boqKkN6C&>QahAJ zfMi?2aqOQ1VRd&CR?KvY#`i4E6mT2VzyI(p*Zl8VJg-s$RtT`c-jwjE4sBo#vGt19 z5`!uCpr=$nkuu`!H_=1dZ=Z+>6GRs5xnu*k`ssrInIG3?)CCBi zyU}(0A&n~^*i$y2BOpyf(uxugUP;7l>0UJZwEQfvDPOE+RyQ0YS*lMvbwu6qO%(i# z_F~udM=ihE=^vno#@o!%4jQ%Gp;?C-l*Kc?aIp(Vd}2jIZYLfEwBjXm?&WOrrhy!d262-> zX-J2cB?cGHo=6-rthkEi=x zprO1?Ia~ajv7;VMvx9QZ-8vda^3sS<^~m}EO(M#vn@(oerx*Q?IGw33b5ApgxHZGx zqG@31q}F|tp!G z3i`)U*XxeZYnuOS)yL(I|MNxH%WHDarl_d-rtl?QXG4aJxm!(jSnh?W&65|t!N zGJpdmt%_7K>2@U#wfVrIA`_jkeML>|mX)k&Rz@zzuFuFvrLA`{^9?bYmTfhG83wi} z|0c9`X{kz)OB14Zjz*GZesoT18(hjWc?u@;;E@Oj@w#}r<&!RtGsmBAR28INEO;qu z%C+njTMBuOin9kDK&AP}$|bD5R*I(_2xmCgs}$_;9KRyrZyTu~t35yER(E>po=P(e z>O_4i5g)nHT~{wSr!T;EmEeqeAx91)Reub^#i2tdc)B&sIBDKD9X z*OLJUAq{k(jtJYQ{gRxR!S;gcB$m)&_`H#d5yNlUs?w{5A)3;4gfg5Wydho+Qa&Ii zIl6wWPh+qy{d4@RS^3{pu`;T}%-UMFyC0!NivoQG2&+ixMAQP~h9$Esa#ANRf0nF_ zt0(VCTS%nYa7+K>%Wy8tVgOT4TU*~>f}M4zDy$>VzW7w&xVRhJo-t^D=B%{`Y@?LZ zSw*Yfxe55Yn%5;njF0f_^_D}*a+@r7RL{t?4=mTPZ@6E~wk}>Ub|Csmm@2zmd0a#~84H_4v)J83) zH!h-6+FBbsu546tqTsPVO-rb5pYxdYbJxfmL#f`yDT&cu^0Ji|$s$9sN)HDgrM=9} zz3c^XuDjL2Fys0O87S<*ie(GZd6izmqo$kIz+ZpbbZ6En(xv0h&&&h_!^J5NPZACr z^d!GcUYJrj^nB8YhLG6J>p+Ip1@#$wlk-+5Zdz@2=Aml^oJVGo*|Q~Sy$jF~SiYof zz>VvVW3Ed)733Ju)^+_>6M7*!dss=E!rH)v%5^f*AJLu^cV|oUpX7>bkUM z>#Z<5uo0x^=aCccyg*at8USJ#)JoU4FwaIS>`eL+h88(==0{U~-B()c?3TuP+}wK< z%~u-hKA$z;Kj^-+-LL($^B!qwJL6xx>DyV_aJw)k<|o)$jXJo1$AhPI(2+nV|e* zBZQPF=BDY>GH$)l>Js|ww_S8Xh0i1~5HF!g0fa!JJ|m-ydQBP_x#EWDK?NLRs*FVh zyHe_I-I85NLbzqF$x;PQYh+in=)f&jCt^Ts|KF)2e7?$1DD+(SAxl2)gg-hUD}8-? z{lKQWo0!rBkpJT^7T0+)-ZcD99zLffTpp8$oLcV<jpS6i8pR-%yJ zVnfqRV?u$CjHUz)dZICQTN-QJn0$njXBND#jV zP+--)ylEPAZy~q58bL0}g8rQ~@avjq^%*uotsx1(XubrDtvl4+WmI!N=3nYfYVNfRAB`LCmuz9&vi{jGXxR})>)F8-)-2sYx|DO) z=2cDqZp`SX@Xs(q9;`Ump}n1skd6Z`TrzBWhJyongd;lIO{dE(@7oA}EjDxA((UWWFq@FPqIR`&0iPs-86B_R1~qQOtm!SdMv-{Pf7 zD-K3Gn$WR=YMm6f=w8U?vAkIU5VMGFJT#@rMzOt9Jsc;h@16#eV5I*j7!7)gBa2yG z<9uQYdbhWaiz-%i#MN5`=Amy7QrlX6mYeRuXqs7(P)~@b#G3l#X;s`n?~(1qC{#j~ zo`Oo~q{s4MCwWueEXTra;1RsIXzW%kHT`p!mFnwNVUq#G+URv;ph)7M)XO(+?HxV* z)}`-xM`rN2^Z^O2gLEL}WbOCmxxajFiY?aP&CZUJeB8>1glm_F;2B4blO=Y13k$Es z3m6klNirstCbUxl%#0)ZDHK-nu+{jq;$QW|8LLJB5%*@>k2WlO1 z4ED@jlHoBRH4~6ZSZr}3HIoKNBjn<^Ww3tgtn7*e<-H|pr9x*3i-co+L0%SD3#?4W zcZ@&Jkwt8qkYc85K!}1S*At-obu}gyq6sc={9S=WIAn%|$xs3+pfY9OAXgmBM=G-3 zrzjI{-L|7oOUe_ZxaQ;$#zRdRwv;|v3x?4p&HJGIGBp_IA|buU^IM(erW&UK=i0t5 ze(yqp`N4S<0c=6P#bjxsg4RHOh*46jpI#15|72`T&(U4ayEi#(85|}TOf64Q5 zP3cdc2dGEeqbHnv{Ys{x!&4{l-Z;A)Y)%P2&lmVC!X=Yd2DE5jAeIg1y-B_4}C$%A}cbY7A*>eQ#e?Z zQi8Tlrzh;UQolxUvhJVX28NT3L?XI!;W8|Z_(kTHhH@&9N2Y6k%v$TJ`YjKN3d(=#GOX~&^EH`NC;(GbMjL&W{CY$ zR7Q-Ue5!u8CR2x`OH)tLG*QYVzP#*VK0W`ZT2tO=XhO>Y+fY)sEaq*t)FZ}-ZBUpWuS2oNPq1pOyT5Ih6N%~*K_Q*ttOe(8Y-NFf?u z4KD>ESM1R|C6F;^0Lhs!f6TP=)~mNdOoOb(!S?j(`1tQN`$Xx-W4{cQDprCNn!;7< z)GU#W_*G-*SPIJldrd6`JvZcPWTmrZ)^Z4EegNfW zQmANDNfP6A=~5?@`J`j1lw)hK>#wgLF^^#)cOvgnR@mDycEB4^! zZd1-0{4rZi#<*ZiB!n|N(}eg%U;)AtERblz7#bTRdl~voJ0FUR(wQS>ReVf=DrQ+4 z%sE{;#;bvhJ+Kr1sLLL%P&CC#Z485^HnZvqAI=L*X+LxAz>Cf%BW&g21M#!@c{Cm6C%_*Hhr zfQI|2q~~P%0XYM7qbdoS#1dM{=M=8`@>Ao%nx^DNu-BznBm+0pM2G3=s@2!c9-z{y zW0@L;Ws4S#LR6 z%;`-x1es+_==bZomG)BTB{nc9M;gg#hqB`an)k-CiC_T582A1KxO5vof_r;UDZwo;VDTK2m6qk zwJ>u(@gT>MV+B}+yXY8tB7a?xnWfJ!*<~`rtJ4`jR&jh*C;mYeQS|kQZ`7+sWxzGU z+sv(>|oxK;^@g*TLZ`A2wIf9vjxpj)w|a3#cjS2E^JpX{ET8uNuKz z1IkNBY~wRfgoli0BNg~+mdM!%mhJxCk9p3J2@?9nv!OhTOQJS)WzE3wAb$sM2E5bo z#@D+J8^U0YYIh@)QbI{k7a_ozTEZH`FO(HzCRap6o@y8z?_?(>+9VSPEi85Nuxu=! zE^A$^FKz#nCoY`wciZo1D!F|SCvo6NrwuKT>*%0uKF!VbzK|N&cwAcA>8kj4SB0n8 zKCSAn&+sqvr+QlS8FGE7$+VU(VT}7fwW!$$0bWWa6h_s)H7e}m4BoNTAU~_6M!59o zhfQ+Fy{LoB$(AOYG1Cfu9eb?_NPZ)6=*jBXBD)Tnr3J0Ygl*R` z=L2Fl`alv+o$!CTIxz6P?!ORxKP^WWdfUDl$o=+sxX>->`Z2Zr945J;)V+Xpg-2f` z$LUOxgr(4OWoGx(pBu?eAqfVzr~-Rx4#TRmBcn70u^VvtoA5 zv$hIsU=FAg?la$h(#7H==8BZ>ndhF*sp9p0KE0Z;*x+&BO37TtW0~*pXxRdX?WgHf zvzT16Y)P}Iq_enqR3WYCr^#+8m#t5iY_Ji=@n>Izidv`*-4yQN75`?LVBxCxGgVfW zda?4nqRnh3nNvZz^AR|QF@YqSD1~r>O4xuSB1YlSpxURRa(IOgN8+4_B(5)y=vyMK znzQ$XRpMe4=9!yL33WJGong!)B=HOvJ(j9BNU`4j*3#U)*nT5OH- zZ5jENr#dzH1S#p272}d8MjulFK)D3gMr*RVbT?Iyv8C_fXrc$$j~*<}O!u=_IIM2> z9Z=12sGZY~^CTCT-|b&j97=gmflHU`j_e&|slA4DdQSfshA-VVIpd8m8>HNvm#95# z#&mgAAw-wet!fB_>q8sH2)SG!j}Zd@p{yS*67@T?gI(qsW(>InXiRqrREB^UdXi;_ z@rug8f4`{*uv{wkWUEUs&uQpwlxh5TE&Wkc78;D}%qB`o!)O&K+1=NKJhTLFDZ?)_ ztr*rgjTTj(O_|1~?yOmrI8(fR|8P0$>vI;+ooB^i84xr0$&`eBGl&fa3K^p=ea|g= z)ktlz63K&hL1x!^*I7r{HCpY+EdX?=yvWs2pRTX28x83TN>Ox6AVZ! z4pc=rSy~Sd9?zR4(zyIY5Ivq3An7m&g%?aeqNz@v0e4zH3b0J3Srmp<;vO7mxXobV zoi%p1w9Bg>G<6p?fpub5teY`&@;=qauX=<5TAp0-zWKSc?xGKf4(0chE@eFJ?e(YJ z-QL#E!-$ znI~PdVzh-4pb>PKkcd8MI4|jhAZSSR)@B!ZyUu|BWreQy--e{|h_ zZ)7_^Vzu5I*_%j@x~Q=SzJLp=K|vVvQu-B* zZ(?47i_Vf1mCC-4_oX^k|K76J*Q)d5xu-j({M$=g+1XE9&-b6rJ9ufpPMP2BecK&< zruyd>cC#n>!7SX|?*;p#Rlsb|{P)$a`SypZ1Eo#{Dm~3XHf!d7TZ`p2-D0WDE5tVu zE2brWzS8W>8O4digM%2kk>4q}FaN?A>-N^yt_)fT+r9c-91U028*Lq0?(YRYdv|lY zRv!eNg#Y3fp4eU<%LnW%C%PF)@@J7squUH5y`Yo&J?~uzy}y>neX@8uo?-ESK&<~b zVafG@Rvvjn7i_tC>??1&h*4CG==Z_OPnB&IFa9*!Yu@O*SSbKZo%mm5y;W3P0h2Bo z2$ta11b24{PH=bE;O_43q;Y8+8Vl|og1fuByE{Q{|7Xt3U3czF@27s*YgO&4&#GVK z15Hds1pm%KSPbxGh#w`PzlZ%BH}7ZBf<~26Mm1~t^fi&9z#3huAtNiZ8C`88TlW@Q zvN+itXDKd%@8VdgOXv%HfFZWtAW=Pl0qNlAd3##{w#n$)=b90c8jhR&oo4D+b ztA%5i>RjG`C&%^7xjY{|>4=?wwEm5T4;VBV>XXFlo3a=jI2)+aFs-$D6#vrc&^(n= zmnF?AV(5cO$qEK!s%6cjC9nl!nQ{Q=Pby_X!h>z#D@?MpCYZ+L_yTgPp#uI_kpj>R(mH$%5zKJe zS4H7LV!|jLB@gCH6FM-+!g4MVbx{0Fa12KlM+@nu^q+X;$SlUJ5|c?7XxvoT_BIj3 zHh05u$u5G;zc~(6z1V9PN0>#MAx<8(hKdZ*$yiP7M}urflnt=7Zo<=;)=_2qHjp-u zh_cTEDD*9LSyl}gmAhN?#pn1{pZvcW5e>7sM54nwo#(7G!!Z88@rr@|Mqj&708QxI z{pY6KlsM_DJ!i_!FT%!%S<#3PccqX95HO@$0nrfBu;POm(dtd%-7%ovp}W3D>*zV! z17}fYT(aH4E?;2oF>hUt!@}-*Zsulg)=nghJt?x}yhH3mS_6cNk7kGdeTTT4ir7o; z2J9`%=jq+NyR;j;dHyDev&f|VrKz*V;m$8wwLPz0@~&*^SX=tX*i?}N8_vL_Bxu@< ze~yB=_F@|}aNOkW>mE0`efxQ0=dtZg*ntHLAuMFuk~>FENN^5Eb5Vn<381+MAF;y; zoGHQiY!xRB=l4j2jT6!8dEFxo-LY&qXK&|FQNe&z(ng7qqp@x^N6+xv9w-ePGd^S2 z{Gc^%xM{7iGWL}lUQ>IEfyt+NbQ+l&*}@@p&FZuv4Xlg&Q;H4i^5gX_D&WJ?)TC_D z1{1dtIyr9+lt1>MRVajcSln2P{^W`NepnP)*pn4 z|63_@ap`T(hA^GpkB`3a|K8tzEUtoBRJZyxLt25sDUG@;tg#C1VpCmg0huFD*+ub~wyN@P z<<|s*Se-hfa+(px!nq5hutZA-N^HE5n+`*$vr5@F!9?>uwv6>Vw_cNm|4peQYiSqMfO17~7$kI2WlS;{N`~Au(2o9%oxTSsZ zLz~(=MlUH!4p;NV?jcs&e$|yEOF7gEs_Wb15f|w%rpr!pV9p1Sna4*$+TH(mm!O&J zF>1P53atOc3Jip?^SbiKuR-5l34KCA|8l^vr(o3-AGtwURQ^ZHPsTlfOL3NY4mr?Q zuN}`)x6U8HF2ra^Z9^CwqVvZVfXkshlnBd2R1@}qt-ZB#k5Y(pFoOu3l`UrjnL8avxN)3c@7zw<$I4_R2^18bJKImamE&C|kj7)lnL21}G{9!1l&m#!Kfvx=Bp zOrQ`XXO54I3j@l3+PPS6`CEpp+T6bo2E|hoAcyZJSkeKA<@(^0N)x)fr}Oe^x4W+s z=B|{SS+HfPAfZAGInk7fQQdiSS64$%d#^hq3lAF$6YsAxb(1!nxTaJBca?mM5?0Mw zVOAZE*RMXN7y}m^mlqZXIKO?o+X^wNG#J^B_RLK{^><5^?NbZZ8tE}#wdV63wb}@4 z5+OqkUCZ>B2ef4fpR=|LQ_^pBe#h5lfA;e6^nBJ%IBHojYGuWK^u&v7P<&wX()!k( zMTAYlAOkB2%Z28)uf}Lwn#O2v(bPP>A~wf~E)KIMN|6`OfRzBdE}|e5H}A@M%xlG3 z?CV#0$x_hnWZB`f?)dM|^@93rW%7Zh>AbvC`}~AZU?3Y9P6{_bn+YV_Ca*C*RGk zzoYhGZA3<=NjUp_kDBYUbbVe)8O-&4c-oyL^1nL{K^1I$grp(l`nvv`hF`oCY;`~EPSN#1wO@lA zcJQ-yZz@)VV{@`f)doWn?ndpmomfSFbjXXHSdxpZa*}HIQD3);F-3Hl?tB9)Acz*5 zbfdJVK)C>@8p3oVwB!gf!wzc@_?hmiqejdV@H0;ovjtpU)}CD&Q#W|8H*~2t%Lk1k z8F&-=Nm%lXFw!B~cZpBByg3TJ(#d z>@X;=E8D*ep^_p9d>}}n)re2tL%^0}eS^tH*Y%9V3QQhl%K1^E1XdOHRc0D5F|`~a zi?dMF(hi!eCuc!^6)D6EOTl?3Sq+QrNJ2=}kmi>e#$gV^nwMNU_VxbZGO z5^*0xxZtw5z zZ{+V}Wp8EWZ)Rs@=4a+-7wLD!qgdMl9a7DOKlhQSwN76G>E(+sEA(^KmF> zO2Vs+N-b_{0{A>MC$Ec^n*6d_5aW$2mEf_|=vnUp4x9 z0B^pF$Lg>sr{fRQI4ke7#bNBn$<4B=h=QnrqHKVmyO8{pWeFAe>M<4^be(CL55v0P z_sZN%c1FfD&D3-J(+ffp=j*fcKotw_EZD2$O8LVF5WGF1^W}MqM^k&?kZqd~+XWTJ zLX0}vVjV|$dZwCU%w3Gc&*Ov?NSdMUGB{Sw?--H?3kQh?AzqpVaeA z(vTQF|7NVQqT&0-iK!LA%>bQ>4DAH#i!|&p*THovV{83?HZa%lF)$zjFjy_$= zl+|!fWbpHf2sMl4)E3wS0#*jrPwCKNS+WQTirSMV)uZZy$vN<3Das&Hht(H8bw(OC zXAX!269CEN2RX#r`!Rt*GiXVJV-{Y!NFzUPOVmhJaI2H8wBn6pFn))S2@eH+EA*qS zrWyW;w;ma)GDsq)wLF*hwHeD!iLOJjyZxTTfKIJNgQs=`|2LWeW1O0d>Gz>Fcu`!e zO`a1@fHxSir4Xqv*;?zy0ivOdyhRKX)8dpCNa8WuP6J!{!KbS@u)h?7p{zQCDXTIW76J!7Xv6^K%T9y2O@Ig58 zKxN>kCIti`4Ol9A+k}Rse!D$eF(x5KlG?EK@|_2dTd?xuO;C&_SiahRu{VlQ!cTHR zCMkm9r@L(GJVkv*A&VmvS*opA)jG5iu%801yGrAE3MRf*j)4J1Uhek*^}z2#sso&2 z06=o)*pY5OM(lN4iG<(*#`hv{_1I;(U7>Bz$Yqt03Ak*;!GmO8+`YrLA^^`jcL20t=00(*;yKm=aMsCc_eLha^&qK<~GxW{AaOIILnDx24!I+;(FSk?HhliD0^9#yV!Lvii-s+$xQ)NT~V0leiG6@+D zlc)P~TQNy8Xm^zg^DgqXYSrGyRr(-GgoIMBh{uL`ywvpPYv8&P!VyL zV57;vS3j?i0a=9AW7dtEt;dKyzhF?FS#cgKM6DRu_q;tCyuRk@`#*C7H5VQJmdwnR zcuz+5r+UAfe*fdaGiETC^^!X4Kx{FqbI+U6JAMu0MU*>rXoDKjKb^*rt`-~Z5vOpc zsBm&!gfNK0z8Gv_pfP9MUYc` zql9e&!^f@nZ@UDc&?Ea{$@eh52nduKYp!jnK+-lwte>p&4tWqWqm1fVra-+tPFkK2 zvT|35ne@5^3ogWS=gy~^$miBsl{~7OgyqQdNDu*RYJ>qcV!4)s%jxc#H4;F~hCAQ7 zDS=%|0+S|WMD|gUmExYo>(HqEu7T_b9;Y90pOumU^O`oJ7WjHoYeQRgAKw_E`Vd%AI2D|5ZgMX+DnxFZ%=TwXD$8D|~>a2wNQX z?+qB*+Dm7tGgp#vs8tiUk))75>`(rys<@FXO5-QIL@t7qn+$$k;qZflL|5QzSdQvo zGA{6|a1%l|0=Ble8b+z^5|jm5odYmi5_?=YMQKzOhI&L$_kf6(4CnWMEeVbPLpYRZ z=@G&Aj=KPUz>k`lcoAH6-ibqJeR|#MYmB;b)IbzR+GC_hPg_WF@Vf}Qb%OVXAY$AN zWz$QXh!OuoO22Bx|Jw`SltE?_>B^B)*TH#?ZW}mre+c+6<#y9-T^h_Nc zB7)37_W^T&IY`3-08%yC?e-)~lL}3Wa^$~2g$G}YgTKJ;PHWLYjxAG4!oyQbNX+i; zs$%ZWCLJ#y-<{3%jm_|8q;9>;(Ze?U$k)cL1Jp2xR_fa$nbrPAFx){wf#&b z+qukU*krwhTeBoYicWA~eD`#g7s-WjFKOJ}XJ~BY_jdJC*=y5`Qnd~_Hc*$aoStqF zNiFJb6SqvmZrPvcZY{cg48&2WSx&ZO@bonEFr`?gbkL+(X|Qe;F6O{h^HP_Mo39uH z39OEjpmCiNIrFFyX>uW;x{tIowT?2mEEf?;u4dDB3;HL#?`4>|m+);RYsJ`63AnBX zIUq#CA?UhV2mQ${zBoj=yM6I)38N$P!f8_l;g^QeUVMVcSjEC6C@6x_(O}6i6o(H{ z#9MkB}rbl2X;0U`OZ7d z$C;Otp{7`j3`Bg0aa&XvXoTR2!=CQV?Op|FX>Wdy7bs{=U2tTN7gg{XZ$G)@^uejz zZ1@l3>boUWWUc^2bti0nGVY#|_s6E$S7wWW#`A7rtrM1liJ51OVh2#xi? z{L|w^@n%d#&PL@%Ocl+WM!3xKeXQKQfHuqXK?9JPkIQa&l$ygd>Dw z`qXH+^)2myz%(dADEN1}Sa=f$Q75Fx9ZsK1_A`IcrPM`~-0YHz(NCfYlS;h?CFWw8 zGVdoBkM)F*{n!r&52lp)($VQnjSKH55>TM=oKRtTNoxNqETo-WzfP`*`B`N%TCl_FOuf-d-Vl348JFl`|F5;C&ufZjew2`D^E zI4t~=O#M&ee@hbVNP2F#zY)QYMua22<@5;)GC%9_yDD|{3QMlQgmRMB5+XtUX2gQj zJ#C)5wAC*KiRNH|Pi2vw8U7u=4Zz2k)-Pc?@f*-%VpN}L{5kQ9v{#Iw)QV7ewFg?rdab?&W4=XX5PdZ)t4o=xd9R5p~^NTe}@q zJ3V}TT{>J_I(&W1(ZJ68g^&QYmCmF$YP$r4Y<96D3J|$n?=3Dl#L;E8)0WX@6;DSPR zdSarh4k+p^Yta6q=bkbOg_q%LU$nT4?Kt|GC~TDq zHmjB_%*0T{PP?3(+SjukYIIZ>f++~ z`SF2!Ikya~ER%)1TdgTuxR@NahtkTb6ojLL0Ep$XxdIBj6lash0 z9NWy(hsu~U(;!5Ss`F?c5BRZ-Di^-pm34PhcfB?~#~+Z%P{ELDrOvkEo8|!$P&K18 zFDF4tbIwk_h>Tn{Xs?Bu&GdFKW&%907ynoWdu~h@6FI2gXTpqjjWb-R^Yxich_Qw{ zY>@P-^-Ac2KqwHMV_9yEz{se33^pFee1R*}h^^KZ5ZgQ8!UOh+oTPN&rXVZ5ANPoD z_q!28gpxF7m1Y1W^atNFAhnFsAZk1R)51eWbOCA5HKxOuGQ_}$U?wa@QVY$Bk#Jyi z{Rr!={Ll1XmNl<(Wa}SYvP+9y27dAW#lJ%{aZ|GtqWV%#EG?IVS@$pPxnj@MwVMUH zkwwNxD;%Mkc0oi?q%BBBN@iKu^jtYiPkuVWZy|W$y``ybmkr~w+!3RvVXYBNgn%`K>5gXfn-35z@Cmrbqfh^-u z7}s5R5mlf)wfQ!=a8D!a-<=AtYH}?YO)f8@1VUKCyXd6p#kl_9YQD0)P=_r*Ba1I; z2s+}-7+MpVPRHm`;SP5GLRz5v_31ZQg|>-JlLZaiz!QF4p6-iQ2h-{^ePaJwRX*sX ztjb{>vI0S#mvi1I*4s5Y5HbKKFy$;Z5E$W6v;0wx>P-Ligc7JTw8QyJPUJLHLMCbS zhz=J21s4@ZFyz9*Ef)<>J{RFBisPJw98eX|r2U5hoU3KbCKJ?*U^*yMd1V^LNaYu? zOo5*%&1`!yR#j1ah;z8uq|xkw!^L^9xL^U&GzZ`mLoSP@f@W@AcG-^;qOKtgp}Tf_ z??}m)23vlhci55}7l<@aQ0hs6i%6ZH=gFOn5Qc$%bSm)bi-@NH<-GJ(!?EL45d+(7f7Z-XS9czGd?BO)0iJAe z^~PVn(~aJCdQA(Q)EV~K0+U|-W{q-yaPzgwd8sVUBtlqL!t`ACwgLwrUMg&T9V0}+ zYOY)^+}@=l6afoWd(S3iefe zXIVS*T5LQ_DH6_;RX6J(1#*}IY&{DD)B%ZIXlksAgAMdDeUW8hI_(HJPt#)jQbp zvSnVZBMq4@ued@tj>cMeO#W%L+T3tHKjU?8S{Is!hHp7Lhp3k-b!|W~l(#2Y{`S6~ z7MipoBJy`#^0sj`UFXhXtZX~<+ttgCjGS6MeLpwd6A<|XeNq}V`>NE)VHQTG?e{fT zcFW0Vt_MO}0tkCYtaZUgUETl*CIHUfE?X^u)_l)yq9*}HrhMN{roQ0Az_lthD+M%0 z|$u^;d5eS8zsj)v3!*YtdTDWI^#j1gp@siJ=sr6G=?ala=%}1_j)W5 zXRr$gztTDgYx&Ui{3c7FMNJjn=*6Iw5&wIGc3&y$pz~gz!cNFN4Etsura&gfZGSxr zlGXgWNQawQcAvxn5aMt(-nY%w-KORl{6Vfv`WNJr(DW2 zas?$14*u`g-|c?{iDo1_b$Pi!Mi#s78}6w|S%x!eXueM!SB|Y07DQ7`k*hR>WOZV4 zpE=2Be{KvPf$F`J1DlZUZsz@hp{=J;8jk1Az0s2CIdKcY5UUfVM9311td1=peLZ8; zE{G`Oj*t`R>t*Biv5>vIKfgaNcd#|GF<*Ylw>p}c2|PVLeqL5S+EtK8Rg|NUj3_8I zB>_B$2fte)tb7?DH^fKm?V`ksgyb~@@*UlVzP)SFWJUK!sD_yIitdt!n2<*$bW1_x z$Cq*9U84ovLUY8iFFncuEN5>2&QO~BFF5(uAya{^M}{p*2=Wv_3DM32?_8mN)#mYK zJxGa#064+SY|!cVc=a;A+}}T3TAJS4nwM9i%xqDw!~D~?`eH`gDZ->f%RN*;_^|n7nXjV&P z+r?cg`kYD>otVOSjvvrH!Uq<9FK;uOQ?|5 z9eKLAg+=#QO#%x@+a*L%*br4hM#^A?z6Ik;U=WDosw7_JpQtMe%!q;#Ef;s|0Dj~I zIz`9&0E4}d7d?XqyW2kRDFtIi?&<}3#~c@ecfW;rv>GhhHsqWp=h~;{*e7+2gWK1f zpRsa-*vn|$5ogbLp8Q^wl(3?j#7%a@R5I|+2=G9tac*()C~}ilI0t#GOw8#Tnicr? zpnJD}fKN>Gp zQ$oeN!rw?q}hs?Z@vCo^o0|i(5~EGWa}&-~u5 zBe$e;+DkYbHoEzRl$`wer$clf8jXsQn&}=o+{@Tuz~4H|Z$l0dSff}4 zJbLsJVg>`Syl9tVou8Roxzh+@hwD;&u>a(Cn2@>g*5> zYQpx(0H(h4r6Jyo1?|;0J)3q(18f((OEs;TGeY-4Ky!#3jy`@7$HN_^m@s3qijP3XdH^ngW|F`R;fjDM%3YEnE#bD}In0 z4Gs{|1yHYSJmaxc`K#!@|J37>*}?W)zt#PIp%UNY!t~)Wm5b|8Lu0_AID&dsbQiNW zFHea704TPW2Px~v(9b{8nw|=b9Jmo;s0mzQ-=;i8paM9s6eLVUqn@q|jZJA-`M=@I zNhCT2n(rhS^5e;fkwWZsNPPy(uV85k^0~q8mKqjoO5$I@<{!kxdIx z;5qtay@MxlkU}w|#@E~Fx$GyInB?UYCz%+=n3=c|hg9@3YG*V`F8 zH)&J8`uG%jS7Lw~?zG0%HR?yKHI^!!ThgR2+=tf{(oqi>{F@n$&VRLV!&{Ane7EB5 zlkt2)v56Q*A^}25Tsvq!djl_yxpz+x>x;dQ- zhpXI<*7BAh>jWS1ksteJ2YMyV>CmTc-Qm^si@9jwGU2SA?!rR%IBF3n$|TWh(@_%R zUEtzhW~M1A${!ydHq^6+7g6@^BzoD+L%#O92JF>Lx(k};>5RJOvmM#BW=(eK(RO^F z=a@*Oacr~sbSKV{{{dWYg8hwT1O#T-0rhfm6Bieko4z%E=NB*U_x&;L(|3QTjeoN{ zr7hHR!&n>5=%$Tn05CJ6^ry%M`FmMo{WH2`njE z@(Rt`v7z5hli9q;gip!WjVC5bSka{m70w6jk(@@zyb8nGs7NoPvB5w7s9V8J7;qMp zRL{v5ryqLK6*HC*=yp@U&H6jd(FsL;t53Kn6;Iur2H88AyS- zBxPFdG1din}EB+D8>3C$QFV! z$Nzc4__$VE-MzdAN>0={wSM$G@p-K-5dMIn>H%HwZ2|hNT(>N{HK)Yj#KnWxjxAH$ zc<%FD{_LkzKuhn6`;om3oY;wfp8jBZI%U8E&%en4z}xX;{>N+5QQU{@)8z;0Q*S3? z#}nh@_P>VH<|s{j3={{nstuA0q8+xjv~$9<15S58%=)_6pIY687P{dBx-rUZ3v=3= z{e0t;gw)M{gB(1OH9u5l!xpdZ9oU^NS!#>v1C_We)TDEyK)ra06DmSj+rp8aOu2j0 zU1;rD;$!MbB9NDzJv}C8b7+2#2n;V0$E?AsOatMJkB$nR)U~46RqE=37)cyz53PWR zQy|90iQnw?Mn5Aaau+8RON%A{QL7x>4fSc5AFiyArL2~(Y8D@8X4`W`$PPfO%Oay2 z$Wj^nR>AvrXMU9XIWvXP7E<_2yrCgY1e~#7EE>CDC%*-1S~5Zmjxh@&7JT1t~@Pe%{0{+H`^czT3FIM8Y>c}=luW}X`V!xWC+HN9bBa)VUJEzyf;4G`B9&H zjzF(!PlThad@%~rD^UWe<+stgGLrXr(7Pk;3!>%+mO@vSM5>|XlKR{yKBZxNpv%k$ zA27fwOG7oj`l)Wmspoq^OavgUcqJ9QQ}RJ|L%M0MN(rn? z;ylBQfjrK-V6EYE(|P(&6?&u}`+;|Nf`UW0V2@VaaQUNMvoz@vXZ5 zB_BTcZyE7D!T%9;E(aD1>7pPax^wevLAU+7(w>_cplHVDuC<04%l%n6LU0SI3lHQtgcAd| zQ)|dZBqMT25gg^ofydSP!$aZODKn6WSXk#ep4zI$%8P z$PGXc^WBJtm+L!(AEQM31z!;5$bcI~C6d4$$kq`^{q0Mr@wOB{D93>Nua}KGIv8~P z+?vwZSd?NGqCC5zvbf|}+bmi8m#wq5(Yg`Wj4kMof%#mBkRDLmtiGhqOjN-Khvr*d zBaggkNZmk@o%8XvvdMa}CZFKixgJ64VDL|WkJQEa{f6NR^KS~rkP%9YqwqMeX^LC=iaTiwBdG_n8KYqQ|h$8Ma6C(QEm4gJO2U_-mj)BO5Ek=*Eu{ppLs>5jzhn)U9O zbMKkVZY5tK>JL+XuUz+kKin+CR95NvS0(u9^E*eoI(xg?e?bC$iXQeCGMaJIOpwNK zSrIRtAce2hi#f%jLSCt{%9CE9U&*&w#7%!*q5QCb{OtrTnz|*+K>x=hZ&j^CkHfL% z4zf=EvXt9W3xpmZum*;%7NA>^Eu_c@6%feWN28P%&lvtLcD=oQ=@*BL2mVIgH7$nv zKRGL_(lj;mSSua0IGe|GGeL}K>pM%!&KI_=?#*j_flW)C8uIj0vgY6GG2c{s1q!Z% z)I1%XtWHPHQ_jm(5pC>qb0>?pSq>qwKorJ|DSDCxbL_Vzuwtvu2zs13@NCgS8i*0F z9;iX?7T;Q?bdEXVCq*JC8OQi7f^2f42(M`7 zO<%2O)zT%E=t4$HO<|GvuX?*qr8cyW?J|FY&=N z3${1#Cg$K}q!Facp(-j+Nl5gT3L9{od=1Nk;N5}6g>VHSq6v93+J%P*3^kf{!P=|#?H`dh znVU5~o|B*7C8K*+)egBE8{6)qBVB&U3QSOSh}(H3t-aDUJs_*>vg(|EAu1gP7fG0m z^{_8?kAOo@<3ES|@FI*e%78&UAzo8InRwo*|y}=~K znsQE^Yg7$VB0R112ut&?q(^&D?fi^tv9_$Su&wa0tq3soCCOeG8?!z*c(M51i0Bf& z4^b2+8hQ2CG#G2?vsYDSa&pF;o*vx0(XZ+5t-+P_x52-P#a1GpzSQ|!Nz}9Ou<%h4 zBSHSW@aVu~PZT>@!OgE96tB>gO^t3%TR5sVV#eDTEA3!xq0VRb=RN)7Vi3a_5v9?H$VFqTPD`5~?J{7vdh zPP$p-eMNdB#iO{;LORNd!o3{L_$sVpMc52gXDX>&#=ondVR*dbCtZdWXv$36lLUSpf2GkqST}dtstQD4*QkP5NYAU0QV_5U$7*9h;q>2 zrVIcxLfg+|mQ_UJLkkAo=;tLOrTSJ+EiTdqw934XLOnD4^a+|Fe58}U4#HhL%BC@! zX9OhK2Or!Xn_GU2wt_Ypo8DL14&qK*1RXGghW61w&7Y{1N&oCSq=%2Ji#Crcl>ZlV zu=(H20WU@)i!P5XLnf?_=GGcustVd7cxNlbQIAPZ7hD+_G5J-OSIiMx8NO0>M9sBU z?dKU0EDI#;1KLJ6um(0G$oG+54#tq?6U8@eN}w?OgIh+^nI+r`qsINx*xJj*#=(aE zfuUf1XJ^K;{>B(Xg~LQ;g*-Ep;shg7(QH)0^wN}+t0N6B2L?WobaZsl02LfHmCA0? zb?x=i5qCpOTrBm#06aBS#O!ebttC|i)qtu=LDY2KlOx>^wlo4lg&0)o!SKWaFfuPt z$GFsz8^u$G2Jn?CxZ9H@3tkpfspRXL>zrXKzCxXtKH=MIITL6I$U0?LQQiY0-IdD_VlV+?T$+t2Q z@)tF3ycL8R$o@iC89Gm7Z!@UF-v#kdU78NRU&ljY@SfM4b9J|GX&{ntw5vp zZ;{%Dl(Ipl!v7R->`apG-X-rI1F_@Oq!A1HN&(_&QdL+L}Gz8r*Rxu8%WkWrI;hGB;Y9kX}}2pERz7?(lV61RY+ zT>DTsgjmwdJP$0&Mh({8EYqoP0nuincMl9%a z9VVMb3LTDr|8=)&{pNXSsldJNWFDqzN2B`~Est zNp>3UnU^6qtR71zkU4|>y6f!Ut3HqWmXH-!9k-l36nD?m6%JbYtLbj-l@^ueJJ7@A z49L$1V}xUP0>S|BO6Pq?4A03X`N6p298E`Loewe(mo3=EJ!iC*sLe`lXrNr@d=qdi!N&r`X_{Ziy_c_hfIu)lt8^G ziJNO>=LQ>m{42aIiIodG&ms(&G3GN@rd<$IR##}!wITl>KwM>i!8S#j5`P^4xiUOt zv>SW!*JAsQ{+G&7s4(rHUl`j!>zr}xC$+17D8-xS`VW;73&y3UO?nVvev#Jdh&t!j z#_^d0bE5*CD!Y|4P9}#rLp5RoTO8R_=l-=b4o%A3IVCY%bVjjGmRTCg#C3`x<$ z6lCeHa!i=Crev*ZTh1!>dd08DidmCCcH1AJZr@FA)+P5Ot5st+3)lImHvdYtIHfvz zVtz_3(0y7(mEyWYe~hh3Y40A{P%`tV`75Bcf=Ak*dd~ z{Us<~n>7P&Hz(g8U~jITTUn!xiKJFSU-cB@`S&aT| zH(OhqS{%uc(bpeiIM|szp3*YbPSyh*?ASDp-;c!hcJecsCw-|_mO3omyX6>xg{yQ( z=IO)huhG^Dt?w#<9o^po6Q<=vv*7k3@q)U$f*A=N0lc0Fg>(fZUsH#S(Rpm4UoX+^ zO$$FPnBQMF5a2W#Sxy>YtGpyEWoHFo;Di||FyQdmybvgw0wV#R^lF~`8HW1K z2Y0+n_9a_7up7b8;?z%;@3@=d$kPInO~AF!MFGzTPr6EoW7=sXyv<4V4aYw+0?wOh z23$f}*4Z>Nw2E)viMixOS&A*WFx^U7MSjlju+fZ7Uyr+-d{>3NmG(b+bN%4E?%*QFi48ftcrj?(_qyM>Szau_*_63!PdfSVgS3y7?M{1> z5C1?(OGpEOaKV{TEBox(;!;N!h3@;Z5%6UZI^N~Y9h&9;=jpF>$}tCB>}iY+`3>69 zLbC0Z2o(?3!NX-DX$T%2AVY86g~mXzLsvGZIbMAK<^unoAq2OPzHdt`I^~$IC*nv-RiN z8l8h(N^>(JOQSUQ*K1qOjBuv7>i*qrQ@V^{in`+`Z{7@P@x<#!EmJSXBmHY8c+H+` zM{cM&rkudRgT~CCfbziuJ*PGe{s}V&k~+Leiu(PTjcp?{d?|+!UB1kJ@c3;$DV25w z%R}2Um7H(;z3a9=NRt#NCxwZ9MT*lGY%InGv?KgB%!y{1keROZzfpg1Ep&aXKc0TX zD83gNCzx_6w%EGZ5B{Cyv}m4KwZka)nzqg2Ix=rOu}Xm$^fKdRsSAO|rD6;}@lvG+ z_h+1j6NgUoHrhr1of>!=_>w+dnJl!M+}yW?>Y_>wIw%nfE=>Ud+z?Z%nG-&{x6hZl z&hDTtpNTD6Sy+02-G67y$(DqoPzZe^#}Lo_Z=pwZ78>InDa;iG3|J9|Du_66IS9D7_m6UbdxENh?PMX0HC9FB1x*uumnR}pOSJv{sp`~Xo`9= zqK4;^A(!TAq`{L6E#&CG-j$#R@(zDd2tPG3RC3+cO^%xl@p-!FnAsD6J~U!R%Y zfunuQ)jfHPUBwp0u7K z)Cy~&&%2VL+0zZ7ZfPQb1Z>V;>yfIKnU{aJTqwsszJbI^kE>btk7tiu_`6+x%6S{P z(6{m_R$A4mR+}kO`J*+~rCX!QAp9@gm^6KIaB-2m6zdANKfilOhXOsSk37h+3;(JW z^wYh1{lINWNUv&e+k;P&pMJX0ld4sp?%Y6NWsQ$!p^0v_Z8a6u12vA*-5o@6>&n}G z-uSciPpU!$W#Vv=c!GvN(c<_~<1Bb~8{V1kSFHONqBD>la_TUh^H;jfEf+y)l<{?2ek}#RCDS9SnHbGsjokKU zcd`c@*F!iR-{&x$0HXpy-DCRs7g_wb(<0#X9#3}IzFxwZrPmzpi1x!!`B=B6Bx@&O zeQAMx3A0Iu(OF8t){ zWQV5zuTvkhXg4b(yaKHrn-sC^aN{XuWZv-pzNS@f6za+2?KXaqYQ>}5ICyB)ge$Rc zQ=nE;b0#Qr+U%cF<`n4_{F-V9vcGJV)fsH)zON7bcCs0TF`un#w_1fVSzqV4v^}z+ zRQLLs0h}m=W{(P_!=0{t>8pn(yHNjn%@E@q)P00+hC-GVHc2k@5r5n2NrXDNZ|Jr5 zrz{M1H8^dI$^xnZ28v0}ks!2|q$CUxqGEbGe19(ff4F+fs5ZlHTNHPv1gAI@2=2w* zt!QvsEXCd3-Q8V^lOV;N;@0BsEl}Lc&A0d6=bW*BJ^7t6-Z|!4Q6TXNL!ykJX$z*nPX|pm7-D=&>|vs z`fOEscyOo1hcp@FGb(UM9iswRs?Y(w-+ky)rT7`!H``Ef8~2iF-3cN%$8FVgKX*^p z>TDrltA1Kl7+&>AeI1E)YBoc@$oSac(@cnir-MAO%6~Do^&XGL?%+g`YvPrEOCvWD zZ9^`s0;o!{!0-z?$Q~vVuI5`@j5q!v=N+$g$2~5(Va~r&MsSD93v6I<4midQFIJDq zy8rZg!SF23q9Hbhx21{`m_+H1H)IU$@!5Di9o&8tFteX{&JI>rBB8dkU5>(AL~!{J ze4UpBP2@inX)1q0SkT1ZkOp=y8E(}s{nsmAwL}RiOLZ#6Kv|}>AsG@5!*WAPa3GBk zn&7mf9%z=2?D&JfO|#oq1HiXbQVHDykzl&AAwk&st7Mz|QWb*=WEltts=^zRF8NsS z0&6ufisuHZWXs{a*_CFs*cP*OqNP3<)>$g0YtSCx%C7epeP!7G+pzpP0cacDcO#`# z3*CUZ^Uh2PT`gB2WH4&2q2P#cjlZPoQZfvPW@nK!8Vvc=B z#ngBO?Io)G6CPTVN@y@6h>u8)n)-G&<*TRnd6Z^utQ#hhF;6qYF& zhy|>A_ZF8+!^6(iUsNiyxsHpnxycz@wNjEE9M^XO5qvUCUx^3$)QPR5b+20og}?I6 ztWL&S5%MbQ#!lKH{sd!qOt#PU=IU_7A2n^Um=(zdg7HXuvFz z(}z(97y)0&10R!Mjb%G`l)L%9X6Ktsn;q(1{)rFU_>ceS=3&HR;#VWNue9v4aN63j z@*zhV2Jw9Z*87dVv-b(~3&emjnObW#OTcukJaVkt)dQ!bU>M;pdt*ePYS-VH%z$fU z#((R(iz|;SV&x7R)}&2>jr_zTU&t48o|h^zhj$B;ImP=;UXFr){j>c0r$K+Hg(pNg zHkD);L6>3ls}6mB4tRD>RW4#>fOl=#gK6sw;MHAX`R2vRj1yk@c|2PmES!@SUzs`I zfm~#7kXe6Zxgr9`9^TxE)$HO9$uW0bXh+uuW}M|%)>_r*+%Bcmq-7owU}i%)w4H24 zMTkuSt2%$GH3@dG(%ll&x?!MsjF$;31!GT`8ASIIY(%7zk~WLq)n|J-W|q=%;`{>N zhospK^J^l^wlpdl@kfusyZ+l`fg<~xSfD zTWMt`b+Jo-sKmJ92UX+85GmlOeP{a5hM%(a12*Y8r2(TCNWaMHORQBJUDnyPZ6!%T ztjzZ`;P>&MS+4_=TCXZP!Zypt6R*24Wi-)ze4x$Zybnh7M7g_E>Vk$c&n{9hYesot{-Jt{Gy75x#rVTjfd7kn67z` zLU+3&dYxmbz$I9_6NL zu>8uTS7#YBEW=Nz$Ph9&2Bs%blPNtJ0^3csoO(VKx4HU;va;^*;Wgw&RbmWxv&uiq z{}RAucl<+XXDTGz?y+&=JYOwHl?eryh;?9(_Y12^!w7pJ+RrVpOA* z3(H0UW!l4w&13Zce3~;oD2q`|LN;erIoYOznyxI=)d>p)c z@Eb@!xP<)uLeEbWtuDXU(zIwH%w){*!$nqz`!S_iUo%}G?VzUFNK>a~^)$kvw5C?K zH9e&>OOMDUX1$XwS9_oER12hMkXa|s_+s@hB}qk4nxW8gmYqA>-wu{69=`Gx6vO4% zmkuTTdx>Bo+8WLm^WJhNyHwxGcvieBC)>i1Rhm}%OVw2}GIkGY+A!|#!L0{0DereZZ`=TjVN zxHr8l@3>(MW3bkjwYL6*v|fc}-j?x}tO*>=J?@pv^Y=}K=bvTV-GpsFG9<*3Uo8h~ zLGJaNVlNyoo16_CY(SeDVSsUrhWt>u+JdDjD{YJ*P?F}@5Jrc7ah6O_tO_@y!8`W; zmUil_!UI+HCMG6>)YQr9537ssyumSoqM?PkX)zL!}qVKnD^vjCrgSy8ODc# z>;e>MNYj88`vl*Nl(cI<2#XGWu>yT7)g}LRw)^N;BH?7`ZqH4T9X(Q<9nVC}BjnwK zq)xiZfjsLz(?3G!b|;>ORZ|L7Mj*1g*sYSjxNJH3R>U8K<8$iZgt?FP{5ywo&39PXZ_< zrZZORva-L`b2TsicEi+QERtBN`B%Zi;hH+tsAZ}W!zx_ynMRZ^EJ4OwjcBWHw2?e^^tIqq+DSvNU4^9hcmS*3D2Ok{TKNQ^Ln(3|RTB|Dm(5m~twVw_D0)3$HT_ zEgV#^>3GX7P{vMK&D{F)->b*8OJyuBnk^exucFmt|F=Wc!S6sUNUK8%=;-5d#Sz{7 z(f0@O46gS_sN&PO-Kp(vT|WOd-d%D$$Kd{y--_N7+b9U+;OIH+YW?hUQgJ`I_cvU? zB6Cv_!F-~yW-xHA-``o{;fe+SojBb9!0qxHy+cV4TNxK}qwDBDqxWC$YthdSRSd&c z=JMt`n3JCx`&48aYq!diU$e>kO8bT7)D?4|l-lqHy_Cv1W2MUXA zfJ-Fl`gEf2GuF<&s%n|AAXnBDm`(#y70*c2%9pD%oBSjpAIFj^X**vV=K~o>%l`(+ zy49`ici{PW#-BKG;{M&w;UebOdm|hf-Cq{P8X^R0L$uXiZ23_QOoS_L!kJU=FdxAk z8XpdR9N3k2nV?j&f6xd{OSEor)}YLfsYp$R6^$82;z+glKmLHMdBQ>?O}<{OzFycq ze1KfPodL&T3lA2`9zV;`-z{otO_tory1<_`VJ)Bi_m$t$MX$t(TKrM?*1nJkkTPO* zeh3ZtSyd>2u-I(II|(zB8gU;J7tu+~lob4|vF1Isg}5lyWoavn?h_QNu!KN>dodAE zWS{nSIBHA@;Pp#~!TAlZFF1vd#!4-Ul#uWyT5Fp(Sxqn6~Px$aZesQgnr^RnW|SgSCKc~wmh5hB zOYv1;m%<}&Fx=H^`^ozK=1#<2asR5Q;CJ6;JE1ngXH4OL$fvW8;c2GV3LMck#F<0> z^D6--esA+HMPI1s3_f#^;CLUM+jCiuso)ui-tq5d^n~R4x@OfJf%WkL;4eo%@0`KK*_+}+*n7U_Nu!4 zCTGlX-@x)S`&pWyfVmNg;V%3lZJ#vaXJQ*PzaY?GSW*OeiaBAtW)#x5!v@#fD35Lj z2%cheHv65FB#(btO#!&n<9IzaeS2j(dgj=*)uyjS-m;+{m^;>IA(E!WPF0vXr=hl! ze8=v@=)hW1eoljshb*jcW(j$D_<5~~S@kU0#Z>7UQ?C@yKQ^BL>an$^1)hzh{%pBj zTxPP*%*(-fZf6LqHoYqQ$b91WiC5OgC^6^}1=*qV!Bfr4FXv^~KpK8=I(pV3gMZN_ zWZR7lqPA)O3Z#Mr7Glfeo|+RM!hDzWEVd4BGP5G~ET1~Bh>_?a2X(VOwkSO!cIgsr zS}uk;nHBM9d{Zh$ykESR6ZB@6k%_|B#=b}jD(6?`9lJQtZ}U6Jv7SH`t%V=a>U3xp zHD}hBAD{Lmc_khrwLk?wpX10l+;b7gavG9*1gT#vUH4)8ibR*t_g|h0!=`(wPToG- z@1~v(tvSDpdLLGPkBKzLptbAh^}(^F^9iH`KVF>*s zNu5KVqHS0@0I^5|{Tgf(sw1B|ZhUAmYvCZu%}KEh4NTW7b{%OA_WS%b)lyu>Z1$!- zF4vo(rE8wJ^;_+Jdl`eD-;)Y?UcNY5StE7$`MtSJo$#xuwh+Z|)%i$OlA7j}Hdb+{ z$2`UP6t4)HFRC9yQiZ3|Bn4tMh)UtHmL;i9iQUL%Maz~{zAk+LjH}IEX8N5}O2A(SuFVPOw0Sfx;2} zN(fd_M#wjf6yk}Ps|fJtPmm6V28Xc;OP%U6c7XP9SB-WMQv=Ik+VD5F!W`9bWPA?S zscCig8J&gsv#!=Wef<_y1=-J1T^dmhh{R$V5T|7?FNtMfBR;%0hC^#GsLvTEdV=A) zy-$>$K%czmOgljfkI^8qTaZRti;5tx$$%H3%8qD6 zmIHeE;X+=}eqFbK)Jnv3Qsce@KOTNB0=Rv3p<_8%*;7OP zl>1oq%4f)uRk4m}&br|nr55cb>6-v*iw6wM8ah;emO}+dUM52w+xK34zFnZNA_yvc zXRio_79D-~!Mhj*a8URR+TnYe+lQ zWhYEP=56^G!k z{_Q6HePlmRJlILRmVR%Ex)o>QN74EBpYXv#e)v64oF@TY;7aOikD&A5f~T4HW5vZ} zDPALfH)2f`%9|18zcqdUKD5M1Z-oHYoL!0Z{T((sd6g`jXzc3L*rmGK10j)XfoI z$=u~q-sG+FM>R|9BXZup*1xxZOg-Z|iVBK#y52u^qk{9V1|CiCjs0w$olCUG|4?e5 zsh!}3oL{3nzh1f&aFb`_Q~fg&SXsn=qmTxH4gVT#^j^*?3A?p zcFeJDzLipGPMBO>UYg=aS*+hUfoU^y61&vaewGoe6lpXQ5RTcHW$6*Y!!w+dXQmd% zGyHjyiw0Is9gIIWGE%)~5#tm$MEqYTLQy9KyadJGkM2o^9{rWT$6emp)u&*KFJqMiLo z@ya*buSD*UGB)D<;k?!)l|NkSaR~aL^_(yKzK9U z;ZjD3kZmD}Md@u92?$=UDh0CfG}$S<`Xw1_y|3V15nk#+4EOnjFSe@v(&|0vh} zK?wmdurn2<#6jpI6DWhR|IbX8d9&reL8sGxn9=0Buz^tTj7yu!BQqMhD>vo#qFn!A}1XUE@94QAz-=nL*@ z4fi=<{GeUs0VZiE;)m_^q#pYcbpEAZykGgb&y;@IX0a@;EmL@AYohK_zdU0C>2=|N z38MSEYtcQp+=zk9IFiHxJ*Y=}2GA~I7fHpe6X|E2-*Kbr<@G6DaJ3X^j!Lywy*(wB z@uv9lMCOkkv$10*@T6|~IGX?ooZ;N>V#=%*k}^=fOsRoZXfl|WARa(=Ozjr({xqt99((Dao=L`;vyy;}?Ekp>$33TL$*wids5@zNytNVe7{Y{=Q@LeU

{H&xTE3GS6Y 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�#68sG;*3#T==n=~mC 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 + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8043dd9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[tool.black] +line-length = 99 + +[tool.isort] +line_length = 99 +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +indent = "\t" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7668191 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +# frappe -- https://github.com/frappe/frappe is installed via 'bench init' \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1a0b2cc --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup, find_packages + +with open("requirements.txt") as f: + install_requires = f.read().strip().split("\n") + +# get version from __version__ variable in healthcare/__init__.py +from healthcare import __version__ as version + +setup( + name="healthcare", + version=version, + description="healthcare", + author="healthcare", + author_email="healthcare", + packages=find_packages(), + zip_safe=False, + include_package_data=True, + install_requires=install_requires, +)