From 85aab34421075fcf6530691e0e235154d1efb7bb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 2 Mar 2020 23:14:30 +0530 Subject: [PATCH] feat: Patient Appointment Analytics Script Report --- .../patient_appointment.json | 3 +- .../patient_appointment_analytics/__init__.py | 0 .../patient_appointment_analytics.js | 73 +++++++ .../patient_appointment_analytics.json | 36 ++++ .../patient_appointment_analytics.py | 183 ++++++++++++++++++ 5 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 erpnext/healthcare/report/patient_appointment_analytics/__init__.py create mode 100644 erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js create mode 100644 erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json create mode 100644 erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index be431849277..51db3e9ce1d 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -228,7 +228,6 @@ "default": "0", "fieldname": "invoiced", "fieldtype": "Check", - "in_list_view": 1, "label": "Invoiced", "read_only": 1 }, @@ -286,7 +285,7 @@ } ], "links": [], - "modified": "2020-02-25 17:57:56.971064", + "modified": "2020-03-02 14:35:54.040428", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/report/patient_appointment_analytics/__init__.py b/erpnext/healthcare/report/patient_appointment_analytics/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js new file mode 100644 index 00000000000..6494ef269ab --- /dev/null +++ b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js @@ -0,0 +1,73 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// 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 + } + ] +}; diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json new file mode 100644 index 00000000000..64750c012f1 --- /dev/null +++ b/erpnext/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/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py new file mode 100644 index 00000000000..627c38809c2 --- /dev/null +++ b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py @@ -0,0 +1,183 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import getdate, flt, add_to_date, add_days +from frappe import _ , scrub +from six import iteritems +from erpnext.accounts.utils import get_fiscal_year + +def execute(filters=None): + return Analytics(filters).run() + +class Analytics(object): + def __init__(self, filters=None): + 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() + + self.chart = '' + return self.columns, self.data, None, self.chart + + def get_period_date_ranges(self): + from dateutil.relativedelta import relativedelta, MO + 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': 'Float', + 'width': 120 + }) + + self.columns.append({ + 'label': _('Total'), + 'fieldname': 'total', + 'fieldtype': 'Float', + '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_chart_data(self): + pass + + def get_period(self, appointment_date): + if self.filters.range == 'Weekly': + period = 'Week ' + str(appointment_date.isocalendar()[1]) + ' ' + str(appointment_date.year) + elif self.filters.range == 'Monthly': + period = str(self.months[appointment_date.month - 1]) + ' ' + str(appointment_date.year) + elif self.filters.range == 'Quarterly': + period = 'Quarter ' + str(((appointment_date.month - 1) // 3) + 1) + ' ' + str(appointment_date.year) + else: + year = get_fiscal_year(appointment_date, company=self.filters.company) + period = str(year[0]) + + 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 iteritems(self.appointment_periodic_data): + 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 \ No newline at end of file