feat: Patient Appointment Analytics Script Report
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index be43184..51db3e9 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 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/report/patient_appointment_analytics/__init__.py
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 0000000..6494ef2
--- /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 0000000..64750c0
--- /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 0000000..627c388
--- /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