More recurring appointment fixes
[openemr.git] / library / appointments.inc.php
blobd72cb1a0ffd779b36cf5c51afdd5bfb01f313f75
1 <?php
3 // Copyright (C) 2011 Ken Chapple
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // Holds library functions (and hashes) used by the appointment reporting module
14 ////////////
15 require_once(dirname(__FILE__)."/encounter_events.inc.php");
16 require_once(dirname(__FILE__)."/../interface/main/calendar/modules/PostCalendar/pnincludes/Date/Calc.php");
17 ////////////
20 $COMPARE_FUNCTION_HASH = array(
21 'doctor' => 'compareAppointmentsByDoctorName',
22 'patient' => 'compareAppointmentsByPatientName',
23 'pubpid' => 'compareAppointmentsByPatientId',
24 'date' => 'compareAppointmentsByDate',
25 'time' => 'compareAppointmentsByTime',
26 'type' => 'compareAppointmentsByType',
27 'comment' => 'compareAppointmentsByComment',
28 'status' => 'compareAppointmentsByStatus',
29 'completed' => 'compareAppointmentsByCompletedDrugScreen',
30 'trackerstatus' => 'compareAppointmentsByTrackerStatus'
33 $ORDERHASH = array(
34 'doctor' => array( 'doctor', 'date', 'time' ),
35 'patient' => array( 'patient', 'date', 'time' ),
36 'pubpid' => array( 'pubpid', 'date', 'time' ),
37 'date' => array( 'date', 'time', 'type', 'patient' ),
38 'time' => array( 'time', 'date', 'patient' ),
39 'type' => array( 'type', 'date', 'time', 'patient' ),
40 'comment' => array( 'comment', 'date', 'time', 'patient' ),
41 'status' => array( 'status', 'date', 'time', 'patient' ),
42 'completed' => array( 'completed', 'date', 'time', 'patient' ),
43 'trackerstatus' => array( 'trackerstatus', 'date', 'time', 'patient' ),
46 // Returns the next appointment.
47 // (typically is used to collect the next appointment for a patient, although the patient id is not required)
48 // will return as an arrayed structure for the elements of the next appointment
49 // (note it returns data in the first array element of the array (so same code can support functions in future that return multiple elements)
50 function collect_next_appointment($from_date,$patient_id=null) {
51 $events = array();
52 $events = fetchAppointments($from_date, null, $patient_id, null, null, null, null, null, null, false, true);
53 $events = sortAppointments($events);
54 $return_event = array();
55 $return_event[0] = $events[0];
56 return $return_event;
59 function fetchEvents( $from_date, $to_date, $where_param = null, $orderby_param = null, $tracker_board = false, $flagPSM = false )
61 //////
62 if($flagPSM) { // Patient Summary Mode
64 $where =
65 "((e.pc_endDate >= '$from_date' AND e.pc_recurrtype > '0') OR " .
66 "(e.pc_eventDate >= '$from_date'))";
68 } else {
69 //////
70 $where =
71 "((e.pc_endDate >= '$from_date' AND e.pc_eventDate <= '$to_date' AND e.pc_recurrtype > '0') OR " .
72 "(e.pc_eventDate >= '$from_date' AND e.pc_eventDate <= '$to_date'))";
76 if ( $where_param ) $where .= $where_param;
78 $order_by = "e.pc_eventDate, e.pc_startTime";
79 if ( $orderby_param ) {
80 $order_by = $orderby_param;
83 // Tracker Board specific stuff
84 $tracker_fields = '';
85 $tracker_joins = '';
86 if ($tracker_board) {
87 $tracker_fields = "e.pc_room, e.pc_pid, t.id, t.date, t.apptdate, t.appttime, t.eid, t.pid, t.original_user, t.encounter, t.lastseq, t.random_drug_test, t.drug_screen_completed, " .
88 "q.pt_tracker_id, q.start_datetime, q.room, q.status, q.seq, q.user, " .
89 "s.toggle_setting_1, s.toggle_setting_2, s.option_id, " ;
90 $tracker_joins = "LEFT OUTER JOIN patient_tracker AS t ON t.pid = e.pc_pid AND t.apptdate = e.pc_eventDate AND t.appttime = e.pc_starttime AND t.eid = e.pc_eid " .
91 "LEFT OUTER JOIN patient_tracker_element AS q ON q.pt_tracker_id = t.id AND q.seq = t.lastseq " .
92 "LEFT OUTER JOIN list_options AS s ON s.list_id = 'apptstat' AND s.option_id = q.status " ;
95 $query = "SELECT " .
96 "e.pc_eventDate, e.pc_endDate, e.pc_startTime, e.pc_endTime, e.pc_duration, e.pc_recurrtype, e.pc_recurrspec, e.pc_recurrfreq, e.pc_catid, e.pc_eid, " .
97 "e.pc_title, e.pc_hometext, e.pc_apptstatus, " .
98 "p.fname, p.mname, p.lname, p.pid, p.pubpid, p.phone_home, p.phone_cell, " .
99 "u.fname AS ufname, u.mname AS umname, u.lname AS ulname, u.id AS uprovider_id, " .
100 "$tracker_fields" .
101 "c.pc_catname, c.pc_catid " .
102 "FROM openemr_postcalendar_events AS e " .
103 "$tracker_joins" .
104 "LEFT OUTER JOIN patient_data AS p ON p.pid = e.pc_pid " .
105 "LEFT OUTER JOIN users AS u ON u.id = e.pc_aid " .
106 "LEFT OUTER JOIN openemr_postcalendar_categories AS c ON c.pc_catid = e.pc_catid " .
107 "WHERE $where " .
108 "ORDER BY $order_by";
111 ///////////////////////////////////////////////////////////////////////
112 // The following code is from the calculateEvents function in the
113 // PostCalendar Module modified and inserted here by epsdky
115 $events2 = array();
116 $res = sqlStatement($query);
117 ////////
118 if($flagPSM) { // Patient Summary Mode
119 global $resNotNull;
120 $resNotNull = (isset($res) && $res != null);
123 while ($event = sqlFetchArray($res)) {
124 ///////
125 if($flagPSM) $stopDate = $event['pc_endDate'];
126 else $stopDate = ($event['pc_endDate'] <= $to_date) ? $event['pc_endDate'] : $to_date;
127 ///////
128 switch($event['pc_recurrtype']) {
130 case '0' :
132 $events2[] = $event;
134 break;
135 //////
136 case '1' :
138 $event_recurrspec = @unserialize($event['pc_recurrspec']);
140 $rfreq = $event_recurrspec['event_repeat_freq'];
141 $rtype = $event_recurrspec['event_repeat_freq_type'];
142 $exdate = $event_recurrspec['exdate'];
144 list($ny,$nm,$nd) = explode('-',$event['pc_eventDate']);
145 // $occurance = Date_Calc::dateFormat($nd,$nm,$ny,'%Y-%m-%d');
146 $occurance = $event['pc_eventDate'];
148 while($occurance < $from_date) {
149 $occurance =& __increment($nd,$nm,$ny,$rfreq,$rtype);
150 list($ny,$nm,$nd) = explode('-',$occurance);
153 while($occurance <= $stopDate) {
155 $excluded = false;
156 if (isset($exdate)) {
157 foreach (explode(",", $exdate) as $exception) {
158 // occurrance format == yyyy-mm-dd
159 // exception format == yyyymmdd
160 if (preg_replace("/-/", "", $occurance) == $exception) {
161 $excluded = true;
166 if ($excluded == false) {
167 $event['pc_eventDate'] = $occurance;
168 $event['pc_endDate'] = '0000-00-00';
169 $events2[] = $event;
170 //////
171 if ($flagPSM) break;
172 //////
175 $occurance =& __increment($nd,$nm,$ny,$rfreq,$rtype);
176 list($ny,$nm,$nd) = explode('-',$occurance);
180 break;
182 //////
183 case '2' :
185 $event_recurrspec = @unserialize($event['pc_recurrspec']);
187 $rfreq = $event_recurrspec['event_repeat_on_freq'];
188 $rnum = $event_recurrspec['event_repeat_on_num'];
189 $rday = $event_recurrspec['event_repeat_on_day'];
190 $exdate = $event_recurrspec['exdate'];
192 list($ny,$nm,$nd) = explode('-',$event['pc_eventDate']);
194 $occuranceYm = "$ny-$nm"; // YYYY-mm
195 $from_dateYm = substr($from_date,0,7); // YYYY-mm
196 $stopDateYm = substr($stopDate,0,7); // YYYY-mm
198 // $nd will sometimes be 29, 30 or 31, and if used in mktime below, a problem
199 // with overflow will occur ('01' should be plugged in to avoid this). We need
200 // to mirror the calendar code which has this problem, so $nd has been used.
201 while($occuranceYm < $from_dateYm) {
202 $occuranceYmX = date('Y-m-d',mktime(0,0,0,$nm+$rfreq,$nd,$ny));
203 list($ny,$nm,$nd) = explode('-',$occuranceYmX);
204 $occuranceYm = "$ny-$nm";
207 while($occuranceYm <= $stopDateYm) {
209 // (YYYY-mm)-dd
210 $dnum = $rnum;
211 do {
212 $occurance = Date_Calc::NWeekdayOfMonth($dnum--,$rday,$nm,$ny,$format="%Y-%m-%d");
213 } while($occurance === -1);
215 if($occurance >= $from_date && $occurance <= $stopDate) {
217 $excluded = false;
218 if (isset($exdate)) {
219 foreach (explode(",", $exdate) as $exception) {
220 // occurrance format == yyyy-mm-dd
221 // exception format == yyyymmdd
222 if (preg_replace("/-/", "", $occurance) == $exception) {
223 $excluded = true;
228 if ($excluded == false) {
230 $event['pc_eventDate'] = $occurance;
231 $event['pc_endDate'] = '0000-00-00';
232 $events2[] = $event;
233 //////
234 if($flagPSM) break;
235 //////
241 $occuranceYmX = date('Y-m-d',mktime(0,0,0,$nm+$rfreq,$nd,$ny));
242 list($ny,$nm,$nd) = explode('-',$occuranceYmX);
243 $occuranceYm = "$ny-$nm";
247 break;
252 return $events2;
253 ////////////////////// End of code inserted by epsdky
255 //////////////////////
257 function fetchAllEvents( $from_date, $to_date, $provider_id = null, $facility_id = null )
259 $where = "";
260 if ( $provider_id ) $where .= " AND e.pc_aid = '$provider_id'";
262 $facility_filter = '';
263 if ( $facility_id ) {
264 $event_facility_filter = " AND e.pc_facility = '" . add_escape_custom($facility_id) . "'"; //escape $facility_id
265 $provider_facility_filter = " AND u.facility_id = '" . add_escape_custom($facility_id) . "'"; //escape $facility_id
266 $facility_filter = $event_facility_filter . $provider_facility_filter;
269 $where .= $facility_filter;
270 $appointments = fetchEvents( $from_date, $to_date, $where );
271 return $appointments;
274 function fetchAppointments( $from_date, $to_date, $patient_id = null, $provider_id = null, $facility_id = null, $pc_appstatus = null, $with_out_provider = null, $with_out_facility = null, $pc_catid = null, $tracker_board = false, $flagPSM = false )
276 $where = "";
277 if ( $provider_id ) $where .= " AND e.pc_aid = '$provider_id'";
278 if ( $patient_id ) {
279 $where .= " AND e.pc_pid = '$patient_id'";
280 } else {
281 $where .= " AND e.pc_pid != ''";
284 $facility_filter = '';
285 if ( $facility_id ) {
286 $event_facility_filter = " AND e.pc_facility = '" . add_escape_custom($facility_id) . "'"; // escape $facility_id
287 $provider_facility_filter = " AND u.facility_id = '" . add_escape_custom($facility_id) . "'"; // escape $facility_id
288 $facility_filter = $event_facility_filter . $provider_facility_filter;
291 $where .= $facility_filter;
293 //Appointment Status Checking
294 $filter_appstatus = '';
295 if($pc_appstatus != ''){
296 $filter_appstatus = " AND e.pc_apptstatus = '".$pc_appstatus."'";
298 $where .= $filter_appstatus;
300 if($pc_catid !=null)
302 $where .= " AND e.pc_catid=".intval($pc_catid); // using intval to escape this parameter
305 //Without Provider checking
306 $filter_woprovider = '';
307 if($with_out_provider != ''){
308 $filter_woprovider = " AND e.pc_aid = ''";
310 $where .= $filter_woprovider;
312 //Without Facility checking
313 $filter_wofacility = '';
314 if($with_out_facility != ''){
315 $filter_wofacility = " AND e.pc_facility = 0";
317 $where .= $filter_wofacility;
319 $appointments = fetchEvents( $from_date, $to_date, $where, '', $tracker_board, $flagPSM );
320 return $appointments;
323 // get the event slot size in seconds
324 function getSlotSize()
326 if ( isset( $GLOBALS['calendar_interval'] ) ) {
327 return $GLOBALS['calendar_interval'] * 60;
329 return 15 * 60;
332 function getAvailableSlots( $from_date, $to_date, $provider_id = null, $facility_id = null )
334 $appointments = fetchAllEvents( $from_date, $to_date, $provider_id, $facility_id );
335 $appointments = sortAppointments( $appointments, "date" );
336 $from_datetime = strtotime( $from_date." 00:00:00" );
337 $to_datetime = strtotime( $to_date." 23:59:59" );
338 $availableSlots = array();
339 $start_time = 0;
340 $date = 0;
341 for ( $i = 0; $i < count( $appointments ); ++$i )
343 if ( $appointments[$i]['pc_catid'] == 2 ) { // 2 == In Office
344 $start_time = $appointments[$i]['pc_startTime'];
345 $date = $appointments[$i]['pc_eventDate'];
346 $provider_id = $appointments[$i]['uprovider_id'];
347 } else if ( $appointments[$i]['pc_catid'] == 3 ) { // 3 == Out Of Office
348 continue;
349 } else {
350 $start_time = $appointments[$i]['pc_endTime'];
351 $date = $appointments[$i]['pc_eventDate'];
352 $provider_id = $appointments[$i]['uprovider_id'];
355 // find next appointment with the same provider
356 $next_appointment_date = 0;
357 $next_appointment_time = 0;
358 for ( $j = $i+1; $j < count( $appointments ); ++$j ) {
359 if ( $appointments[$j]['uprovider_id'] == $provider_id ) {
360 $next_appointment_date = $appointments[$j]['pc_eventDate'];
361 $next_appointment_time = $appointments[$j]['pc_startTime'];
362 break;
366 $same_day = ( strtotime( $next_appointment_date ) == strtotime( $date ) ) ? true : false;
368 if ( $next_appointment_time && $same_day ) {
369 // check the start time of the next appointment
371 $start_datetime = strtotime( $date." ".$start_time );
372 $next_appointment_datetime = strtotime( $next_appointment_date." ".$next_appointment_time );
373 $curr_time = $start_datetime;
374 while ( $curr_time < $next_appointment_datetime - (getSlotSize() / 2) ) {
375 //create a new appointment ever 15 minutes
376 $time = date( "H:i:s", $curr_time );
377 $available_slot = createAvailableSlot(
378 $appointments[$i]['pc_eventDate'],
379 $time,
380 $appointments[$i]['ufname'],
381 $appointments[$i]['ulname'],
382 $appointments[$i]['umname'] );
383 $availableSlots []= $available_slot;
384 $curr_time += getSlotSize(); // add a 15-minute slot
389 return $availableSlots;
392 function createAvailableSlot( $event_date, $start_time, $provider_fname, $provider_lname, $provider_mname = "", $cat_name = "Available" )
394 $newSlot = array();
395 $newSlot['ulname'] = $provider_lname;
396 $newSlot['ufname'] = $provider_fname;
397 $newSlot['umname'] = $provider_mname;
398 $newSlot['pc_eventDate'] = $event_date;
399 $newSlot['pc_startTime'] = $start_time;
400 $newSlot['pc_endTime'] = $start_time;
401 $newSlot['pc_catname'] = $cat_name;
402 return $newSlot;
405 function getCompareFunction( $code ) {
406 global $COMPARE_FUNCTION_HASH;
407 return $COMPARE_FUNCTION_HASH[$code];
410 function getComparisonOrder( $code ) {
411 global $ORDERHASH;
412 return $ORDERHASH[$code];
416 function sortAppointments( array $appointments, $orderBy = 'date' )
418 global $appointment_sort_order;
419 $appointment_sort_order = $orderBy;
420 usort( $appointments, "compareAppointments" );
421 return $appointments;
424 // cmp_function for usort
425 // The comparison function must return an integer less than, equal to,
426 // or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
427 function compareAppointments( $appointment1, $appointment2 )
429 global $appointment_sort_order;
430 $comparisonOrder = getComparisonOrder( $appointment_sort_order );
431 foreach ( $comparisonOrder as $comparison )
433 $cmp_function = getCompareFunction( $comparison );
434 $result = $cmp_function( $appointment1, $appointment2 );
435 if ( 0 != $result ) {
436 return $result;
440 return 0;
443 function compareBasic( $e1, $e2 )
445 if ( $e1 < $e2 ) {
446 return -1;
447 } else if ( $e1 > $e2 ) {
448 return 1;
451 return 0;
454 function compareAppointmentsByDate( $appointment1, $appointment2 )
456 $date1 = strtotime( $appointment1['pc_eventDate'] );
457 $date2 = strtotime( $appointment2['pc_eventDate'] );
459 return compareBasic( $date1, $date2 );
462 function compareAppointmentsByTime( $appointment1, $appointment2 )
464 $time1 = strtotime( $appointment1['pc_startTime'] );
465 $time2 = strtotime( $appointment2['pc_startTime'] );
467 return compareBasic( $time1, $time2 );
470 function compareAppointmentsByDoctorName( $appointment1, $appointment2 )
472 $name1 = $appointment1['ulname'];
473 $name2 = $appointment2['ulname'];
474 $cmp = compareBasic( $name1, $name2 );
475 if ( $cmp == 0 ) {
476 $name1 = $appointment1['ufname'];
477 $name2 = $appointment2['ufname'];
478 return compareBasic( $name1, $name2 );
481 return $cmp;
484 function compareAppointmentsByPatientName( $appointment1, $appointment2 )
486 $name1 = $appointment1['lname'];
487 $name2 = $appointment2['lname'];
488 $cmp = compareBasic( $name1, $name2 );
489 if ( $cmp == 0 ) {
490 $name1 = $appointment1['fname'];
491 $name2 = $appointment2['fname'];
492 return compareBasic( $name1, $name2 );
495 return $cmp;
498 function compareAppointmentsByType( $appointment1, $appointment2 )
500 $type1 = $appointment1['pc_catid'];
501 $type2 = $appointment2['pc_catid'];
502 return compareBasic( $type1, $type2 );
505 function compareAppointmentsByPatientId( $appointment1, $appointment2 )
507 $id1 = $appointment1['pubpid'];
508 $id2 = $appointment2['pubpid'];
509 return compareBasic( $id1, $id2 );
512 function compareAppointmentsByComment( $appointment1, $appointment2 )
514 $comment1 = $appointment1['pc_hometext'];
515 $comment2 = $appointment2['pc_hometext'];
516 return compareBasic( $comment1, $comment2 );
519 function compareAppointmentsByStatus( $appointment1, $appointment2 )
521 $status1 = $appointment1['pc_apptstatus'];
522 $status2 = $appointment2['pc_apptstatus'];
523 return compareBasic( $status1, $status2 );
526 function compareAppointmentsByTrackerStatus( $appointment1, $appointment2 )
528 $trackerstatus1 = $appointment1['status'];
529 $trackerstatus2 = $appointment2['status'];
530 return compareBasic( $trackerstatus1, $trackerstatus2 );
533 function compareAppointmentsByCompletedDrugScreen( $appointment1, $appointment2 )
535 $completed1 = $appointment1['drug_screen_completed'];
536 $completed2 = $appointment2['drug_screen_completed'];
537 return compareBasic( $completed1, $completed2 );
540 function fetchAppointmentCategories()
542 $catSQL= " SELECT pc_catid as id, pc_catname as category "
543 . " FROM openemr_postcalendar_categories WHERE pc_recurrtype=0 and pc_cattype=0 ORDER BY category";
544 return sqlStatement($catSQL);