diff --git a/docs/StudentHealthTracking.md b/docs/StudentHealthTracking.md index 20b2fa9b..317e098d 100644 --- a/docs/StudentHealthTracking.md +++ b/docs/StudentHealthTracking.md @@ -61,4 +61,16 @@ the Death Information panel will appear so you can also input the Cause of Death sent. View the **Cause of Death Report** and the **Student Health Report** by navigating to the Reports module and clicking -on the appropriate report title. \ No newline at end of file +on the appropriate report title. + +## Student Vitals Dashlet +A new dashlet is available to track the number of days students are in a given Vitals status. This dashlet leverages +the Contacts Audit table to populate a pie chart summarizing the number of days all Students in a given Super Group (or all Super Groups) +are in each Vital status. + +This dashlet displays use of the Sucrose charts in a custom dashlet and is available in Home, List Views, and Record Views. + +There is also an accompanying API enpoint for retrieve the data from the Student records and shows use of Sugar Query in joins +and union queries. For more information on the endpoint see /rest/v11/help. + +The pull request for the Student vitals dashlet can be viewed in [#27](https://github.com/sugarcrm/school/pull/27). diff --git a/package/pack.php b/package/pack.php index f4ff487d..b8a3fbf4 100755 --- a/package/pack.php +++ b/package/pack.php @@ -199,7 +199,7 @@ 'default_value' => 'active', 'date_modified' => '2018-02-01 21:32:16', 'deleted' => '0', - 'audited' => '0', + 'audited' => '1', 'mass_update' => '1', 'duplicate_merge' => '1', 'reportable' => '1', diff --git a/package/src/custom/Extension/application/Ext/Language/en_us.student_vitals_dashboard.php b/package/src/custom/Extension/application/Ext/Language/en_us.student_vitals_dashboard.php new file mode 100644 index 00000000..e88c1120 --- /dev/null +++ b/package/src/custom/Extension/application/Ext/Language/en_us.student_vitals_dashboard.php @@ -0,0 +1,6 @@ + array( + 'reqType' => 'GET', + 'noLoginRequired' => false, + 'path' => array('professorM', 'getStudentVitalData', '?'), + 'pathVars' => array('', '', 'supergroup'), + 'method' => 'getStudentVitalData', + 'shortHelp' => 'API End point to retrieve data for vitals dashlet', + 'longHelp' => 'custom/include/api/help/student_vitals_api_help.html', + ), + ); + } + + /** + * API Endpoint for student vitals chart + * @param $api + * @param $args + * + * @return false|string + * @throws SugarQueryException + */ + public function getStudentVitalData($api, $args) + { + + global $app_list_strings; + require_once('custom/include/ProfessorM/StudentVitalHelper.php'); + $supergroup = $args['supergroup']; + $helper = new \Sugarcrm\ProfessorM\Helpers\StudentVitalHelper(); + $status_data = $helper->getStudentVitalsByDays($supergroup); + + // Sort if we have an array + if (is_array($status_data)) { + arsort($status_data); + } + $chart_data = array( + + ); + + foreach ($status_data as $key => $value) { + + $chart_data[] = array( + "key" => $app_list_strings['vitals_list'][$key], + "values" => array( + array("x"=> 1, "y"=> $value), + ) + ); + + } + $data = array( + "properties" => array( + "title" => "Student Vitals Days Count", + ), + "data"=> $chart_data + + + ); + + + + return json_encode($data); + + } + +} diff --git a/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.hbs b/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.hbs new file mode 100644 index 00000000..ab0eecec --- /dev/null +++ b/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.hbs @@ -0,0 +1,6 @@ +
+
+ +
+ +
diff --git a/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.js b/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.js new file mode 100644 index 00000000..c55291df --- /dev/null +++ b/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.js @@ -0,0 +1,131 @@ +/* + * Your installation or use of this SugarCRM file is subject to the applicable + * terms available at + * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/. + * If you do not agree to all of the applicable terms or do not have the + * authority to bind the entity as an authorized representative, then do not + * install or use this SugarCRM file. + * + * Copyright (C) SugarCRM Inc. All rights reserved. + */ +({ + plugins: ['Dashlet', 'Chart'], + className: 'student-vital-chart', + chartCollection: null, + hasData: false, + total: 0, + + /** + * @inheritdoc + */ + initialize: function(options) { + this._super('initialize', [options]); + + this.chart = sucrose.charts.pieChart() + .donut(true) + .donutLabelsOutside(true) + .donutRatio(0.25) + .hole(false) + .showTitle(true) + .tooltips(true) + .showLegend(true) + .colorData('class') + .tooltipContent(_.bind(function(eo, properties) { + + var value = parseInt(this.chart.getValue()(eo), 10); + var total = parseInt(properties.total, 10); + (total == 0) ? total = 1 : ''; + var percentage = value/total * 100; + return '

' + this.chart.fmtKey()(eo) + '

' + + '

' + value + ' days

' + + '

' + percentage.toFixed(2) + '%

'; + }, this)) + .strings({ + noData: app.lang.get('LBL_CHART_NO_DATA') + }); + }, + + /** + * Override to set options for supergroup selector in config dynamically + */ + initDashlet: function() { + if (this.meta.config) { + + var supergroup_value = this.meta.vitals_dashlet_supergroup ? this.meta.vitals_dashlet_supergroup : 'all'; + var accounts = app.data.createBeanCollection('Accounts'); + var supergroups = []; + supergroups.push({id: 'all', text:'All'}) + accounts.fetch({ + success: function() { + accounts.comparator = 'name'; + accounts.sort({silent: true}); + _.each(accounts.models, function(account){ + supergroups.push({id: account.id, text: account.attributes.name}); + }); + + $('[name="vitals_dashlet_supergroup"]').html('').select2({data: supergroups, width: '100%'}); + $('[name="vitals_dashlet_supergroup"]').val(supergroup_value).trigger('change'); + } + }) + } + + this._super('initDashlet'); + }, + + + /** + * Generic method to render chart with check for visibility and data. + * Called by _renderHtml and loadData. + */ + renderChart: function() { + var self = this; + if (!self.isChartReady()) { + + return; + } + + + d3sugar.select(this.el).select('svg#' + this.cid) + .datum(self.chartCollection) + .call(self.chart); + + this.chart_loaded = _.isFunction(this.chart.update); + this.displayNoData(!this.chart_loaded); + + }, + + /** + * @inheritdoc + */ + loadData: function(options) { + + if(this.meta.config) { + + return; + } + var self = this; + var supergroup_value = this.meta.vitals_dashlet_supergroup ? this.meta.vitals_dashlet_supergroup : 'all'; + url = app.api.buildURL('professorM/getStudentVitalData/' + supergroup_value); + this.hasData = false; + app.api.call('GET', url, null, { + success: function(data) { + self.hasData = true; + self.evaluateResponse(data); + self.render(); + }, + complete: options ? options.complete : null + }); + + }, + + + evaluateResponse: function(data) { + + this.total = 1; + this.hasData = true; + this.chartCollection = $.parseJSON(data); + + }, + + +})   diff --git a/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.php b/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.php new file mode 100644 index 00000000..728a0bd0 --- /dev/null +++ b/package/src/custom/clients/base/views/student-vital-chart/student-vital-chart.php @@ -0,0 +1,49 @@ + array( + array( + 'label' => 'LBL_STUDENT_VITAL_CHART', + 'description' => 'LBL_STUDENT_VITAL_CHART_DESC', + 'config' => array( + 'date_range' => 'all', + 'supergroup' => 'all', + ), + 'preview' => array( + ), + 'filter' => array( + 'view' => array( + 'records', + 'record' + ) + ), + + ), + + ), + 'panels' => array( + array( + 'name' => 'panel_body', + 'columns' => 2, + 'labelsOnTop' => true, + 'placeholders' => true, + 'fields' => array( + + array( + 'name' => 'vitals_dashlet_supergroup', + 'label' => 'LBL_VITALS_DASHLET_SELECT_TEAM', + 'type' => 'enum', + 'span' => 6, + 'options' => array('all' => 'All'), + ), + array( + 'name' => 'vitals_dashlet_date_range', + 'label' => 'LBL_VITALS_DASHLET_SELECT_DATE_RANGE', + 'type' => 'enum', + 'span' => 6, + 'options' => array('all' => 'All Time', 'ThisYear' => 'This Year', 'LastYear' => 'Last Year'), + ), + ), + ), + ), +); diff --git a/package/src/custom/include/ProfessorM/StudentVitalHelper.php b/package/src/custom/include/ProfessorM/StudentVitalHelper.php new file mode 100644 index 00000000..f71f0131 --- /dev/null +++ b/package/src/custom/include/ProfessorM/StudentVitalHelper.php @@ -0,0 +1,261 @@ +timedate = $timedate; + $now_dt = clone $this->timedate; + $this->now = $now_dt->nowDb(); + + } + + /** + * Called by API to query days in Vitals status using contacts (Students) + * and contacts_audit tables + * @param $supergroup + * + * @return mixed + * @throws \SugarQueryException + */ + public function getStudentVitalsByDays($supergroup) + { + + $change_list = $this->getVitalChangeData($supergroup); + + $students_array = $this->compressStudentListToStudentIDArray($change_list); + + $this->setStatusList($change_list); + + $this->countStatusDays($students_array); + + return $this->status_list; + + } + + + /** + * Query to retrieve student vitals transactions + * @param $supergroup + * + * @return array + * @throws \SugarQueryException + */ + public function getVitalChangeData($supergroup) + { + + /** + * Query 1 is to get create date of student record + */ + $query1 = new \SugarQuery(); + $query1->from(\BeanFactory::newBean('Contacts')); + $query1->select( + array( + array('id', 'student_id'), + array('date_entered', 'transaction_date'), + array('vitals_c', 'start_status'), + array('vitals_c', 'end_status'), + ) + ); + + + /** + * Query 2 retrieves data from Audit table + */ + $query2 = new \SugarQuery(); + + $query2->from(\BeanFactory::newBean('Contacts')); + $query2->joinTable('contacts_audit', array( + 'alias' => 'ca', + 'joinType' => 'INNER', + 'linkingTable' => true, + ))->on() + ->equalsField('contacts.id', 'ca.parent_id') + ->equals('ca.field_name', 'vitals_c'); + $query2->select(array( + array('id', 'student_id'), + array('ca.date_created', 'transaction_date'), + array('ca.before_value_string', 'start_status'), + array('ca.after_value_string', 'end_status'), + )); + + + /** + * Format for for supergroups + */ + if ($supergroup != 'all') { + $query1->joinTable('accounts_contacts', array('alias' => 'ac'))->on() + ->equalsField('contacts.id','ac.contact_id') + ->equals('ac.account_id',$supergroup); + $query2 ->joinTable('accounts_contacts', array('alias' => 'ac'))->on() + ->equalsField('contacts.id','ac.contact_id') + ->equals('ac.account_id',$supergroup); + } + + $sqUnion = new \SugarQuery(); + $sqUnion->union($query1); + $sqUnion->union($query2); + $sqUnion->orderBy('student_id', 'ASC'); + $sqUnion->orderBy('transaction_date', 'ASC'); + + $results = $sqUnion->execute(); + + return $results; + + } + + /** + * Retrieves unique values from an array. + * Used to get listing of vitals transaction in recordset + * @param $array + * @param $key + * + * @return array + */ + protected function getUniqueArray($array, $key) + { + + $grouped_array = array(); + foreach ($array as $row) { + $grouped_array[] = $row[$key]; + } + + $grouped_array = array_unique($grouped_array); + + return $grouped_array; + } + + /** + * Set array of all unique start and end statuses in record set. + * Used to populate return data to API + * + * @param $change_list + */ + protected function setStatusList($change_list) + { + + $start_statuses = $this->getUniqueArray($change_list, 'start_status'); + $end_statuses = $this->getUniqueArray($change_list, 'end_status'); + + $combined_statuses = array_unique(array_merge($start_statuses, $end_statuses)); + + foreach ($combined_statuses as $status) { + + //Set initial days for each status to zero + $this->status_list[$status] = 0; + + } + } + + /** + * Format query record set to be grouped by Student IDs + * @param $change_list + * + * @return array + */ + protected function compressStudentListToStudentIDArray($change_list) + { + $student_change_array = array(); + foreach ($change_list as $row) { + $student_change_array[$row['student_id']][] = $row; + } + + return $student_change_array; + } + + /** + * Loop through transactions (changes in vitals) by student and + * appends days in each transaction to totals + * @param $student_change_list + */ + protected function countStatusDays($student_change_list) + { + + foreach ($student_change_list as $student_id => $changes) { + + $max_array = count($changes) - 1; + + for ($i = 0; $i <= $max_array; $i++) { + $days_in_status = $this->countTransaction($changes[$i], $changes[$i + 1]); + + if ($i == $max_array) { + $append_status = $changes[$i]['end_status']; + } else { + $append_status = $changes[$i + 1]['start_status']; + } + + $this->status_list[$append_status] += $days_in_status; + } + + } + } + + + /** + * Count days in vital status between before and after transaction + * @param $before + * @param null $after + * + * @return mixed + */ + protected function countTransaction($before, $after = null) + { + + $start_string = $before['transaction_date']; + + if (!$after) { + $end_string = $this->now; + } else { + $end_string = $after['transaction_date']; + } + + $days = $this->countDaysDiff($start_string, $end_string); + + return $days; + + } + + /** + * Count days between dates + * @param $before_date + * @param $after_date + * + * @return mixed + */ + protected function countDaysDiff($before_date, $after_date) + { + $start = clone $this->timedate; + $end = clone $this->timedate; + + $start_td = $start->fromString($before_date); + $end_td = $end->fromString($after_date); + $interval = $start_td->diff($end_td); + + $days = $interval->days; + + return $days; + + } + +} \ No newline at end of file diff --git a/package/src/custom/include/api/help/student_vitals_api_help.html b/package/src/custom/include/api/help/student_vitals_api_help.html new file mode 100644 index 00000000..bfc3be5f --- /dev/null +++ b/package/src/custom/include/api/help/student_vitals_api_help.html @@ -0,0 +1,144 @@ + +

Overview

+ + Get student vitals data for Student Vitals Dashlet + + +

+ + This endpoint is used for by the Student Vitals dashlet to poll the audit table on the Contacts table + for changes in the vitals_c field. The output is a summary of the number of days in each status the Students + in a given Super Group are in. The results are formatted to the expected input for a pie chart dashlet. + +

+ +

Request Arguments

+ + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
+ supergroup + + String + + Super Group to collect data from. Options are pulled in dynamically in Dashlet Config and the ID is passed. + Also can pass 'all' to get all Super Groups + + true +
+ +

Sample Request

+
+    rest/v11_4/professorM/getStudentVitalData/all
+
+ +

Response Arguments

+ + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ properties.title + + String + + Display name of the chart +
+ data + + Array + + An array of results grouped by vitals_c value and days in each status +
+ + + +

Response

+
+{
+  "properties": {
+    "title": "Student Vitals Days Count"
+  },
+  "data": [
+    {
+      "key": "Active",
+      "values": [
+        {
+          "x": 1,
+          "y": 58
+        }
+      ]
+    },
+    {
+      "key": "Injured",
+      "values": [
+        {
+          "x": 1,
+          "y": 43
+        }
+      ]
+    },
+    {
+      "key": "Deceased",
+      "values": [
+        {
+          "x": 1,
+          "y": 15
+        }
+      ]
+    },
+    {
+      "key": "Comatose",
+      "values": [
+        {
+          "x": 1,
+          "y": 12
+        }
+      ]
+    }
+  ]
+}
+
+