Fix for exporting a large number of lists.
[openemr.git] / library / reminders.php
blob2d89f9777dde54c91cf24cbeb37ac08988142883
1 <?php
3 /**
4 * Patient reminders functions.
6 * Functions for collection/displaying/sending patient reminders. This is
7 * part of the CDR engine, which can be found at library/clinical_rules.php.
9 * Copyright (C) 2010-2012 Brady Miller <brady.g.miller@gmail.com>
11 * LICENSE: This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
22 * @package OpenEMR
23 * @author Brady Miller <brady.g.miller@gmail.com>
24 * @link https://www.open-emr.org
27 /**
28 * Include the main CDR engine library, email class and maviq class
30 require_once(dirname(__FILE__) . "/clinical_rules.php");
31 require_once(dirname(__FILE__) . "/maviq_phone_api.php");
33 //only used in commented out code
34 use OpenEMR\Common\Crypto\CryptoGen;
36 /**
37 * Display the patient reminder widget.
39 * @param integer $patient_id pid of selected patient
40 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
42 function patient_reminder_widget($patient_id, $dateTarget = '')
45 // Set date to current if not set
46 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
48 // Update reminders for patient
49 update_reminders($dateTarget, $patient_id);
51 // Fetch the active reminders
52 $listReminders = fetch_reminders($patient_id);
54 if (empty($listReminders)) {
55 // No reminders to show.
56 echo xlt('No active patient reminders.');
57 return;
60 echo "<table cellpadding='0' cellspacing='0'>";
61 foreach ($listReminders as $reminder) {
62 echo "<tr><td style='padding:0 1em 0 1em;'><span class='small'>";
63 // show reminder label
64 echo generate_display_field(array('data_type' => '1','list_id' => 'rule_action_category'), $reminder['category']) .
65 ": " . generate_display_field(array('data_type' => '1','list_id' => 'rule_action'), $reminder['item']);
66 echo "</span></td><td style='padding:0 1em 0 1em;'><span class='small'>";
67 // show reminder due status
68 echo generate_display_field(array('data_type' => '1','list_id' => 'rule_reminder_due_opt'), $reminder['due_status']);
69 echo "</span></td><td style='padding:0 1em 0 1em;'><span class='small'>";
70 // show reminder sent date
71 if (empty($reminder['date_sent'])) {
72 echo xlt('Reminder Not Sent Yet');
73 } else {
74 echo text(xl('Reminder Sent On') . ": " . $reminder['date_sent']);
77 echo "</span></td></tr>";
80 echo "</table>";
83 /**
84 * Function to update reminders via a batching method to improve performance and decrease memory overhead.
86 * Function that updates reminders and returns an array with a specific data structure.
87 * <pre>The data structure of the return array includes the following elements
88 * 'total_active_actions' - Number of active actions.
89 * 'total_pre_active_reminders' - Number of active reminders before processing.
90 * 'total_pre_unsent_reminders' - Number of unsent reminders before processing.
91 * 'total_post_active_reminders' - Number of active reminders after processing.
92 * 'total_post_unsent_reminders' - Number of unsent reminders after processing.
93 * 'number_new_reminders' - Number of new reminders
94 * 'number_updated_reminders' - Number of updated reminders (due_status change)
95 * 'number_inactivated_reminders' - Number of inactivated reminders.
96 * 'number_unchanged_reminders' - Number of unchanged reminders.
97 * </pre>
99 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
100 * @param integer $batchSize number of patients to batch (default is 25; plan to optimize this default setting in the future)
101 * @param integer $report_id id of report in database (if already bookmarked)
102 * @param boolean $also_send if TRUE, then will also call send_reminder when done
103 * @return array see above for data structure of returned array
105 function update_reminders_batch_method($dateTarget = '', $batchSize = 25, $report_id = null, $also_send = false)
108 // Default to a batchsize, if empty
109 if (empty($batchSize)) {
110 $batchSize = 25;
113 // Collect total number of pertinent patients (to calculate batching parameters)
114 $totalNumPatients = (int)buildPatientArray('', '', '', null, null, true);
116 // Cycle through the batches and collect/combine results
117 if (($totalNumPatients % $batchSize) > 0) {
118 $totalNumberBatches = floor($totalNumPatients / $batchSize) + 1;
119 } else {
120 $totalNumberBatches = floor($totalNumPatients / $batchSize);
123 // Prepare the database to track/store results
124 if ($also_send) {
125 $report_id = beginReportDatabase("process_send_reminders", '', $report_id);
126 } else {
127 $report_id = beginReportDatabase("process_reminders", '', $report_id);
130 setTotalItemsReportDatabase($report_id, $totalNumPatients);
132 $patient_counter = 0;
133 for ($i = 0; $i < $totalNumberBatches; $i++) {
134 $patient_counter = $batchSize * ($i + 1);
135 if ($patient_counter > $totalNumPatients) {
136 $patient_counter = $totalNumPatients;
139 $update_rem_log_batch = update_reminders($dateTarget, '', (($batchSize * $i) + 1), $batchSize);
140 $update_rem_log = array();
141 if ($i == 0) {
142 // For first cycle, simply copy it to update_rem_log
143 $update_rem_log = $update_rem_log_batch;
144 } else {
145 // Debug statements
146 //error_log("CDR: ".print_r($update_rem_log,TRUE),0);
147 //error_log("CDR: ".($batchSize*$i). " records",0);
149 // Integrate batch results into main update_rem_log
150 $update_rem_log['total_active_actions'] = $update_rem_log['total_active_actions'] + $update_rem_log_batch['total_active_actions'];
151 $update_rem_log['total_pre_active_reminders'] = $update_rem_log['total_pre_active_reminders'] + $update_rem_log_batch['total_pre_active_reminders'];
152 $update_rem_log['total_pre_unsent_reminders'] = $update_rem_log['total_pre_unsent_reminders'] + $update_rem_log_batch['total_pre_unsent_reminders'];
153 $update_rem_log['number_new_reminders'] = $update_rem_log['number_new_reminders'] + $update_rem_log_batch['number_new_reminders'];
154 $update_rem_log['number_updated_reminders'] = $update_rem_log['number_updated_reminders'] + $update_rem_log_batch['number_updated_reminders'];
155 $update_rem_log['number_unchanged_reminders'] = $update_rem_log['number_unchanged_reminders'] + $update_rem_log_batch['number_unchanged_reminders'];
156 $update_rem_log['number_inactivated_reminders'] = $update_rem_log['number_inactivated_reminders'] + $update_rem_log_batch['number_inactivated_reminders'];
157 $update_rem_log['total_post_active_reminders'] = $update_rem_log['total_post_active_reminders'] + $update_rem_log_batch['total_post_active_reminders'];
158 $update_rem_log['total_post_unsent_reminders'] = $update_rem_log['total_post_unsent_reminders'] + $update_rem_log_batch['total_post_unsent_reminders'];
161 //Update database to track results
162 updateReportDatabase($report_id, $patient_counter);
165 // Create an array for saving to database (allows combining with the send log)
166 $save_log = array();
167 $save_log[] = $update_rem_log;
169 // Send reminders, if this was selected
170 if ($also_send) {
171 $log_send = send_reminders();
172 $save_log[] = $log_send;
175 // Record combo results in database
176 finishReportDatabase($report_id, json_encode($save_log));
178 // Just return the process reminders array
179 return $update_rem_log;
183 * Function to update reminders.
185 * Function that updates reminders and returns an array with a specific data structure.
186 * <pre>The data structure of the return array includes the following elements
187 * 'total_active_actions' - Number of active actions.
188 * 'total_pre_active_reminders' - Number of active reminders before processing.
189 * 'total_pre_unsent_reminders' - Number of unsent reminders before processing.
190 * 'total_post_active_reminders' - Number of active reminders after processing.
191 * 'total_post_unsent_reminders' - Number of unsent reminders after processing.
192 * 'number_new_reminders' - Number of new reminders
193 * 'number_updated_reminders' - Number of updated reminders (due_status change)
194 * 'number_inactivated_reminders' - Number of inactivated reminders.
195 * 'number_unchanged_reminders' - Number of unchanged reminders.
196 * </pre>
198 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
199 * @param integer $patient_id pid of patient. If blank then will check all patients.
200 * @param integer $start applicable patient to start at (when batching process)
201 * @param integer $batchSize number of patients to batch (when batching process)
202 * @return array see above for data structure of returned array
204 function update_reminders($dateTarget = '', $patient_id = '', $start = null, $batchSize = null)
207 $logging = array();
209 // Set date to current if not set
210 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
212 // Collect reminders (note that this function removes redundant and keeps the most distant
213 // reminder (ie. prefers 'past_due' over 'due' over 'soon_due')
214 // Note that due to a limitation in the test_rules_clinic function, the patient_id is explicitly
215 // needed to work correctly. So rather than pass in a '' patient_id to do the entire clinic,
216 // we instead need to pass in each patient_id separately.
217 $collectedReminders = array();
218 $patient_id_complete = "";
219 if (!(empty($patient_id))) {
220 // only one patient id, so run the function
221 $collectedReminders = test_rules_clinic('', 'patient_reminder', $dateTarget, 'reminders-due', $patient_id);
222 $patient_id_complete = $patient_id;
223 } else {
224 // as described above, need to pass in each patient_id
225 // Collect all patient ids
226 $patientData = buildPatientArray('', '', '', $start, $batchSize);
228 $first_flag = true;
229 foreach ($patientData as $patient) {
230 // collect reminders
231 $tempCollectReminders = test_rules_clinic('', 'patient_reminder', $dateTarget, 'reminders-due', $patient['pid']);
233 // build the $patient_id_complete variable
234 if ($first_flag) {
235 $patient_id_complete .= $patient['pid'];
236 $first_flag = false;
237 } else {
238 $patient_id_complete .= "," . $patient['pid'];
241 $collectedReminders = array_merge($collectedReminders, $tempCollectReminders);
244 $logging['total_active_actions'] = count($collectedReminders);
246 // For logging purposes only:
247 // Collect number active of active and unsent reminders
248 $logging['total_pre_active_reminders'] = count(fetch_reminders($patient_id_complete));
249 $logging['total_pre_unsent_reminders'] = count(fetch_reminders($patient_id_complete, 'unsent'));
251 // Migrate reminders into the patient_reminders table
252 $logging['number_new_reminders'] = 0;
253 $logging['number_updated_reminders'] = 0;
254 $logging['number_unchanged_reminders'] = 0;
256 foreach ($collectedReminders as $reminder) {
257 // See if a reminder already exist
258 $sql = "SELECT `id`, `pid`, `due_status`, `category`, `item` FROM `patient_reminders` WHERE " .
259 "`active`='1' AND `pid`=? AND `category`=? AND `item`=?";
260 $result = sqlQueryCdrEngine($sql, array($reminder['pid'], $reminder['category'], $reminder['item']));
262 if (empty($result)) {
263 // It does not yet exist, so add a new reminder
264 $sql = "INSERT INTO `patient_reminders` (`pid`, `due_status`, `category`, `item`, `date_created`) " .
265 "VALUES (?, ?, ?, ?, NOW())";
266 sqlStatementCdrEngine($sql, array($reminder['pid'], $reminder['due_status'], $reminder['category'], $reminder['item']));
267 $logging['number_new_reminders']++;
268 } else {
269 // It already exist (see if if needs to be updated via adding a new reminder)
270 if ($reminder['due_status'] == $result['due_status']) {
271 // No change in due status, so no need to update
272 $logging['number_unchanged_reminders']++;
273 continue;
274 } else {
275 // Change in due status, so inactivate current reminder and create a new one
276 // First, inactivate the previous reminder
277 $sql = "UPDATE `patient_reminders` SET `active` = '0', `reason_inactivated` = 'due_status_update', " .
278 "`date_inactivated` = NOW() WHERE `id`=?";
279 sqlStatementCdrEngine($sql, array($result['id']));
280 // Then, add the new reminder
281 $sql = "INSERT INTO `patient_reminders` (`pid`, `due_status`, `category`, `item`, `date_created`) " .
282 "VALUES (?, ?, ?, ?, NOW())";
283 sqlStatementCdrEngine($sql, array($reminder['pid'], $reminder['due_status'], $reminder['category'], $reminder['item']));
288 // Inactivate reminders that no longer exist
289 // Go through each active reminder and ensure it is in the current list
290 $sqlReminders = fetch_reminders($patient_id_complete);
291 $logging['number_inactivated_reminders'] = 0;
292 foreach ($sqlReminders as $row) {
293 $inactivateFlag = true;
294 foreach ($collectedReminders as $reminder) {
295 if (
296 ($row['pid'] == $reminder['pid']) &&
297 ($row['category'] == $reminder['category']) &&
298 ($row['item'] == $reminder['item']) &&
299 ($row['due_status'] == $reminder['due_status'])
301 // The sql reminder has been confirmed, so do not inactivate it
302 $inactivateFlag = false;
303 break;
307 if ($inactivateFlag) {
308 // The sql reminder was not confirmed, so inactivate it
309 $sql = "UPDATE `patient_reminders` SET `active` = '0', `reason_inactivated` = 'auto', " .
310 "`date_inactivated` = NOW() WHERE `id`=?";
311 sqlStatementCdrEngine($sql, array($row['id']));
312 $logging['number_inactivated_reminders']++;
316 // For logging purposes only:
317 // Collect number of active and unsent reminders
318 $logging['total_post_active_reminders'] = count(fetch_reminders($patient_id_complete));
319 $logging['total_post_unsent_reminders'] = count(fetch_reminders($patient_id_complete, 'unsent'));
321 return $logging;
326 * Function to send reminders.
328 * Function that sends reminders and returns an array with a specific data structure.
329 * <pre>The data structure of the return array includes the following elements
330 * 'total_pre_unsent_reminders' - Number of reminders before processing.
331 * 'total_post_unsent_reminders' - Number of reminders after processing.
332 * 'number_success_emails' - Number of successfully sent email reminders.
333 * 'number_failed_emails' - Number of failed sent email reminders.
334 * 'number_success_calls' - Number of successfully call reminders.
335 * 'number_failed_calls' - Number of failed call reminders.
336 * </pre>
338 * @return array see above for data structure of returned array
340 function send_reminders()
343 $logging = array();
345 // Collect active reminders that have not yet been sent.
346 $active_unsent_reminders = fetch_reminders('', 'unsent');
347 $logging['total_pre_unsent_reminders'] = count($active_unsent_reminders);
349 // Send the unsent reminders
350 $logging['number_success_emails'] = 0;
351 $logging['number_failed_emails'] = 0;
352 $logging['number_success_calls'] = 0;
353 $logging['number_failed_calls'] = 0;
354 foreach ($active_unsent_reminders as $reminder) {
355 // Collect patient information that reminder is going to.
356 $sql = "SELECT `fname`, `lname`, `email`, `phone_home`, `hipaa_voice`, `hipaa_allowemail` from `patient_data` where `pid`=?";
357 $result = sqlQueryCdrEngine($sql, array($reminder['pid']));
358 $patientfname = $result['fname'];
359 $patientlname = $result['lname'];
360 $patientemail = $result['email'];
361 $patientphone = $result['phone_home'];
362 $hipaa_voice = $result['hipaa_voice'];
363 $hipaa_allowemail = $result['hipaa_allowemail'];
365 // Email to patient if Allow Email and set reminder sent flag.
366 if ($hipaa_allowemail == "YES") {
367 $mail = new MyMailer();
368 $sender_name = $GLOBALS['patient_reminder_sender_name'];
369 $email_address = $GLOBALS['patient_reminder_sender_email'];
370 $mail->FromName = $sender_name; // required
371 $mail->Sender = $email_address; // required
372 $mail->From = $email_address; // required
373 $mail->AddAddress($patientemail, $patientfname . ", " . $patientlname); // required
374 $mail->AddReplyTo($email_address, $sender_name); // required
375 $category_title = generate_display_field(array('data_type' => '1','list_id' => 'rule_action_category'), $reminder['category']);
376 $item_title = generate_display_field(array('data_type' => '1','list_id' => 'rule_action'), $reminder['item']);
377 $mail->Body = "Dear " . $patientfname . ", This is a message from your clinic to remind you of your " . $category_title . ": " . $item_title;
378 $mail->Subject = "Clinic Reminder";
379 if ($mail->Send()) {
380 // deal with and keep track of this successful email
381 sqlStatementCdrEngine("UPDATE `patient_reminders` SET `email_status`='1', `date_sent`=NOW() WHERE id=?", array($reminder['id']));
382 $logging['number_success_emails']++;
383 } else {
384 // deal with and keep track of this unsuccesful email
385 $logging['number_failed_emails']++;
389 // Call to patient if Allow Voice Message and set reminder sent flag.
390 if ($hipaa_voice == "YES") {
391 /******************************************************************************
392 * // Maviq does not work, is not currently supported, and seems to break on windows servers, so this
393 * // feature has been commented out for now.
394 * // Automated VOIP service provided by Maviq. Please visit http://signup.maviq.com for more information.
395 * $siteId = $GLOBALS['phone_gateway_username'];
396 * $cryptoGen = new CryptoGen();
397 * $token = $cryptoGen->decryptStandard($GLOBALS['phone_gateway_password']);
398 * $endpoint = $GLOBALS['phone_gateway_url'];
399 * $client = new MaviqClient($siteId, $token, $endpoint);
400 * //Set up params.
401 * $data = array(
402 * "firstName" => $patientfname,
403 * "lastName" => $patientlname,
404 * "phone" => $patientphone,
405 * //"apptDate" => "$scheduled_date[1]/$scheduled_date[2]/$scheduled_date[0]",
406 * "timeRange" => "10-18",
407 * "type" => "reminder",
408 * "timeZone" => date('P'),
409 * "greeting" => str_replace("[[sender]]", $sender_name, str_replace("[[patient_name]]", $patientfname, $myrow['reminder_content']))
410 * );
412 * // Make the call.
413 * $response = $client->sendRequest("appointment", "POST", $data);
415 * if ($response->IsError) {
416 * // deal with and keep track of this unsuccessful call
417 * $logging['number_failed_calls']++;
419 * else {
420 * // deal with and keep track of this succesful call
421 * sqlStatementCdrEngine("UPDATE `patient_reminders` SET `voice_status`='1', `date_sent`=NOW() WHERE id=?", array($reminder['id']) );
422 * $logging['number_success_calls']++;
424 *******************************************************************************/
428 // For logging purposes only:
429 // Collect active reminders that have not yet been sent.
430 $logging['total_post_unsent_reminders'] = count(fetch_reminders('', 'unsent'));
432 return $logging;
436 * Function to fetch reminders.
438 * @param integer/array $patient_id pid(s) of patient(s).
439 * @param string $type Can choose unsent ('unsent') vs all active (BLANK) reminders
440 * @param string $due_status due status of reminders (soon_due,due,past_due). If blank, then will return all.
441 * @param string $select Select component of select statement. If blank, then will return all columns.
442 * @return array Returns an array of reminders.
444 function fetch_reminders($patient_id = '', $type = '', $due_status = '', $select = '*')
447 $arraySqlBind = array();
449 if (!empty($patient_id)) {
450 // check the specified pid(s)
451 $where = "`pid` IN (?) AND ";
452 $arraySqlBind[] = $patient_id;
455 if (!empty($due_status)) {
456 $where .= "`due_status`=? AND ";
457 $arraySqlBind[] = $due_status;
460 if (empty($type)) {
461 $where .= "`active`='1'";
462 } else { // $type == 'unsent'
463 $where .= "`active`='1' AND `date_sent` IS NULL";
466 $order = "`due_status`, `date_created`";
468 $sql = "SELECT " . $select . " FROM `patient_reminders` WHERE " .
469 $where . " ORDER BY " . $order;
470 $rez = sqlStatementCdrEngine($sql, $arraySqlBind);
472 $returnval = array();
473 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
474 $returnval[$iter] = $row;
477 return $returnval;