function getShiftData($user_date_id = NULL, $user_id = NULL, $epoch = NULL, $filter = NULL, $tmp_punch_control_obj = NULL)
 {
     global $profiler;
     $profiler->startTimer('PayPeriodScheduleFactory::getShiftData()');
     if (is_numeric($user_date_id) and $user_date_id > 0) {
         $user_id = $epoch = NULL;
     }
     if ($user_date_id == '' and $user_id == '' and $epoch == '') {
         return FALSE;
     }
     //Debug::text('User Date ID: '. $user_date_id .' User ID: '. $user_id .' TimeStamp: '. TTDate::getDate('DATE+TIME', $epoch), __FILE__, __LINE__, __METHOD__, 10);
     $new_shift_trigger_time = $this->getNewDayTriggerTime();
     $plf = new PunchListFactory();
     if ($user_date_id != '') {
         $plf->getByUserDateId($user_date_id);
     } else {
         //Get punches by time stamp.
         $punch_control_id = 0;
         if (is_object($tmp_punch_control_obj)) {
             $punch_control_id = $tmp_punch_control_obj->getId();
         }
         $plf->getShiftPunchesByUserIDAndEpoch($user_id, $epoch, $punch_control_id, $this->getMaximumShiftTime());
         unset($punch_control_id);
     }
     Debug::text('Punch Rows: ' . $plf->getRecordCount() . ' UserID: ' . $user_id . ' Date: ' . TTDate::getDate('DATE+TIME', $epoch) . '(' . $epoch . ') MaximumShiftTime: ' . $this->getMaximumShiftTime(), __FILE__, __LINE__, __METHOD__, 10);
     //Debug::Arr($punches, ' Punches: ', __FILE__, __LINE__, __METHOD__, 10);
     if ($plf->getRecordCount() > 0) {
         $shift = 0;
         $i = 0;
         $nearest_shift_id = 0;
         $nearest_punch_difference = FALSE;
         $prev_punch_obj = FALSE;
         foreach ($plf as $p_obj) {
             //Debug::text('Shift: '. $shift .' Punch ID: '. $p_obj->getID() .' Punch Control ID: '. $p_obj->getPunchControlID() .' TimeStamp: '. TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp() ), __FILE__, __LINE__, __METHOD__, 10);
             //If we're editing a punch, we need to use the object passed to this function instead of the one
             //from the database.
             if ($epoch == NULL) {
                 //If user_date_id is passed without epoch, set epoch to the first punch we find.
                 $epoch = $p_obj->getTimeStamp();
             }
             if (isset($prev_punch_arr) and $p_obj->getTimeStamp() > $prev_punch_arr['time_stamp']) {
                 $shift_data[$shift]['previous_punch_key'] = $i - 1;
                 if ($shift_data[$shift]['previous_punch_key'] < 0) {
                     $shift_data[$shift]['previous_punch_key'] = NULL;
                 }
             }
             //Determine if a non-saved PunchControl object was passed, and if so, match the IDs to use that instead.
             if (is_object($tmp_punch_control_obj) and $p_obj->getPunchControlID() == $tmp_punch_control_obj->getId()) {
                 Debug::text('Passed non-saved punch control object that matches, using that instead... Using ID: ' . (int) $tmp_punch_control_obj->getId(), __FILE__, __LINE__, __METHOD__, 10);
                 $punch_control_obj = $tmp_punch_control_obj;
             } else {
                 $punch_control_obj = $p_obj->getPunchControlObject();
             }
             //Can't use PunchControl object total_time because the record may not be saved yet when editing
             //an already existing punch.
             //When editing, simply pass the existing PunchControl object to this function so we can
             //use it instead of the one in the database perhaps?
             $total_time = $punch_control_obj->getTotalTime();
             /*
             				//We can't skip records with total_time == 0, because then when deleting one of the two
             				//punches in a pair, the remaining punch is ignored and causing punches to jump around between days in some cases.
             				if ( $total_time == 0 ) {
             					Debug::text('Total time is 0, skipping this punch control object...', __FILE__, __LINE__, __METHOD__, 10);
             					//continue;
             				}
             */
             if ($i > 0 and isset($shift_data[$shift]['last_out']) and ($p_obj->getStatus() == 10 or $p_obj->getStatus() == $prev_punch_arr['status_id'])) {
                 Debug::text('Checking for new shift...', __FILE__, __LINE__, __METHOD__, 10);
                 if ($p_obj->getTimeStamp() - $shift_data[$shift]['last_out']['time_stamp'] > $new_shift_trigger_time) {
                     $shift++;
                 }
             }
             if (!isset($shift_data[$shift]['total_time'])) {
                 $shift_data[$shift]['total_time'] = 0;
             }
             $punch_day_epoch = TTDate::getBeginDayEpoch($p_obj->getTimeStamp());
             if (!isset($shift_data[$shift]['total_time_per_day'][$punch_day_epoch])) {
                 $shift_data[$shift]['total_time_per_day'][$punch_day_epoch] = 0;
             }
             //Determine which shift is closest to the given epoch.
             $punch_difference_from_epoch = abs($epoch - $p_obj->getTimeStamp());
             if ($nearest_punch_difference === FALSE or $punch_difference_from_epoch < $nearest_punch_difference) {
                 Debug::text('Nearest Shift Determined to be: ' . $shift . ' Nearest Punch Diff: ' . (int) $nearest_punch_difference . ' Punch Diff: ' . $punch_difference_from_epoch, __FILE__, __LINE__, __METHOD__, 10);
                 $nearest_shift_id = $shift;
                 $nearest_punch_difference = $punch_difference_from_epoch;
             }
             $punch_arr = array('id' => $p_obj->getId(), 'punch_control_id' => $p_obj->getPunchControlId(), 'user_date_id' => $punch_control_obj->getUserDateID(), 'time_stamp' => $p_obj->getTimeStamp(), 'status_id' => $p_obj->getStatus(), 'type_id' => $p_obj->getType());
             $shift_data[$shift]['punches'][] = $punch_arr;
             $shift_data[$shift]['punch_control_ids'][] = $p_obj->getPunchControlId();
             if ($punch_control_obj->getUserDateID() != FALSE) {
                 $shift_data[$shift]['user_date_ids'][] = $punch_control_obj->getUserDateID();
             }
             $shift_data[$shift]['span_midnight'] = FALSE;
             if (!isset($shift_data[$shift]['first_in']) and $p_obj->getStatus() == 10) {
                 //Debug::text('First In -- Punch ID: '. $p_obj->getID() .' Punch Control ID: '. $p_obj->getPunchControlID() .' TimeStamp: '. TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp() ), __FILE__, __LINE__, __METHOD__, 10);
                 $shift_data[$shift]['first_in'] = $punch_arr;
             } elseif ($p_obj->getStatus() == 20) {
                 //Debug::text('Last Out -- Punch ID: '. $p_obj->getID() .' Punch Control ID: '. $p_obj->getPunchControlID() .' TimeStamp: '. TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp() ), __FILE__, __LINE__, __METHOD__, 10);
                 $shift_data[$shift]['last_out'] = $punch_arr;
                 //Debug::text('Total Time: '. $total_time, __FILE__, __LINE__, __METHOD__, 10);
                 $shift_data[$shift]['total_time'] += $total_time;
                 //Check to see if the previous punch was on a different day then the current punch.
                 if (isset($prev_punch_arr) and is_array($prev_punch_arr) and ($p_obj->getStatus() == 20 and $prev_punch_arr['status_id'] != 20) and TTDate::doesRangeSpanMidnight($prev_punch_arr['time_stamp'], $p_obj->getTimeStamp()) == TRUE) {
                     Debug::text('Punch pair DOES span midnight', __FILE__, __LINE__, __METHOD__, 10);
                     $shift_data[$shift]['span_midnight'] = TRUE;
                     $total_time_for_each_day_arr = TTDate::calculateTimeOnEachDayBetweenRange($prev_punch_arr['time_stamp'], $p_obj->getTimeStamp());
                     if (is_array($total_time_for_each_day_arr)) {
                         foreach ($total_time_for_each_day_arr as $begin_day_epoch => $day_total_time) {
                             if (!isset($shift_data[$shift]['total_time_per_day'][$begin_day_epoch])) {
                                 $shift_data[$shift]['total_time_per_day'][$begin_day_epoch] = 0;
                             }
                             $shift_data[$shift]['total_time_per_day'][$begin_day_epoch] += $day_total_time;
                         }
                     }
                     unset($total_time_for_each_day_arr, $begin_day_epoch, $day_total_time, $prev_day_total_time);
                 } else {
                     $shift_data[$shift]['total_time_per_day'][$punch_day_epoch] += $total_time;
                 }
             }
             $prev_punch_arr = $punch_arr;
             $i++;
         }
         //Debug::Arr($shift_data, 'aShift Data:', __FILE__, __LINE__, __METHOD__, 10);
         if (isset($shift_data)) {
             //Loop through each shift to determine the day with the most time.
             foreach ($shift_data as $tmp_shift_key => $tmp_shift_data) {
                 krsort($shift_data[$tmp_shift_key]['total_time_per_day']);
                 //Sort by day first
                 arsort($shift_data[$tmp_shift_key]['total_time_per_day']);
                 //Sort by total time per day.
                 reset($shift_data[$tmp_shift_key]['total_time_per_day']);
                 $shift_data[$tmp_shift_key]['day_with_most_time'] = key($shift_data[$tmp_shift_key]['total_time_per_day']);
                 $shift_data[$tmp_shift_key]['punch_control_ids'] = array_unique($shift_data[$tmp_shift_key]['punch_control_ids']);
                 if (isset($shift_data[$tmp_shift_key]['user_date_ids'])) {
                     $shift_data[$tmp_shift_key]['user_date_ids'] = array_unique($shift_data[$tmp_shift_key]['user_date_ids']);
                 }
             }
             unset($tmp_shift_key, $tmp_shift_data);
             if ($filter == 'first_shift') {
                 //Only return first shift.
                 $shift_data = $shift_data[0];
             } elseif ($filter == 'last_shift') {
                 //Only return last shift.
                 $shift_data = $shift_data[$shift];
             } elseif ($filter == 'nearest_shift') {
                 $shift_data = $shift_data[$nearest_shift_id];
                 //Check to make sure the nearest shift is within the new shift trigger time of EPOCH.
                 if (isset($shift_data['first_in']['time_stamp'])) {
                     $first_in = $shift_data['first_in']['time_stamp'];
                 } elseif (isset($shift_data['last_out']['time_stamp'])) {
                     $first_in = $shift_data['last_out']['time_stamp'];
                 }
                 if (isset($shift_data['last_out']['time_stamp'])) {
                     $last_out = $shift_data['last_out']['time_stamp'];
                 } elseif (isset($shift_data['first_in']['time_stamp'])) {
                     $last_out = $shift_data['first_in']['time_stamp'];
                 }
                 if (TTDate::isTimeOverLap($epoch, $epoch, $first_in - $new_shift_trigger_time, $last_out + $new_shift_trigger_time) == FALSE) {
                     Debug::Text('Nearest shift is outside the new shift trigger time... Epoch: ' . $epoch . ' First In: ' . $first_in . ' Last Out: ' . $last_out . ' New Shift Trigger: ' . $new_shift_trigger_time, __FILE__, __LINE__, __METHOD__, 10);
                     return FALSE;
                 }
                 unset($first_in, $last_out);
             }
             $profiler->stopTimer('PayPeriodScheduleFactory::getShiftData()');
             //Debug::Arr($shift_data, 'bShift Data:', __FILE__, __LINE__, __METHOD__, 10);
             return $shift_data;
         }
     }
     $profiler->stopTimer('PayPeriodScheduleFactory::getShiftData()');
     return FALSE;
 }
Пример #2
0
 function preSave()
 {
     if ($this->isNew()) {
         //Debug::text(' Setting Original TimeStamp: '. $this->getTimeStamp(), __FILE__, __LINE__, __METHOD__,10);
         $this->setOriginalTimeStamp($this->getTimeStamp());
     }
     if ($this->getDeleted() == FALSE) {
         if ($this->getTransfer() == TRUE and $this->getEnableAutoTransfer() == TRUE) {
             Debug::text(' Transfer is Enabled, automatic punch out of last punch pair: ', __FILE__, __LINE__, __METHOD__, 10);
             //Check to make sure there is an open punch pair.
             $plf = new PunchListFactory();
             $plf->getPreviousPunchByUserIdAndEpoch($this->getUser(), $this->getTimeStamp());
             if ($plf->getRecordCount() > 0) {
                 $p_obj = $plf->getCurrent();
                 Debug::text(' Found Last Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                 if ($p_obj->getStatus() == 10) {
                     Debug::text(' Last Punch was in. Auto Punch Out now: ', __FILE__, __LINE__, __METHOD__, 10);
                     //Make sure the current punch status is IN
                     $this->setStatus(10);
                     //In
                     $this->setType(10);
                     //Normal (can't transfer in/out of lunches?)
                     $pf = new PunchFactory();
                     $pf->setUser($this->getUser());
                     $pf->setEnableAutoTransfer(FALSE);
                     $pf->setPunchControlID($p_obj->getPunchControlID());
                     $pf->setTransfer(TRUE);
                     $pf->setType($p_obj->getNextType());
                     $pf->setStatus(20);
                     //Out
                     $pf->setTimeStamp($this->getTimeStamp(), FALSE);
                     //Disable rounding.
                     $pf->setActualTimeStamp($this->getTimeStamp());
                     $pf->setOriginalTimeStamp($this->getTimeStamp());
                     if ($pf->isValid()) {
                         if ($pf->Save(FALSE) == TRUE) {
                             $p_obj->getPunchControlObject()->setEnableCalcTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcSystemTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcUserDateTotal(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcException(TRUE);
                             $p_obj->getPunchControlObject()->setEnablePreMatureException(TRUE);
                             if ($p_obj->getPunchControlObject()->isValid()) {
                                 $p_obj->getPunchControlObject()->Save();
                             } else {
                                 Debug::text(' aError saving auto out punch...', __FILE__, __LINE__, __METHOD__, 10);
                             }
                         } else {
                             Debug::text(' bError saving auto out punch...', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     } else {
                         Debug::text(' cError saving auto out punch...', __FILE__, __LINE__, __METHOD__, 10);
                     }
                 } else {
                     Debug::text(' Last Punch was out. No Auto Punch ', __FILE__, __LINE__, __METHOD__, 10);
                 }
             }
             unset($plf, $p_obj, $pf);
         }
         //Split punch at midnight.
         //This has to be an Out punch, and the previous punch has to be an in punch in order for the split to occur.
         //Check to make sure there is an open punch pair.
         //Make sure this punch isn't right at midnight either, as no point in splitting a punch at that time.
         //FIXME: What happens if a supervisor edits a 11:30PM punch and makes it 5:00AM the next day?
         //		We can't split punches when editing, because we have to split punch_control_ids prior to saving etc...
         if ($this->isNew() == TRUE and $this->getStatus() == 20 and $this->getEnableSplitAtMidnight() == TRUE and $this->getTimeStamp() != TTDate::getBeginDayEpoch($this->getTimeStamp()) and (is_object($this->getPunchControlObject()) and is_object($this->getPunchControlObject()->getPayPeriodScheduleObject()) and $this->getPunchControlObject()->getPayPeriodScheduleObject()->getShiftAssignedDay() == 40)) {
             $plf = new PunchListFactory();
             $plf->getPreviousPunchByUserIdAndEpoch($this->getUser(), $this->getTimeStamp());
             if ($plf->getRecordCount() > 0) {
                 $p_obj = $plf->getCurrent();
                 Debug::text(' Found Last Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                 if ($p_obj->getStatus() == 10 and TTDate::doesRangeSpanMidnight($this->getTimeStamp(), $p_obj->getTimeStamp())) {
                     Debug::text(' Last Punch was in and this is an out punch that spans midnight. Split Punch at midnight now: ', __FILE__, __LINE__, __METHOD__, 10);
                     //FIXME: This will fail if a shift spans multiple days!
                     //Make sure the current punch status is OUT
                     //But we can split LUNCH/Break punches, because someone could punch in at 8PM, then out for lunch at 1:00AM, this would need to be split.
                     $this->setStatus(20);
                     //Out
                     //Reduce the out punch by 60 seconds, and increase the current punch by 60seconds so no time is lost.
                     $this->setTimeStamp($this->getTimeStamp() + 60);
                     //FIXME: May need to use ActualTimeStamp here so we aren't double rounding.
                     //Get new punch control ID for the midnight punch and this one.
                     $new_punch_control_id = $this->getPunchControlObject()->getNextInsertId();
                     $this->setPunchControlID($new_punch_control_id);
                     Debug::text(' Split Punch: Punching out just before midnight yesterday...', __FILE__, __LINE__, __METHOD__, 10);
                     //
                     //Punch out just before midnight
                     //
                     $pf = new PunchFactory();
                     $pf->setUser($this->getUser());
                     $pf->setEnableSplitAtMidnight(FALSE);
                     $pf->setTransfer(FALSE);
                     $pf->setEnableAutoTransfer(FALSE);
                     $pf->setType(10);
                     //Normal
                     $pf->setStatus(20);
                     //Out
                     $before_midnight_timestamp = TTDate::getBeginDayEpoch($this->getTimeStamp()) - 60;
                     $pf->setTimeStamp($before_midnight_timestamp, FALSE);
                     //Disable rounding.
                     $pf->setActualTimeStamp($before_midnight_timestamp);
                     $pf->setOriginalTimeStamp($before_midnight_timestamp);
                     $pf->setPunchControlID($p_obj->getPunchControlID());
                     if ($pf->isValid()) {
                         if ($pf->Save(FALSE) == TRUE) {
                             $p_obj->getPunchControlObject()->setEnableCalcTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcSystemTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcUserDateTotal(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcException(TRUE);
                             $p_obj->getPunchControlObject()->setEnablePreMatureException(TRUE);
                             $p_obj->getPunchControlObject()->Save();
                         }
                     }
                     unset($pf, $p_obj, $before_midnight_timestamp);
                     Debug::text(' Split Punch: Punching int at midnight today...', __FILE__, __LINE__, __METHOD__, 10);
                     //
                     //Punch in again right at midnight.
                     //
                     $pf = new PunchFactory();
                     $pf->setUser($this->getUser());
                     $pf->setEnableSplitAtMidnight(FALSE);
                     $pf->setTransfer(FALSE);
                     $pf->setEnableAutoTransfer(FALSE);
                     $pf->setType(10);
                     //Normal
                     $pf->setStatus(10);
                     //In
                     $at_midnight_timestamp = TTDate::getBeginDayEpoch($this->getTimeStamp());
                     $pf->setTimeStamp($at_midnight_timestamp, FALSE);
                     //Disable rounding.
                     $pf->setActualTimeStamp($at_midnight_timestamp);
                     $pf->setOriginalTimeStamp($at_midnight_timestamp);
                     $pf->setPunchControlID($new_punch_control_id);
                     if ($pf->isValid()) {
                         if ($pf->Save(FALSE) == TRUE) {
                             $pcf = new PunchControlFactory();
                             $pcf->setId($pf->getPunchControlID());
                             $pcf->setPunchObject($pf);
                             $pcf->setBranch($this->getPunchControlObject()->getBranch());
                             $pcf->setDepartment($this->getPunchControlObject()->getDepartment());
                             $pcf->setJob($this->getPunchControlObject()->getJob());
                             $pcf->setJobItem($this->getPunchControlObject()->getJobItem());
                             $pcf->setOtherID1($this->getPunchControlObject()->getOtherID1());
                             $pcf->setOtherID2($this->getPunchControlObject()->getOtherID2());
                             $pcf->setOtherID3($this->getPunchControlObject()->getOtherID3());
                             $pcf->setOtherID4($this->getPunchControlObject()->getOtherID4());
                             $pcf->setOtherID5($this->getPunchControlObject()->getOtherID5());
                             $pcf->setEnableStrictJobValidation(TRUE);
                             $pcf->setEnableCalcUserDateID(TRUE);
                             $pcf->setEnableCalcTotalTime(TRUE);
                             $pcf->setEnableCalcSystemTotalTime(TRUE);
                             $pcf->setEnableCalcWeeklySystemTotalTime(TRUE);
                             $pcf->setEnableCalcUserDateTotal(TRUE);
                             $pcf->setEnableCalcException(TRUE);
                             if ($pcf->isValid() == TRUE) {
                                 $pcf->Save(TRUE, TRUE);
                                 //Force isNEW() lookup.
                             }
                         }
                     }
                     unset($pf, $at_midnight_timestamp, $new_punch_control_id);
                 } else {
                     Debug::text(' Last Punch was out. No Auto Punch ', __FILE__, __LINE__, __METHOD__, 10);
                 }
             }
         }
     }
     return TRUE;
 }
 function getShiftData($user_date_id = NULL, $user_id = NULL, $epoch = NULL, $filter = NULL, $tmp_punch_control_obj = NULL, $maximum_shift_time = NULL, $new_shift_trigger_time = NULL, $plf = NULL)
 {
     global $profiler;
     $profiler->startTimer('PayPeriodScheduleFactory::getShiftData()');
     if (is_numeric($user_date_id) and $user_date_id > 0) {
         $user_id = $epoch = NULL;
     }
     if ($user_date_id == '' and $user_id == '' and $epoch == '') {
         return FALSE;
     }
     if ($maximum_shift_time === NULL) {
         $maximum_shift_time = $this->getMaximumShiftTime();
     }
     //Debug::text('User Date ID: '. $user_date_id .' User ID: '. $user_id .' TimeStamp: '. TTDate::getDate('DATE+TIME', $epoch), __FILE__, __LINE__, __METHOD__, 10);
     if ($new_shift_trigger_time === NULL) {
         $new_shift_trigger_time = $this->getNewDayTriggerTime();
     }
     if (!is_object($plf)) {
         $plf = TTnew('PunchListFactory');
         if ($user_date_id != '') {
             $plf->getByUserDateId($user_date_id);
         } else {
             //Get punches by time stamp.
             $punch_control_id = 0;
             if (is_object($tmp_punch_control_obj)) {
                 $punch_control_id = $tmp_punch_control_obj->getId();
             }
             //We need to double the maximum shift time when searching for punches.
             //Assuming a maximum punch time of 14hrs:
             // In: 10:00AM Out: 2:00PM
             // In: 6:00PM Out: 6:00AM (next day)
             // The above scenario when adding the last 6:00AM punch on the next day will only look back 14hrs and not find the first
             // punch pair, therefore allowing more than 14hrs on the same day.
             // So we need to extend the maximum shift time just when searching for punches and let getShiftData() sort out the proper maximum shift time itself.
             $plf->getShiftPunchesByUserIDAndEpoch($user_id, $epoch, $punch_control_id, $maximum_shift_time * 2);
             unset($punch_control_id);
         }
     }
     Debug::text('Punch Rows: ' . $plf->getRecordCount() . ' UserID: ' . $user_id . ' Date: ' . TTDate::getDate('DATE+TIME', $epoch) . '(' . $epoch . ') MaximumShiftTime: ' . $maximum_shift_time . ' Filter: ' . $filter, __FILE__, __LINE__, __METHOD__, 10);
     if ($plf->getRecordCount() > 0) {
         $shift = 0;
         $i = 0;
         $nearest_shift_id = 0;
         $nearest_punch_difference = FALSE;
         $prev_punch_obj = FALSE;
         foreach ($plf as $p_obj) {
             //Debug::text('Shift: '. $shift .' Punch ID: '. $p_obj->getID() .' Punch Control ID: '. $p_obj->getPunchControlID() .' TimeStamp: '. TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp() ), __FILE__, __LINE__, __METHOD__, 10);
             //If we're editing a punch, we need to use the object passed to this function instead of the one
             //from the database.
             if ($epoch == NULL) {
                 //If user_date_id is passed without epoch, set epoch to the first punch we find.
                 $epoch = $p_obj->getTimeStamp();
             }
             if (isset($prev_punch_arr) and $p_obj->getTimeStamp() > $prev_punch_arr['time_stamp']) {
                 $shift_data[$shift]['previous_punch_key'] = $i - 1;
                 if ($shift_data[$shift]['previous_punch_key'] < 0) {
                     $shift_data[$shift]['previous_punch_key'] = NULL;
                 }
             }
             //Determine if a non-saved PunchControl object was passed, and if so, match the IDs to use that instead.
             if (is_object($tmp_punch_control_obj) and $p_obj->getPunchControlID() == $tmp_punch_control_obj->getId()) {
                 Debug::text('Passed non-saved punch control object that matches, using that instead... Using ID: ' . (int) $tmp_punch_control_obj->getId(), __FILE__, __LINE__, __METHOD__, 10);
                 $punch_control_obj = $tmp_punch_control_obj;
             } else {
                 $punch_control_obj = $p_obj->getPunchControlObject();
             }
             //Can't use PunchControl object total_time because the record may not be saved yet when editing
             //an already existing punch.
             //When editing, simply pass the existing PunchControl object to this function so we can
             //use it instead of the one in the database perhaps?
             $total_time = $punch_control_obj->getTotalTime();
             //We can't skip records with total_time == 0, because then when deleting one of the two
             //punches in a pair, the remaining punch is ignored and causing punches to jump around between days in some cases.
             if ($i > 0 and isset($shift_data[$shift]['last_out']) and ($p_obj->getStatus() == 10 or $p_obj->getStatus() == $prev_punch_arr['status_id'])) {
                 Debug::text('Checking for new shift... This Control ID: ' . $p_obj->getPunchControlID() . ' Last Out Control ID: ' . $shift_data[$shift]['last_out']['punch_control_id'] . ' Last Out Time: ' . TTDate::getDate('DATE+TIME', $shift_data[$shift]['last_out']['time_stamp']), __FILE__, __LINE__, __METHOD__, 10);
                 //Assume that if two punches are assigned to the same punch_control_id are the same shift, even if the time between
                 //them exceeds the new_shift_trigger_time. This helps fix the bug where you could add a In punch then add a Out
                 //punch BEFORE the In punch as long as it was more than the Maximum Shift Time before the In Punch.
                 //ie: Add: In Punch 10-Dec-09 @ 8:00AM, Add: Out Punch 09-Dec-09 @ 5:00PM.
                 //Basically it just helps the validation checks to determine the error.
                 //
                 //It used to be that if shifts are split at midnight, new_shift_trigger_time must be 0, so the "split" punch can occur at midnight.
                 //However we have since added a check to see if punches span midnight and trigger a new shift based on that, regardless of the new shift trigger time.
                 //As the new_shift_trigger_time of 0 also affected lunch/break automatic detection by Punch Time, since an Out punch and a In punch of any time
                 //would trigger a new shift, and it wouldn't be detected as lunch/break.
                 //
                 //What happens when the employee takes lunch/break over midnight? Lunch out at 11:30PM Lunch IN at 12:30AM
                 //  We need to split those into two lunches, or two breaks? But then that can affect those policies if they are only allowed one break.
                 // 	Or do we not split the shift at all when this occurs? Currently we don't split at all.
                 if ($p_obj->getPunchControlID() != $shift_data[$shift]['last_out']['punch_control_id'] and ($p_obj->getTimeStamp() - $shift_data[$shift]['last_out']['time_stamp'] >= $new_shift_trigger_time or $this->getShiftAssignedDay() == 40 and $p_obj->getType() == 10 and $shift_data[$shift]['last_out']['type_id'] == 10 and TTDate::doesRangeSpanMidnight($shift_data[$shift]['last_out']['time_stamp'], $p_obj->getTimeStamp(), TRUE) == TRUE)) {
                     $shift++;
                 }
             } elseif ($i > 0 and isset($prev_punch_arr['time_stamp']) and $prev_punch_arr['punch_control_id'] != $p_obj->getPunchControlId() and abs($prev_punch_arr['time_stamp'] - $p_obj->getTimeStamp()) > $maximum_shift_time) {
                 //Debug::text('  New shift because two punch_control records exist and punch timestamp exceed maximum shift time.', __FILE__, __LINE__, __METHOD__, 10);
                 $shift++;
             }
             if (!isset($shift_data[$shift]['total_time'])) {
                 $shift_data[$shift]['total_time'] = 0;
             }
             $punch_day_epoch = TTDate::getBeginDayEpoch($p_obj->getTimeStamp());
             if (!isset($shift_data[$shift]['total_time_per_day'][$punch_day_epoch])) {
                 $shift_data[$shift]['total_time_per_day'][$punch_day_epoch] = 0;
             }
             //Determine which shift is closest to the given epoch.
             $punch_difference_from_epoch = abs($epoch - $p_obj->getTimeStamp());
             if ($nearest_punch_difference === FALSE or $punch_difference_from_epoch <= $nearest_punch_difference) {
                 Debug::text('Nearest Shift Determined to be: ' . $shift . ' Nearest Punch Diff: ' . (int) $nearest_punch_difference . ' Punch Diff: ' . $punch_difference_from_epoch, __FILE__, __LINE__, __METHOD__, 10);
                 //If two punches have the same timestamp, use the shift that matches the passed punch control object, which is usually the one we are currently editing...
                 //This is for splitting shifts at exactly midnight.
                 if ($punch_difference_from_epoch != $nearest_punch_difference or $punch_difference_from_epoch == $nearest_punch_difference and (is_object($tmp_punch_control_obj) and $tmp_punch_control_obj->getId() == $p_obj->getPunchControlID())) {
                     //Debug::text('Found two punches with the same timestamp... Tmp Punch Control: '.$tmp_punch_control_obj->getId() .' Punch Control: '. $p_obj->getPunchControlID() , __FILE__, __LINE__, __METHOD__, 10);
                     $nearest_shift_id = $shift;
                     $nearest_punch_difference = $punch_difference_from_epoch;
                 }
             }
             $punch_arr = array('id' => $p_obj->getId(), 'punch_control_id' => $p_obj->getPunchControlId(), 'user_date_id' => $punch_control_obj->getUserDateID(), 'time_stamp' => $p_obj->getTimeStamp(), 'status_id' => $p_obj->getStatus(), 'type_id' => $p_obj->getType());
             $shift_data[$shift]['punches'][] = $punch_arr;
             $shift_data[$shift]['punch_control_ids'][] = $p_obj->getPunchControlId();
             if ($punch_control_obj->getUserDateID() != FALSE) {
                 $shift_data[$shift]['user_date_ids'][] = $punch_control_obj->getUserDateID();
             }
             if (!isset($shift_data[$shift]['span_midnight'])) {
                 $shift_data[$shift]['span_midnight'] = FALSE;
             }
             if (!isset($shift_data[$shift]['first_in']) and $p_obj->getStatus() == 10) {
                 //Debug::text('First In -- Punch ID: '. $p_obj->getID() .' Punch Control ID: '. $p_obj->getPunchControlID() .' TimeStamp: '. TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp() ), __FILE__, __LINE__, __METHOD__, 10);
                 $shift_data[$shift]['first_in'] = $punch_arr;
             } elseif ($p_obj->getStatus() == 20) {
                 //Debug::text('Last Out -- Punch ID: '. $p_obj->getID() .' Punch Control ID: '. $p_obj->getPunchControlID() .' TimeStamp: '. TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp() ), __FILE__, __LINE__, __METHOD__, 10);
                 $shift_data[$shift]['last_out'] = $punch_arr;
                 //Debug::text('Total Time: '. $total_time, __FILE__, __LINE__, __METHOD__, 10);
                 $shift_data[$shift]['total_time'] += $total_time;
                 //Check to see if the previous punch was on a different day then the current punch.
                 if (isset($prev_punch_arr) and is_array($prev_punch_arr) and ($p_obj->getStatus() == 20 and $prev_punch_arr['status_id'] != 20) and TTDate::doesRangeSpanMidnight($prev_punch_arr['time_stamp'], $p_obj->getTimeStamp()) == TRUE) {
                     Debug::text('Punch PAIR DOES span midnight', __FILE__, __LINE__, __METHOD__, 10);
                     $shift_data[$shift]['span_midnight'] = TRUE;
                     $total_time_for_each_day_arr = TTDate::calculateTimeOnEachDayBetweenRange($prev_punch_arr['time_stamp'], $p_obj->getTimeStamp());
                     if (is_array($total_time_for_each_day_arr)) {
                         foreach ($total_time_for_each_day_arr as $begin_day_epoch => $day_total_time) {
                             if (!isset($shift_data[$shift]['total_time_per_day'][$begin_day_epoch])) {
                                 $shift_data[$shift]['total_time_per_day'][$begin_day_epoch] = 0;
                             }
                             $shift_data[$shift]['total_time_per_day'][$begin_day_epoch] += $day_total_time;
                         }
                     }
                     unset($total_time_for_each_day_arr, $begin_day_epoch, $day_total_time, $prev_day_total_time);
                 } else {
                     $shift_data[$shift]['total_time_per_day'][$punch_day_epoch] += $total_time;
                 }
             }
             $prev_punch_arr = $punch_arr;
             $i++;
         }
         //Debug::Arr($shift_data, 'aShift Data:', __FILE__, __LINE__, __METHOD__, 10);
         if (isset($shift_data)) {
             //Loop through each shift to determine the day with the most time.
             foreach ($shift_data as $tmp_shift_key => $tmp_shift_data) {
                 krsort($shift_data[$tmp_shift_key]['total_time_per_day']);
                 //Sort by day first
                 arsort($shift_data[$tmp_shift_key]['total_time_per_day']);
                 //Sort by total time per day.
                 reset($shift_data[$tmp_shift_key]['total_time_per_day']);
                 $shift_data[$tmp_shift_key]['day_with_most_time'] = key($shift_data[$tmp_shift_key]['total_time_per_day']);
                 $shift_data[$tmp_shift_key]['punch_control_ids'] = array_unique($shift_data[$tmp_shift_key]['punch_control_ids']);
                 if (isset($shift_data[$tmp_shift_key]['user_date_ids'])) {
                     $shift_data[$tmp_shift_key]['user_date_ids'] = array_unique($shift_data[$tmp_shift_key]['user_date_ids']);
                 }
             }
             unset($tmp_shift_key, $tmp_shift_data);
             if ($filter == 'first_shift') {
                 //Only return first shift.
                 $shift_data = $shift_data[0];
             } elseif ($filter == 'last_shift') {
                 //Only return last shift.
                 $shift_data = $shift_data[$shift];
             } elseif ($filter == 'nearest_shift') {
                 $shift_data = $shift_data[$nearest_shift_id];
                 //Check to make sure the nearest shift is within the new shift trigger time of EPOCH.
                 if (isset($shift_data['first_in']['time_stamp'])) {
                     $first_in = $shift_data['first_in']['time_stamp'];
                 } elseif (isset($shift_data['last_out']['time_stamp'])) {
                     $first_in = $shift_data['last_out']['time_stamp'];
                 }
                 if (isset($shift_data['last_out']['time_stamp'])) {
                     $last_out = $shift_data['last_out']['time_stamp'];
                 } elseif (isset($shift_data['first_in']['time_stamp'])) {
                     $last_out = $shift_data['first_in']['time_stamp'];
                 }
                 //The check below must occur so if the user attempts to add an In punch that occurs AFTER the Out punch, this function
                 //still returns the shift data, so the validation checks can occur in PunchControl factory.
                 if ($first_in > $last_out) {
                     //It appears that the first in punch has occurred after the OUT punch, so swap first_in and last_out, so we don't return FALSE in this case.
                     list($first_in, $last_out) = array($last_out, $first_in);
                 }
                 if (TTDate::isTimeOverLap($epoch, $epoch, $first_in - $new_shift_trigger_time, $last_out + $new_shift_trigger_time) == FALSE) {
                     Debug::Text('Nearest shift is outside the new shift trigger time... Epoch: ' . $epoch . ' First In: ' . $first_in . ' Last Out: ' . $last_out . ' New Shift Trigger: ' . $new_shift_trigger_time, __FILE__, __LINE__, __METHOD__, 10);
                     return FALSE;
                 }
                 unset($first_in, $last_out);
             }
             $profiler->stopTimer('PayPeriodScheduleFactory::getShiftData()');
             //Debug::Arr($shift_data, 'bShift Data:', __FILE__, __LINE__, __METHOD__, 10);
             return $shift_data;
         }
     }
     $profiler->stopTimer('PayPeriodScheduleFactory::getShiftData()');
     return FALSE;
 }
Пример #4
0
 public static function calculateTimeOnEachDayBetweenRange($start_epoch, $end_epoch)
 {
     if (TTDate::doesRangeSpanMidnight($start_epoch, $end_epoch) == TRUE) {
         $total_before_first_midnight = TTDate::getEndDayEpoch($start_epoch) + 1 - $start_epoch;
         if ($total_before_first_midnight > 0) {
             $retval[TTDate::getBeginDayEpoch($start_epoch)] = $total_before_first_midnight;
         }
         $loop_start = TTDate::getEndDayEpoch($start_epoch) + 1;
         $loop_end = TTDate::getBeginDayEpoch($end_epoch);
         for ($x = $loop_start; $x < $loop_end; $x += 86400) {
             $retval[TTDate::getBeginDayEpoch($x)] = 86400;
         }
         $total_after_last_midnight = $end_epoch - TTDate::getBeginDayEpoch($end_epoch);
         if ($total_after_last_midnight > 0) {
             $retval[TTDate::getBeginDayEpoch($end_epoch)] = $total_after_last_midnight;
         }
     } else {
         $retval = array(TTDate::getBeginDayEpoch($start_epoch) => $end_epoch - $start_epoch);
     }
     return $retval;
 }
 static function calcExceptions($user_date_id, $enable_premature_exceptions = FALSE, $enable_future_exceptions = TRUE)
 {
     global $profiler;
     $profiler->startTimer("ExceptionPolicy::calcExceptions()");
     if ($user_date_id == '') {
         return FALSE;
     }
     Debug::text(' User Date ID: ' . $user_date_id . ' PreMature: ' . (int) $enable_premature_exceptions, __FILE__, __LINE__, __METHOD__, 10);
     $current_epoch = TTDate::getTime();
     //Get user date info
     $udlf = TTnew('UserDateListFactory');
     $udlf->getById($user_date_id);
     if ($udlf->getRecordCount() > 0) {
         $user_date_obj = $udlf->getCurrent();
         if ($enable_future_exceptions == FALSE and $user_date_obj->getDateStamp() > TTDate::getEndDayEpoch($current_epoch)) {
             return FALSE;
         }
     } else {
         return FALSE;
     }
     //16hrs... If punches are older then this time, its no longer premature.
     //This should actually be the PayPeriod Schedule maximum shift time.
     if (is_object($user_date_obj->getPayPeriodObject()) and is_object($user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject())) {
         self::$premature_delay = $user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject()->getMaximumShiftTime();
         Debug::text(' Setting preMature Exception delay to maximum shift time: ' . self::$premature_delay, __FILE__, __LINE__, __METHOD__, 10);
     } else {
         self::$premature_delay = 57600;
     }
     //Get list of existing exceptions, so we can determine if we need to delete any. We can't delete them all blindly and re-create them
     //as this will send duplicate email notifications for every single punch.
     $existing_exceptions = array();
     $elf = TTnew('ExceptionListFactory');
     $elf->getByUserDateID($user_date_id);
     if ($elf->getRecordCount() > 0) {
         foreach ($elf as $e_obj) {
             $existing_exceptions[] = array('id' => $e_obj->getId(), 'user_date_id' => $e_obj->getUserDateID(), 'exception_policy_id' => $e_obj->getExceptionPolicyID(), 'type_id' => $e_obj->getType(), 'punch_id' => $e_obj->getPunchID(), 'punch_control_id' => $e_obj->getPunchControlID());
         }
     }
     unset($elf, $e_obj);
     //Get all Punches on this date for this user.
     $plf = TTnew('PunchListFactory');
     $plf->getByUserDateId($user_date_id);
     if ($plf->getRecordCount() > 0) {
         Debug::text(' Found Punches: ' . $plf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10);
     }
     $slf = TTnew('ScheduleListFactory');
     $slf->getByUserDateIdAndStatusId($user_date_id, 10);
     if ($slf->getRecordCount() > 0) {
         Debug::text(' Found Schedule: ' . $slf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10);
     }
     $schedule_id_cache = NULL;
     //Cache schedule IDs so we don't need to do a lookup for every exception.
     $current_exceptions = array();
     //Array holding current exception data.
     //Get all active exceptions.
     $eplf = TTnew('ExceptionPolicyListFactory');
     $eplf->getByPolicyGroupUserIdAndActive($user_date_obj->getUser(), TRUE);
     if ($eplf->getRecordCount() > 0) {
         Debug::text(' Found Active Exceptions: ' . $eplf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10);
         foreach ($eplf as $ep_obj) {
             //Debug::text(' Found Exception Type: '. $ep_obj->getType() .' ID: '. $ep_obj->getID() .' Control ID: '. $ep_obj->getExceptionPolicyControl(), __FILE__, __LINE__, __METHOD__,10);
             if ($enable_premature_exceptions == TRUE and self::isPreMature($ep_obj->getType()) == TRUE) {
                 //Debug::text(' Premature Exception: '. $ep_obj->getType() , __FILE__, __LINE__, __METHOD__,10);
                 $type_id = 5;
                 //Pre-Mature
             } else {
                 //Debug::text(' NOT Premature Exception: '. $ep_obj->getType() , __FILE__, __LINE__, __METHOD__,10);
                 $type_id = 50;
                 //Active
             }
             switch (strtolower($ep_obj->getType())) {
                 case 's1':
                     //Unscheduled Absence... Anytime they are scheduled and have not punched in.
                     //Ignore these exceptions if the schedule is after today (not including today),
                     //so if a supervisors schedules an employee two days in advance they don't get a unscheduled
                     //absence appearing right away.
                     //Since we now trigger In Late/Out Late exceptions immediately after schedule time, only trigger this exception after
                     //the schedule end time has passed.
                     //**We also need to handle shifts that start at 11:00PM on one day, end at 8:00AM the next day, and they are assigned to the day where
                     //the most time is worked (ie: the next day).
                     //Handle split shifts too...
                     //- This has a side affect that if the schedule policy start/stop time is set to 0, it will trigger both a UnScheduled Absence
                     //  and a Not Scheduled exception for the same schedule/punch.
                     //Loop through all schedules, then find punches to match.
                     if ($slf->getRecordCount() > 0) {
                         foreach ($slf as $s_obj) {
                             if ($s_obj->getStatus() == 10 and $current_epoch >= $s_obj->getEndTime()) {
                                 $add_exception = TRUE;
                                 //Debug::text(' Found Schedule: Start Time: '. TTDate::getDate('DATE+TIME', $s_obj->getStartTime() ), __FILE__, __LINE__, __METHOD__,10);
                                 //Find punches that fall within this schedule time including start/stop window.
                                 if (TTDate::doesRangeSpanMidnight($s_obj->getStartTime(), $s_obj->getEndTime()) and is_object($user_date_obj) and is_object($user_date_obj->getPayPeriodObject()) and is_object($user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject())) {
                                     //Get punches from both days.
                                     $plf_tmp = TTnew('PunchListFactory');
                                     $plf_tmp->getShiftPunchesByUserIDAndEpoch($user_date_obj->getUser(), $s_obj->getStartTime(), 0, $user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject()->getMaximumShiftTime());
                                     Debug::text(' Schedule spans midnight... Found rows from expanded search: ' . $plf_tmp->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10);
                                     if ($plf_tmp->getRecordCount() > 0) {
                                         foreach ($plf_tmp as $p_obj_tmp) {
                                             if ($s_obj->inSchedule($p_obj_tmp->getTimeStamp())) {
                                                 Debug::text(' aFound punch for schedule...', __FILE__, __LINE__, __METHOD__, 10);
                                                 $add_exception = FALSE;
                                                 break;
                                             }
                                         }
                                     }
                                     unset($plf_tmp, $p_obj_tmp);
                                 } else {
                                     //Get punches from just this day.
                                     foreach ($plf as $p_obj) {
                                         if ($s_obj->inSchedule($p_obj->getTimeStamp())) {
                                             //Debug::text(' bFound punch for schedule...', __FILE__, __LINE__, __METHOD__,10);
                                             $add_exception = FALSE;
                                             break;
                                         }
                                     }
                                 }
                                 if ($add_exception == TRUE) {
                                     //Debug::text(' Adding S1 exception...', __FILE__, __LINE__, __METHOD__,10);
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => FALSE, 'schedule_obj' => $s_obj);
                                 }
                             }
                         }
                     }
                     unset($s_obj, $add_exception);
                     break;
                 case 's2':
                     //Not Scheduled
                     //**We also need to handle shifts that start at 11:00PM on one day, end at 8:00AM the next day, and they are assigned to the day where
                     //the most time is worked (ie: the next day).
                     //Handle split shifts too...
                     if ($plf->getRecordCount() > 1) {
                         //Make sure at least two punche exist.
                         //Loop through each punch, find out if they are scheduled, and if they are in early
                         $prev_punch_time_stamp = FALSE;
                         foreach ($plf as $p_obj) {
                             //Ignore punches that have the exact same timestamp, as they are likely transfer punches.
                             if ($prev_punch_time_stamp != $p_obj->getTimeStamp() and $p_obj->getType() == 10 and $p_obj->getStatus() == 10) {
                                 //Normal In
                                 if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                     $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                 }
                                 //Check if no schedule exists, or an absent schedule exists. If they work when not scheduled (no schedule) or schedule absent, both should trigger this.
                                 if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == FALSE or is_object($p_obj->getScheduleObject()) and $p_obj->getScheduleObject()->getStatus() == 20) {
                                     //Debug::text(' Worked when wasnt scheduled', __FILE__, __LINE__, __METHOD__,10);
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getID(), 'punch_control_id' => FALSE);
                                 } else {
                                     Debug::text('    Schedule Found', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                             $prev_punch_time_stamp = $p_obj->getTimeStamp();
                         }
                     }
                     unset($scheduled_id_cache, $prev_punch_time_stamp, $p_obj);
                     break;
                 case 's3':
                     //In Early
                     if ($plf->getRecordCount() > 0) {
                         //Loop through each punch, find out if they are scheduled, and if they are in early
                         $prev_punch_time_stamp = FALSE;
                         foreach ($plf as $p_obj) {
                             //Ignore punches that have the exact same timestamp, as they are likely transfer punches.
                             if ($prev_punch_time_stamp != $p_obj->getTimeStamp() and $p_obj->getType() == 10 and $p_obj->getStatus() == 10) {
                                 //Normal In
                                 if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                     $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                 }
                                 if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == TRUE) {
                                     if ($p_obj->getTimeStamp() < $p_obj->getScheduleObject()->getStartTime()) {
                                         if (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getStartTime(), $ep_obj->getGrace()) == TRUE) {
                                             Debug::text('    Within Grace time, IGNORE EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                         } elseif (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getStartTime(), $ep_obj->getWatchWindow()) == TRUE) {
                                             Debug::text('    NOT Within Grace time, SET EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getID(), 'punch_control_id' => FALSE, 'punch_obj' => $p_obj, 'schedule_obj' => $p_obj->getScheduleObject());
                                         }
                                     }
                                 } else {
                                     Debug::text('    NO Schedule Found', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                             $prev_punch_time_stamp = $p_obj->getTimeStamp();
                         }
                     }
                     break;
                 case 's4':
                     //In Late
                     if ($plf->getRecordCount() > 0) {
                         $prev_punch_time_stamp = FALSE;
                         foreach ($plf as $p_obj) {
                             Debug::text('    In Late. Punch: ' . TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp()), __FILE__, __LINE__, __METHOD__, 10);
                             //Ignore punches that have the exact same timestamp and/or punches with the transfer flag, as they are likely transfer punches.
                             if ($prev_punch_time_stamp != $p_obj->getTimeStamp() and $p_obj->getTransfer() == FALSE and $p_obj->getType() == 10 and $p_obj->getStatus() == 10) {
                                 //Normal In
                                 if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                     $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                 }
                                 if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == TRUE) {
                                     if ($p_obj->getTimeStamp() > $p_obj->getScheduleObject()->getStartTime()) {
                                         if (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getStartTime(), $ep_obj->getGrace()) == TRUE) {
                                             Debug::text('    Within Grace time, IGNORE EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                         } elseif (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getStartTime(), $ep_obj->getWatchWindow()) == TRUE) {
                                             Debug::text('    NOT Within Grace time, SET EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getID(), 'punch_control_id' => FALSE, 'punch_obj' => $p_obj, 'schedule_obj' => $p_obj->getScheduleObject());
                                         }
                                     }
                                 } else {
                                     Debug::text('    NO Schedule Found', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                             $prev_punch_time_stamp = $p_obj->getTimeStamp();
                         }
                     }
                     unset($scheduled_id_cache);
                     //Late Starting their shift, with no punch yet, trigger exception if:
                     //  - Schedule is found
                     //	- Current time is after schedule start time and before schedule end time.
                     // 	- Current time is after exception grace time
                     //Make sure we take into account split shifts.
                     Debug::text('    Checking Late Starting Shift exception... Current time: ' . TTDate::getDate('DATE+TIME', $current_epoch), __FILE__, __LINE__, __METHOD__, 10);
                     if ($slf->getRecordCount() > 0) {
                         foreach ($slf as $s_obj) {
                             if ($s_obj->getStatus() == 10 and ($current_epoch >= $s_obj->getStartTime() and $current_epoch <= $s_obj->getEndTime())) {
                                 if (TTDate::inWindow($current_epoch, $s_obj->getStartTime(), $ep_obj->getGrace()) == TRUE) {
                                     Debug::text('    Within Grace time, IGNORE EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                 } else {
                                     //See if we can find a punch within the schedule time, if so assume we already created the exception above.
                                     //Make sure we take into account the schedule policy start/stop window.
                                     //However in the case where a single schedule shift and just one punch exists, if an employee comes in really
                                     //early (1AM) before the schedule start/stop window it will trigger an In Late exception.
                                     //This could still be correct though if they only come in for an hour, then come in late for their shift later.
                                     //Schedule start/stop time needs to be correct.
                                     //Also need to take into account shifts that span midnight, ie: 10:30PM to 6:00AM, as its important the schedules/punches match up properly.
                                     $add_exception = TRUE;
                                     Debug::text(' Found Schedule: Start Time: ' . TTDate::getDate('DATE+TIME', $s_obj->getStartTime()), __FILE__, __LINE__, __METHOD__, 10);
                                     //Find punches that fall within this schedule time including start/stop window.
                                     if (TTDate::doesRangeSpanMidnight($s_obj->getStartTime(), $s_obj->getEndTime()) and is_object($user_date_obj) and is_object($user_date_obj->getPayPeriodObject()) and is_object($user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject())) {
                                         //Get punches from both days.
                                         $plf_tmp = TTnew('PunchListFactory');
                                         $plf_tmp->getShiftPunchesByUserIDAndEpoch($user_date_obj->getUser(), $s_obj->getStartTime(), 0, $user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject()->getMaximumShiftTime());
                                         Debug::text(' Schedule spans midnight... Found rows from expanded search: ' . $plf_tmp->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10);
                                         if ($plf_tmp->getRecordCount() > 0) {
                                             foreach ($plf_tmp as $p_obj_tmp) {
                                                 if ($s_obj->inSchedule($p_obj_tmp->getTimeStamp())) {
                                                     Debug::text('    Found punch for this schedule, skipping schedule...', __FILE__, __LINE__, __METHOD__, 10);
                                                     $add_exception = FALSE;
                                                     continue 2;
                                                     //Skip to next schedule without creating exception.
                                                 }
                                             }
                                         }
                                         unset($plf_tmp, $p_obj_tmp);
                                     } else {
                                         //Get punches from just this day.
                                         foreach ($plf as $p_obj) {
                                             if ($s_obj->inSchedule($p_obj->getTimeStamp())) {
                                                 Debug::text(' bFound punch for schedule...', __FILE__, __LINE__, __METHOD__, 10);
                                                 $add_exception = FALSE;
                                                 break;
                                             }
                                         }
                                     }
                                     if ($add_exception == TRUE) {
                                         Debug::text('    NOT Within Grace time, SET EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                         $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => FALSE, 'schedule_obj' => $s_obj);
                                     }
                                 }
                             }
                         }
                     } else {
                         Debug::text('    NO Schedule Found', __FILE__, __LINE__, __METHOD__, 10);
                     }
                     break;
                 case 's5':
                     //Out Early
                     if ($plf->getRecordCount() > 0) {
                         //Loop through each punch, find out if they are scheduled, and if they are in early
                         $prev_punch_time_stamp = FALSE;
                         $total_punches = $plf->getRecordCount();
                         $x = 1;
                         foreach ($plf as $p_obj) {
                             //Ignore punches that have the exact same timestamp and/or punches with the transfer flag, as they are likely transfer punches.
                             //For Out Early, we have to wait until we are at the last punch, or there is a subsequent punch
                             // to see if it matches the exact same time (transfer)
                             //Therefore we need a two step confirmation before this exception can be triggered. Current punch, then next punch if it exists.
                             if ($p_obj->getTransfer() == FALSE and $p_obj->getType() == 10 and $p_obj->getStatus() == 20) {
                                 //Normal Out
                                 if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                     $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                 }
                                 if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == TRUE) {
                                     if ($p_obj->getTimeStamp() < $p_obj->getScheduleObject()->getEndTime()) {
                                         if (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getEndTime(), $ep_obj->getGrace()) == TRUE) {
                                             Debug::text('    Within Grace time, IGNORE EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                         } elseif (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getEndTime(), $ep_obj->getWatchWindow()) == TRUE) {
                                             Debug::text('    NOT Within Grace time, SET EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                             $tmp_exception = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getID(), 'punch_control_id' => FALSE, 'punch_obj' => $p_obj, 'schedule_obj' => $p_obj->getScheduleObject());
                                             if ($x == $total_punches) {
                                                 //Trigger exception if we're the last punch.
                                                 $current_exceptions[] = $tmp_exception;
                                             } else {
                                                 //Save exception to be triggered if the next punch doesn't match the same time.
                                             }
                                         }
                                     }
                                 } else {
                                     Debug::text('    NO Schedule Found', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             } elseif ($p_obj->getType() == 10 and $p_obj->getStatus() == 10) {
                                 //Normal In
                                 //This comes after an OUT punch, so we need to check if there are two punches
                                 //in a row with the same timestamp, if so ignore the exception.
                                 if (isset($tmp_exception) and $p_obj->getTimeStamp() == $prev_punch_time_stamp) {
                                     unset($tmp_exception);
                                 } elseif (isset($tmp_exception)) {
                                     $current_exceptions[] = $tmp_exception;
                                     //Set exception.
                                 }
                             }
                             $prev_punch_time_stamp = $p_obj->getTimeStamp();
                             $x++;
                         }
                     }
                     unset($tmp_exception, $x, $prev_punch_time_stamp);
                     break;
                 case 's6':
                     //Out Late
                     if ($plf->getRecordCount() > 0) {
                         $prev_punch_time_stamp = FALSE;
                         foreach ($plf as $p_obj) {
                             $punch_pairs[$p_obj->getPunchControlID()][] = array('status_id' => $p_obj->getStatus(), 'punch_control_id' => $p_obj->getPunchControlID(), 'time_stamp' => $p_obj->getTimeStamp());
                             if ($prev_punch_time_stamp != $p_obj->getTimeStamp() and $p_obj->getType() == 10 and $p_obj->getStatus() == 20) {
                                 //Normal Out
                                 if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                     $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                 }
                                 if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == TRUE) {
                                     if ($p_obj->getTimeStamp() > $p_obj->getScheduleObject()->getEndTime()) {
                                         if (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getEndTime(), $ep_obj->getGrace()) == TRUE) {
                                             Debug::text('    Within Grace time, IGNORE EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                         } elseif (TTDate::inWindow($p_obj->getTimeStamp(), $p_obj->getScheduleObject()->getEndTime(), $ep_obj->getWatchWindow()) == TRUE) {
                                             Debug::text('    NOT Within Grace time, SET EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getID(), 'punch_control_id' => FALSE, 'punch_obj' => $p_obj, 'schedule_obj' => $p_obj->getScheduleObject());
                                         }
                                     }
                                 } else {
                                     Debug::text('    NO Schedule Found', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                             $prev_punch_time_stamp = $p_obj->getTimeStamp();
                         }
                         //Trigger exception if no out punch and we have passed schedule out time.
                         //  - Schedule is found
                         //	- Make sure the user is missing an OUT punch.
                         //	- Current time is after schedule end time
                         // 	- Current time is after exception grace time
                         //  - Current time is before schedule end time + maximum shift time.
                         if (isset($punch_pairs) and $slf->getRecordCount() > 0) {
                             foreach ($punch_pairs as $punch_control_id => $punch_pair) {
                                 if (count($punch_pair) != 2) {
                                     Debug::text('aFound Missing Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                     if ($punch_pair[0]['status_id'] == 10) {
                                         //Missing Out Punch
                                         Debug::text('bFound Missing Out Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                         foreach ($slf as $s_obj) {
                                             Debug::text('Punch: ' . TTDate::getDate('DATE+TIME', $punch_pair[0]['time_stamp']) . ' Schedule Start Time: ' . TTDate::getDate('DATE+TIME', $s_obj->getStartTime()) . ' End Time: ' . TTDate::getDate('DATE+TIME', $s_obj->getEndTime()), __FILE__, __LINE__, __METHOD__, 10);
                                             //Because this is just an IN punch, make sure the IN punch is before the schedule end time
                                             //So we can eliminate split shift schedules.
                                             if ($punch_pair[0]['time_stamp'] <= $s_obj->getEndTime() and $current_epoch >= $s_obj->getEndTime() and $current_epoch <= $s_obj->getEndTime() + self::$premature_delay) {
                                                 if (TTDate::inWindow($current_epoch, $s_obj->getEndTime(), $ep_obj->getGrace()) == TRUE) {
                                                     Debug::text('    Within Grace time, IGNORE EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                                 } else {
                                                     Debug::text('    NOT Within Grace time, SET EXCEPTION: ', __FILE__, __LINE__, __METHOD__, 10);
                                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $punch_pair[0]['punch_control_id'], 'schedule_obj' => $s_obj);
                                                 }
                                             }
                                         }
                                     }
                                 } else {
                                     Debug::text('No Missing Punches...', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         }
                         unset($punch_pairs, $punch_pair);
                     }
                     break;
                 case 'm1':
                     //Missing In Punch
                     if ($plf->getRecordCount() > 0) {
                         foreach ($plf as $p_obj) {
                             //Debug::text(' Punch: Status: '. $p_obj->getStatus() .' Punch Control ID: '. $p_obj->getPunchControlID() .' Punch ID: '. $p_obj->getId() .' TimeStamp: '. $p_obj->getTimeStamp(), __FILE__, __LINE__, __METHOD__,10);
                             if ($type_id == 5 and $p_obj->getTimeStamp() < $current_epoch - self::$premature_delay) {
                                 $type_id = 50;
                             }
                             $punch_pairs[$p_obj->getPunchControlID()][] = array('status_id' => $p_obj->getStatus(), 'punch_control_id' => $p_obj->getPunchControlID(), 'punch_id' => $p_obj->getId());
                         }
                         if (isset($punch_pairs)) {
                             foreach ($punch_pairs as $punch_control_id => $punch_pair) {
                                 //Debug::Arr($punch_pair, 'Punch Pair for Control ID:'. $punch_control_id, __FILE__, __LINE__, __METHOD__,10);
                                 if (count($punch_pair) != 2) {
                                     Debug::text('a1Found Missing Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                     if ($punch_pair[0]['status_id'] == 20) {
                                         //Missing In Punch
                                         Debug::text('b1Found Missing In Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                         $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $punch_pair[0]['punch_control_id']);
                                     }
                                 } else {
                                     Debug::text('No Missing Punches...', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         }
                         unset($punch_pairs, $punch_pair);
                     }
                     break;
                 case 'm2':
                     //Missing Out Punch
                     if ($plf->getRecordCount() > 0) {
                         foreach ($plf as $p_obj) {
                             Debug::text(' Punch: Status: ' . $p_obj->getStatus() . ' Punch Control ID: ' . $p_obj->getPunchControlID() . ' Punch ID: ' . $p_obj->getId() . ' TimeStamp: ' . $p_obj->getTimeStamp(), __FILE__, __LINE__, __METHOD__, 10);
                             //This causes the exception to trigger if the first punch pair is more than the Maximum Shift time away from the current punch,
                             //ie: In: 1:00AM, Out: 2:00AM, In 3:00PM (Maximum Shift Time less than 12hrs). The missing punch exception will be triggered immediately upon the 3:00PM punch.
                             //if ( $type_id == 5 AND $p_obj->getTimeStamp() < ($current_epoch-self::$premature_delay) ) {
                             //	$type_id = 50;
                             //}
                             $punch_pairs[$p_obj->getPunchControlID()][] = array('status_id' => $p_obj->getStatus(), 'punch_control_id' => $p_obj->getPunchControlID(), 'time_stamp' => $p_obj->getTimeStamp());
                         }
                         if (isset($punch_pairs)) {
                             foreach ($punch_pairs as $punch_control_id => $punch_pair) {
                                 if (count($punch_pair) != 2) {
                                     Debug::text('a2Found Missing Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                     if ($punch_pair[0]['status_id'] == 10) {
                                         //Missing Out Punch
                                         Debug::text('b2Found Missing Out Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                         //Make sure we are at least MaximumShift Time from the matching In punch before trigging this exception.
                                         //Even when an supervisor is entering punches for today, make missing out punch pre-mature if the maximum shift time isn't exceeded.
                                         //This will prevent timesheet recalculations from having missing punches for everyone today.
                                         //if ( $type_id == 5 AND $punch_pair[0]['time_stamp'] < ($current_epoch-self::$premature_delay) ) {
                                         if ($punch_pair[0]['time_stamp'] < $current_epoch - self::$premature_delay) {
                                             $type_id = 50;
                                         } else {
                                             $type_id = 5;
                                         }
                                         $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $punch_pair[0]['punch_control_id']);
                                     }
                                 } else {
                                     Debug::text('No Missing Punches...', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         }
                         unset($punch_pairs, $punch_pair);
                     }
                     break;
                 case 'm3':
                     //Missing Lunch In/Out punch
                     if ($plf->getRecordCount() > 0) {
                         //We need to account for cases where they may punch IN from lunch first, then Out.
                         //As well as just a Lunch In punch and nothing else.
                         foreach ($plf as $p_obj) {
                             if ($type_id == 5 and $p_obj->getTimeStamp() < $current_epoch - self::$premature_delay) {
                                 $type_id = 50;
                             }
                             $punches[] = $p_obj;
                         }
                         if (isset($punches) and is_array($punches)) {
                             foreach ($punches as $key => $p_obj) {
                                 if ($p_obj->getType() == 20) {
                                     //Lunch
                                     Debug::text(' Punch: Status: ' . $p_obj->getStatus() . ' Punch Control ID: ' . $p_obj->getPunchControlID() . ' TimeStamp: ' . $p_obj->getTimeStamp(), __FILE__, __LINE__, __METHOD__, 10);
                                     if ($p_obj->getStatus() == 10) {
                                         //Make sure previous punch is Lunch/Out
                                         if (!isset($punches[$key - 1]) or isset($punches[$key - 1]) and is_object($punches[$key - 1]) and ($punches[$key - 1]->getType() != 20 or $punches[$key - 1]->getStatus() != 20)) {
                                             //Invalid punch
                                             $invalid_punches[] = array('punch_id' => $p_obj->getId());
                                         }
                                     } else {
                                         //Make sure next punch is Lunch/In
                                         if (!isset($punches[$key + 1]) or isset($punches[$key + 1]) and is_object($punches[$key + 1]) and ($punches[$key + 1]->getType() != 20 or $punches[$key + 1]->getStatus() != 10)) {
                                             //Invalid punch
                                             $invalid_punches[] = array('punch_id' => $p_obj->getId());
                                         }
                                     }
                                 }
                             }
                             unset($punches, $key, $p_obj);
                             if (isset($invalid_punches) and count($invalid_punches) > 0) {
                                 foreach ($invalid_punches as $invalid_punch_arr) {
                                     Debug::text('Found Missing Lunch In/Out Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $invalid_punch_arr['punch_id'], 'punch_control_id' => FALSE);
                                 }
                                 unset($invalid_punch_arr);
                             } else {
                                 Debug::text('Lunch Punches match up.', __FILE__, __LINE__, __METHOD__, 10);
                             }
                             unset($invalid_punches);
                         }
                     }
                     break;
                 case 'm4':
                     //Missing Break In/Out punch
                     if ($plf->getRecordCount() > 0) {
                         //We need to account for cases where they may punch IN from break first, then Out.
                         //As well as just a break In punch and nothing else.
                         foreach ($plf as $p_obj) {
                             if ($type_id == 5 and $p_obj->getTimeStamp() < $current_epoch - self::$premature_delay) {
                                 $type_id = 50;
                             }
                             $punches[] = $p_obj;
                         }
                         if (isset($punches) and is_array($punches)) {
                             foreach ($punches as $key => $p_obj) {
                                 if ($p_obj->getType() == 30) {
                                     //Break
                                     Debug::text(' Punch: Status: ' . $p_obj->getStatus() . ' Type: ' . $p_obj->getType() . ' Punch Control ID: ' . $p_obj->getPunchControlID() . ' TimeStamp: ' . $p_obj->getTimeStamp(), __FILE__, __LINE__, __METHOD__, 10);
                                     if ($p_obj->getStatus() == 10) {
                                         //Make sure previous punch is Break/Out
                                         if (!isset($punches[$key - 1]) or isset($punches[$key - 1]) and is_object($punches[$key - 1]) and ($punches[$key - 1]->getType() != 30 or $punches[$key - 1]->getStatus() != 20)) {
                                             //Invalid punch
                                             $invalid_punches[] = array('punch_id' => $p_obj->getId());
                                         }
                                     } else {
                                         //Make sure next punch is Break/In
                                         if (!isset($punches[$key + 1]) or isset($punches[$key + 1]) and is_object($punches[$key + 1]) and ($punches[$key + 1]->getType() != 30 or $punches[$key + 1]->getStatus() != 10)) {
                                             //Invalid punch
                                             $invalid_punches[] = array('punch_id' => $p_obj->getId());
                                         }
                                     }
                                 }
                             }
                             unset($punches, $key, $p_obj);
                             if (isset($invalid_punches) and count($invalid_punches) > 0) {
                                 foreach ($invalid_punches as $invalid_punch_arr) {
                                     Debug::text('Found Missing Break In/Out Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $invalid_punch_arr['punch_id'], 'punch_control_id' => FALSE);
                                 }
                                 unset($invalid_punch_arr);
                             } else {
                                 Debug::text('Lunch Punches match up.', __FILE__, __LINE__, __METHOD__, 10);
                             }
                             unset($invalid_punches);
                         }
                     }
                     break;
                 case 'c1':
                     //Missed Check-in
                     //Use grace period and make sure the employee punches within that period of time (usually a transfer punch, but break/lunch should work too)
                     if ($plf->getRecordCount() > 0 and $ep_obj->getGrace() > 0) {
                         $prev_punch_time_stamp = FALSE;
                         $prev_punch_obj = FALSE;
                         $x = 1;
                         foreach ($plf as $p_obj) {
                             Debug::text('   Missed Check-In Punch: ' . TTDate::getDate('DATE+TIME', $p_obj->getTimeStamp()) . ' Delay: ' . self::$premature_delay . ' Current Epoch: ' . $current_epoch, __FILE__, __LINE__, __METHOD__, 10);
                             //Handle punch pairs below. Only trigger on OUT punches.
                             if (is_object($prev_punch_obj) and $prev_punch_obj->getStatus() == 10 and $p_obj->getStatus() == 20 and $p_obj->getTimeStamp() - $prev_punch_time_stamp > $ep_obj->getGrace()) {
                                 //Only check OUT punches when paired.
                                 Debug::text('   Triggering excepetion as employee missed check-in within: ' . ($p_obj->getTimeStamp() - $prev_punch_time_stamp), __FILE__, __LINE__, __METHOD__, 10);
                                 $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getID(), 'punch_control_id' => FALSE, 'punch_obj' => $p_obj, 'schedule_obj' => $p_obj->getScheduleObject());
                             } elseif ($prev_punch_time_stamp !== FALSE) {
                                 Debug::text('   Employee Checked-In within: ' . ($p_obj->getTimeStamp() - $prev_punch_time_stamp), __FILE__, __LINE__, __METHOD__, 10);
                             }
                             //Handle cases where there is a IN punch but no OUT punch yet.
                             //However ignore cases where there is a OUT punch but no IN punch.
                             if ($x == $plf->getRecordCount() and $p_obj->getStatus() == 10 and $current_epoch - $p_obj->getTimeStamp() > $ep_obj->getGrace() and $p_obj->getTimeStamp() > $current_epoch - self::$premature_delay) {
                                 Debug::text('   Triggering excepetion as employee hasnt checked in yet, within: ' . ($current_epoch - $prev_punch_time_stamp), __FILE__, __LINE__, __METHOD__, 10);
                                 $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $p_obj->getPunchControlID(), 'punch_obj' => $p_obj, 'schedule_obj' => $p_obj->getScheduleObject());
                             }
                             $prev_punch_time_stamp = $p_obj->getTimeStamp();
                             $prev_punch_obj = $p_obj;
                             $x++;
                         }
                     }
                     unset($prev_punch_obj, $prev_punch_time_stamp, $x);
                     break;
                 case 'd1':
                     //No Branch or Department
                     $add_exception = FALSE;
                     foreach ($plf as $p_obj) {
                         //In punches only
                         if ($p_obj->getStatus() == 10 and is_object($p_obj->getPunchControlObject())) {
                             //If no Tasks are setup, ignore checking them.
                             if ($p_obj->getPunchControlObject()->getBranch() == '' or $p_obj->getPunchControlObject()->getBranch() == 0 or $p_obj->getPunchControlObject()->getBranch() == FALSE) {
                                 $add_exception = TRUE;
                             }
                             if ($p_obj->getPunchControlObject()->getDepartment() == '' or $p_obj->getPunchControlObject()->getDepartment() == 0 or $p_obj->getPunchControlObject()->getDepartment() == FALSE) {
                                 //Make sure at least one task exists before triggering exception.
                                 $dlf = TTNew('DepartmentListFactory');
                                 $dlf->getByCompanyID($user_date_obj->getUserObject()->getCompany(), 1);
                                 //Limit to just 1 record.
                                 if ($dlf->getRecordCount() > 0) {
                                     $add_exception = TRUE;
                                 }
                             }
                             if ($add_exception === TRUE) {
                                 $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getId(), 'punch_control_id' => $p_obj->getPunchControlId());
                             }
                         }
                     }
                     break;
                 case 's7':
                     //Over Scheduled Hours
                     if ($plf->getRecordCount() > 0) {
                         //FIXME: Assign this exception to the last punch of the day, so it can be related back to a punch branch/department?
                         //This ONLY takes in to account WORKED hours, not paid absence hours.
                         //FIXME: Do we want to trigger this before their last out punch?
                         $schedule_total_time = 0;
                         if ($slf->getRecordCount() > 0) {
                             //Check for schedule policy
                             foreach ($slf as $s_obj) {
                                 Debug::text(' Schedule Total Time: ' . $s_obj->getTotalTime(), __FILE__, __LINE__, __METHOD__, 10);
                                 $schedule_total_time += $s_obj->getTotalTime();
                             }
                             $daily_total_time = 0;
                             if ($schedule_total_time > 0) {
                                 //Get daily total time.
                                 $udtlf = TTnew('UserDateTotalListFactory');
                                 //Take into account auto-deduct/add meal policies, but not paid absences.
                                 //$udtlf->getByUserDateIdAndStatusAndType( $user_date_id, 10, 10 );
                                 $udtlf->getByUserDateId($user_date_id);
                                 if ($udtlf->getRecordCount() > 0) {
                                     foreach ($udtlf as $udt_obj) {
                                         if ($udt_obj->getTimeCategory() == 'worked_time') {
                                             $daily_total_time += $udt_obj->getTotalTime();
                                         }
                                     }
                                 }
                                 Debug::text(' Daily Total Time: ' . $daily_total_time . ' Schedule Total Time: ' . $schedule_total_time, __FILE__, __LINE__, __METHOD__, 10);
                                 if ($daily_total_time > 0 and $daily_total_time > $schedule_total_time + $ep_obj->getGrace()) {
                                     Debug::text(' Worked Over Scheduled Hours', __FILE__, __LINE__, __METHOD__, 10);
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => FALSE);
                                 } else {
                                     Debug::text(' DID NOT Work Over Scheduled Hours', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         } else {
                             Debug::text(' Not Scheduled', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     }
                     break;
                 case 's8':
                     //Under Scheduled Hours
                     if ($plf->getRecordCount() > 0) {
                         //FIXME: Assign this exception to the last punch of the day, so it can be related back to a punch branch/department?
                         //This ONLY takes in to account WORKED hours, not paid absence hours.
                         $schedule_total_time = 0;
                         if ($slf->getRecordCount() > 0) {
                             //Check for schedule policy
                             foreach ($slf as $s_obj) {
                                 Debug::text(' Schedule Total Time: ' . $s_obj->getTotalTime(), __FILE__, __LINE__, __METHOD__, 10);
                                 $schedule_total_time += $s_obj->getTotalTime();
                             }
                             $daily_total_time = 0;
                             if ($schedule_total_time > 0) {
                                 //Get daily total time.
                                 $udtlf = TTnew('UserDateTotalListFactory');
                                 //Take into account auto-deduct/add meal policies
                                 //$udtlf->getByUserDateIdAndStatusAndType( $user_date_id, 10, 10 );
                                 $udtlf->getByUserDateId($user_date_id);
                                 if ($udtlf->getRecordCount() > 0) {
                                     foreach ($udtlf as $udt_obj) {
                                         if ($udt_obj->getTimeCategory() == 'worked_time') {
                                             $daily_total_time += $udt_obj->getTotalTime();
                                         }
                                     }
                                 }
                                 Debug::text(' Daily Total Time: ' . $daily_total_time . ' Schedule Total Time: ' . $schedule_total_time, __FILE__, __LINE__, __METHOD__, 10);
                                 if ($daily_total_time < $schedule_total_time - $ep_obj->getGrace()) {
                                     Debug::text(' Worked Under Scheduled Hours', __FILE__, __LINE__, __METHOD__, 10);
                                     if ($type_id == 5 and $user_date_obj->getDateStamp() < TTDate::getBeginDayEpoch($current_epoch - self::$premature_delay)) {
                                         $type_id = 50;
                                     }
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => FALSE);
                                 } else {
                                     Debug::text(' DID NOT Work Under Scheduled Hours', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         } else {
                             Debug::text(' Not Scheduled', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     }
                     break;
                 case 'o1':
                     //Over Daily Time.
                     if ($plf->getRecordCount() > 0) {
                         //FIXME: Assign this exception to the last punch of the day, so it can be related back to a punch branch/department?
                         //This ONLY takes in to account WORKED hours, not paid absence hours.
                         //FIXME: Do we want to trigger this before their last out punch?
                         $daily_total_time = 0;
                         //Get daily total time.
                         $udtlf = TTnew('UserDateTotalListFactory');
                         //Take into account auto-deduct/add meal policies
                         //$udtlf->getByUserDateIdAndStatusAndType( $user_date_id, 10, 10 );
                         $udtlf->getByUserDateId($user_date_id);
                         if ($udtlf->getRecordCount() > 0) {
                             foreach ($udtlf as $udt_obj) {
                                 if ($udt_obj->getTimeCategory() == 'worked_time') {
                                     $daily_total_time += $udt_obj->getTotalTime();
                                 }
                             }
                         }
                         Debug::text(' Daily Total Time: ' . $daily_total_time . ' Watch Window: ' . $ep_obj->getWatchWindow() . ' User Date ID: ' . $user_date_id, __FILE__, __LINE__, __METHOD__, 10);
                         if ($daily_total_time > 0 and $daily_total_time > $ep_obj->getWatchWindow()) {
                             Debug::text(' Worked Over Daily Hours', __FILE__, __LINE__, __METHOD__, 10);
                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => FALSE);
                         } else {
                             Debug::text(' DID NOT Work Over Scheduled Hours', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     }
                     break;
                 case 'o2':
                     //Over Weekly Time.
                 //Over Weekly Time.
                 case 's9':
                     //Over Weekly Scheduled Time.
                     if ($plf->getRecordCount() > 0) {
                         //FIXME: Assign this exception to the last punch of the day, so it can be related back to a punch branch/department?
                         //Get Pay Period Schedule info
                         //FIXME: Do we want to trigger this before their last out punch?
                         if (is_object($user_date_obj->getPayPeriodObject()) and is_object($user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject())) {
                             $start_week_day_id = $user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject()->getStartWeekDay();
                         } else {
                             $start_week_day_id = 0;
                         }
                         Debug::text('Start Week Day ID: ' . $start_week_day_id, __FILE__, __LINE__, __METHOD__, 10);
                         $weekly_scheduled_total_time = 0;
                         //Currently we only consider committed scheduled shifts. We may need to change this to take into account
                         //recurring scheduled shifts that haven't been committed yet as well.
                         //In either case though we should take into account the entires week worth of scheduled time even if we are only partially through
                         //the week, that way we won't be triggering s9 exceptions on a Wed and a Fri or something, it will only occur on the last days of the week.
                         if (strtolower($ep_obj->getType()) == 's9') {
                             $tmp_slf = TTnew('ScheduleListFactory');
                             $tmp_slf->getByUserIdAndStartDateAndEndDate($user_date_obj->getUser(), TTDate::getBeginWeekEpoch($user_date_obj->getDateStamp(), $start_week_day_id), TTDate::getEndWeekEpoch($user_date_obj->getDateStamp(), $start_week_day_id));
                             if ($tmp_slf->getRecordCount() > 0) {
                                 foreach ($tmp_slf as $s_obj) {
                                     if ($s_obj->getStatus() == 10) {
                                         //Only working shifts.
                                         $weekly_scheduled_total_time += $s_obj->getTotalTime();
                                     }
                                 }
                             }
                             unset($tmp_slf, $s_obj);
                         }
                         //This ONLY takes in to account WORKED hours, not paid absence hours.
                         $weekly_total_time = 0;
                         //Get daily total time.
                         $udtlf = TTnew('UserDateTotalListFactory');
                         $weekly_total_time = $udtlf->getWorkedTimeSumByUserIDAndStartDateAndEndDate($user_date_obj->getUser(), TTDate::getBeginWeekEpoch($user_date_obj->getDateStamp(), $start_week_day_id), $user_date_obj->getDateStamp());
                         Debug::text(' Weekly Total Time: ' . $weekly_total_time . ' Weekly Scheduled Total Time: ' . $weekly_scheduled_total_time . ' Watch Window: ' . $ep_obj->getWatchWindow() . ' Grace: ' . $ep_obj->getGrace() . ' User Date ID: ' . $user_date_id, __FILE__, __LINE__, __METHOD__, 10);
                         //Don't trigger either of these exceptions unless both the worked and scheduled time is greater than 0. If they aren't scheduled at all
                         //it should trigger a Unscheduled Absence exception instead of a over weekly scheduled time exception.
                         if (strtolower($ep_obj->getType()) == 'o2' and $weekly_total_time > 0 and $weekly_total_time > $ep_obj->getWatchWindow() or strtolower($ep_obj->getType()) == 's9' and $weekly_scheduled_total_time > 0 and $weekly_total_time > 0 and $weekly_total_time > $weekly_scheduled_total_time + $ep_obj->getGrace()) {
                             Debug::text(' Worked Over Weekly Hours', __FILE__, __LINE__, __METHOD__, 10);
                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => FALSE);
                         } else {
                             Debug::text(' DID NOT Work Over Scheduled Hours', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     }
                     break;
                 case 'l1':
                     //Long Lunch
                 //Long Lunch
                 case 'l2':
                     //Short Lunch
                     if ($plf->getRecordCount() > 0) {
                         //Get all lunch punches.
                         $pair = 0;
                         $x = 0;
                         $out_for_lunch = FALSE;
                         foreach ($plf as $p_obj) {
                             if ($p_obj->getStatus() == 20 and $p_obj->getType() == 20) {
                                 $lunch_out_timestamp = $p_obj->getTimeStamp();
                                 $lunch_punch_arr[$pair]['punch_id'] = $p_obj->getId();
                                 $out_for_lunch = TRUE;
                             } elseif ($out_for_lunch == TRUE and $p_obj->getStatus() == 10 and $p_obj->getType() == 20) {
                                 $lunch_punch_arr[$pair][20] = $lunch_out_timestamp;
                                 $lunch_punch_arr[$pair][10] = $p_obj->getTimeStamp();
                                 $out_for_lunch = FALSE;
                                 $pair++;
                                 unset($lunch_out_timestamp);
                             } else {
                                 $out_for_lunch = FALSE;
                             }
                         }
                         if (isset($lunch_punch_arr)) {
                             //Debug::Arr($lunch_punch_arr, 'Lunch Punch Array: ', __FILE__, __LINE__, __METHOD__,10);
                             foreach ($lunch_punch_arr as $pair => $time_stamp_arr) {
                                 if (isset($time_stamp_arr[10]) and isset($time_stamp_arr[20])) {
                                     $lunch_total_time = bcsub($time_stamp_arr[10], $time_stamp_arr[20]);
                                     Debug::text(' Lunch Total Time: ' . $lunch_total_time, __FILE__, __LINE__, __METHOD__, 10);
                                     if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                         $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                     }
                                     //Check to see if they have a schedule policy
                                     if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == TRUE and is_object($p_obj->getScheduleObject()) == TRUE and is_object($p_obj->getScheduleObject()->getSchedulePolicyObject()) == TRUE) {
                                         $mp_obj = $p_obj->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject();
                                     } else {
                                         $mplf = TTnew('MealPolicyListFactory');
                                         $mplf->getByPolicyGroupUserId($user_date_obj->getUserObject()->getId());
                                         if ($mplf->getRecordCount() > 0) {
                                             Debug::text('Found Meal Policy to apply.', __FILE__, __LINE__, __METHOD__, 10);
                                             $mp_obj = $mplf->getCurrent();
                                         }
                                     }
                                     if (isset($mp_obj) and is_object($mp_obj)) {
                                         $meal_policy_lunch_time = $mp_obj->getAmount();
                                         Debug::text('Meal Policy Time: ' . $meal_policy_lunch_time, __FILE__, __LINE__, __METHOD__, 10);
                                         $add_exception = FALSE;
                                         if (strtolower($ep_obj->getType()) == 'l1' and $meal_policy_lunch_time > 0 and $lunch_total_time > 0 and $lunch_total_time > $meal_policy_lunch_time + $ep_obj->getGrace()) {
                                             $add_exception = TRUE;
                                         } elseif (strtolower($ep_obj->getType()) == 'l2' and $meal_policy_lunch_time > 0 and $lunch_total_time > 0 and $lunch_total_time < $meal_policy_lunch_time - $ep_obj->getGrace()) {
                                             $add_exception = TRUE;
                                         }
                                         if ($add_exception == TRUE) {
                                             Debug::text('Adding Exception!', __FILE__, __LINE__, __METHOD__, 10);
                                             if (isset($time_stamp_arr['punch_id'])) {
                                                 $punch_id = $time_stamp_arr['punch_id'];
                                             } else {
                                                 $punch_id = FALSE;
                                             }
                                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $punch_id, 'punch_control_id' => FALSE);
                                             unset($punch_id);
                                         } else {
                                             Debug::text('Not Adding Exception!', __FILE__, __LINE__, __METHOD__, 10);
                                         }
                                     }
                                 } else {
                                     Debug::text(' Lunch Punches not paired... Skipping!', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         } else {
                             Debug::text(' No Lunch Punches found, or none are paired.', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     }
                     break;
                 case 'l3':
                     //No Lunch
                     if ($plf->getRecordCount() > 0) {
                         //If they are scheduled or not, we can check for a meal policy and base our
                         //decision off that. We don't want a No Lunch exception on a 3hr short shift though.
                         //Also ignore this exception if the lunch is auto-deduct.
                         //**Try to assign this exception to a specific punch control id, so we can do searches based on punch branch.
                         //Find meal policy
                         //Use scheduled meal policy first.
                         $meal_policy_obj = NULL;
                         if ($slf->getRecordCount() > 0) {
                             Debug::text('Schedule Found...', __FILE__, __LINE__, __METHOD__, 10);
                             foreach ($slf as $s_obj) {
                                 if ($s_obj->getSchedulePolicyObject() !== FALSE and $s_obj->getSchedulePolicyObject()->getMealPolicyObject() !== FALSE and $s_obj->getSchedulePolicyObject()->getMealPolicyObject()->getType() != 10) {
                                     Debug::text('Found Schedule Meal Policy... Trigger Time: ' . $s_obj->getSchedulePolicyObject()->getMealPolicyObject()->getTriggerTime(), __FILE__, __LINE__, __METHOD__, 10);
                                     $meal_policy_obj = $s_obj->getSchedulePolicyObject()->getMealPolicyObject();
                                 } else {
                                     Debug::text('Schedule Meal Policy does not exist, or is auto-deduct?', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         } else {
                             Debug::text('No Schedule Found...', __FILE__, __LINE__, __METHOD__, 10);
                             //Check if they have a meal policy, with no schedule.
                             $mplf = TTnew('MealPolicyListFactory');
                             $mplf->getByPolicyGroupUserId($user_date_obj->getUser());
                             if ($mplf->getRecordCount() > 0) {
                                 foreach ($mplf as $mp_obj) {
                                     if ($mp_obj->getType() != 10) {
                                         Debug::text('Found UnScheduled meal Policy... Trigger Time: ' . $mp_obj->getTriggerTime(), __FILE__, __LINE__, __METHOD__, 10);
                                         $meal_policy_obj = $mp_obj;
                                     }
                                 }
                                 unset($mplf, $mp_obj);
                             } else {
                                 //There is no  meal policy or schedule policy with a meal policy assigned to it
                                 //With out this we could still apply No meal exceptions, but they will happen even on
                                 //a 2minute shift.
                                 Debug::text('No Lunch policy, applying No meal exception.', __FILE__, __LINE__, __METHOD__, 10);
                                 $meal_policy_obj = TRUE;
                             }
                         }
                         if (is_object($meal_policy_obj) or $meal_policy_obj === TRUE) {
                             $punch_control_id = FALSE;
                             $daily_total_time = 0;
                             $udtlf = TTnew('UserDateTotalListFactory');
                             $udtlf->getByUserDateIdAndStatus($user_date_id, 20);
                             if ($udtlf->getRecordCount() > 0) {
                                 foreach ($udtlf as $udt_obj) {
                                     $daily_total_time += $udt_obj->getTotalTime();
                                     $punch_control_total_time[$udt_obj->getPunchControlID()] = $udt_obj->getTotalTime();
                                 }
                             }
                             Debug::text('Day Total Time: ' . $daily_total_time, __FILE__, __LINE__, __METHOD__, 10);
                             //Debug::Arr($punch_control_total_time, 'Punch Control Total Time: ', __FILE__, __LINE__, __METHOD__,10);
                             if ($daily_total_time > 0 and ($meal_policy_obj === TRUE or $daily_total_time > $meal_policy_obj->getTriggerTime())) {
                                 //Check for meal punch.
                                 $meal_punch = FALSE;
                                 $tmp_punch_total_time = 0;
                                 $tmp_punch_control_ids = array();
                                 foreach ($plf as $p_obj) {
                                     if ($p_obj->getType() == 20) {
                                         //20 = Lunch
                                         Debug::text('Found meal Punch: ' . $p_obj->getTimeStamp(), __FILE__, __LINE__, __METHOD__, 10);
                                         $meal_punch = TRUE;
                                         break;
                                     }
                                     if (isset($punch_control_total_time[$p_obj->getPunchControlID()]) and !isset($tmp_punch_control_ids[$p_obj->getPunchControlID()])) {
                                         $tmp_punch_total_time += $punch_control_total_time[$p_obj->getPunchControlID()];
                                         if ($punch_control_id === FALSE and ($meal_policy_obj === TRUE or $tmp_punch_total_time > $meal_policy_obj->getTriggerTime())) {
                                             Debug::text('Found punch control for exception: ' . $p_obj->getPunchControlID() . ' Total Time: ' . $tmp_punch_total_time, __FILE__, __LINE__, __METHOD__, 10);
                                             $punch_control_id = $p_obj->getPunchControlID();
                                             //Don't meal the loop here, as we have to continue on and check for other meals.
                                         }
                                     }
                                     $tmp_punch_control_ids[$p_obj->getPunchControlID()] = TRUE;
                                 }
                                 unset($tmp_punch_total_time, $tmp_punch_control_ids);
                                 if ($meal_punch == FALSE) {
                                     Debug::text('Triggering No Lunch exception!', __FILE__, __LINE__, __METHOD__, 10);
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $punch_control_id);
                                 }
                             }
                         }
                     }
                     break;
                 case 'b1':
                     //Long Break
                 //Long Break
                 case 'b2':
                     //Short Break
                     if ($plf->getRecordCount() > 0) {
                         //Get all break punches.
                         $pair = 0;
                         $x = 0;
                         $out_for_break = FALSE;
                         foreach ($plf as $p_obj) {
                             if ($p_obj->getStatus() == 20 and $p_obj->getType() == 30) {
                                 $break_out_timestamp = $p_obj->getTimeStamp();
                                 $break_punch_arr[$pair]['punch_id'] = $p_obj->getId();
                                 $out_for_break = TRUE;
                             } elseif ($out_for_break == TRUE and $p_obj->getStatus() == 10 and $p_obj->getType() == 30) {
                                 $break_punch_arr[$pair][20] = $break_out_timestamp;
                                 $break_punch_arr[$pair][10] = $p_obj->getTimeStamp();
                                 $out_for_break = FALSE;
                                 $pair++;
                                 unset($break_out_timestamp);
                             } else {
                                 $out_for_break = FALSE;
                             }
                         }
                         unset($pair);
                         if (isset($break_punch_arr)) {
                             //Debug::Arr($break_punch_arr, 'Break Punch Array: ', __FILE__, __LINE__, __METHOD__,10);
                             foreach ($break_punch_arr as $pair => $time_stamp_arr) {
                                 if (isset($time_stamp_arr[10]) and isset($time_stamp_arr[20])) {
                                     $break_total_time = bcsub($time_stamp_arr[10], $time_stamp_arr[20]);
                                     Debug::text(' Break Total Time: ' . $break_total_time, __FILE__, __LINE__, __METHOD__, 10);
                                     if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                         $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                     }
                                     //Check to see if they have a schedule policy
                                     $bplf = TTnew('BreakPolicyListFactory');
                                     if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == TRUE and is_object($p_obj->getScheduleObject()) == TRUE and is_object($p_obj->getScheduleObject()->getSchedulePolicyObject()) == TRUE) {
                                         $break_policy_ids = $p_obj->getScheduleObject()->getSchedulePolicyObject()->getBreakPolicy();
                                         $bplf->getByIdAndCompanyId($break_policy_ids, $user_date_obj->getUserObject()->getCompany());
                                     } else {
                                         $bplf->getByPolicyGroupUserId($user_date_obj->getUser());
                                     }
                                     unset($break_policy_ids);
                                     if ($bplf->getRecordCount() > 0) {
                                         Debug::text('Found Break Policy(ies) to apply: ' . $bplf->getRecordCount() . ' Pair: ' . $pair, __FILE__, __LINE__, __METHOD__, 10);
                                         foreach ($bplf as $bp_obj) {
                                             $bp_objs[] = $bp_obj;
                                         }
                                         unset($bplf, $bp_obj);
                                         if (isset($bp_objs[$pair]) and is_object($bp_objs[$pair])) {
                                             $bp_obj = $bp_objs[$pair];
                                             $break_policy_break_time = $bp_obj->getAmount();
                                             Debug::text('Break Policy Time: ' . $break_policy_break_time . ' ID: ' . $bp_obj->getID(), __FILE__, __LINE__, __METHOD__, 10);
                                             $add_exception = FALSE;
                                             if (strtolower($ep_obj->getType()) == 'b1' and $break_policy_break_time > 0 and $break_total_time > 0 and $break_total_time > $break_policy_break_time + $ep_obj->getGrace()) {
                                                 $add_exception = TRUE;
                                             } elseif (strtolower($ep_obj->getType()) == 'b2' and $break_policy_break_time > 0 and $break_total_time > 0 and $break_total_time < $break_policy_break_time - $ep_obj->getGrace()) {
                                                 $add_exception = TRUE;
                                             }
                                             if ($add_exception == TRUE) {
                                                 Debug::text('Adding Exception! ' . $ep_obj->getType(), __FILE__, __LINE__, __METHOD__, 10);
                                                 if (isset($time_stamp_arr['punch_id'])) {
                                                     $punch_id = $time_stamp_arr['punch_id'];
                                                 } else {
                                                     $punch_id = FALSE;
                                                 }
                                                 $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $punch_id, 'punch_control_id' => FALSE);
                                                 unset($punch_id);
                                             } else {
                                                 Debug::text('Not Adding Exception!', __FILE__, __LINE__, __METHOD__, 10);
                                             }
                                             unset($bp_obj);
                                         }
                                         unset($bp_objs);
                                     }
                                 } else {
                                     Debug::text(' Break Punches not paired... Skipping!', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         } else {
                             Debug::text(' No Break Punches found, or none are paired.', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     }
                     break;
                 case 'b3':
                     //Too Many Breaks
                 //Too Many Breaks
                 case 'b4':
                     //Too Few Breaks
                     if ($plf->getRecordCount() > 0) {
                         //Get all break punches.
                         $pair = 0;
                         $x = 0;
                         $out_for_break = FALSE;
                         foreach ($plf as $p_obj) {
                             if ($p_obj->getStatus() == 20 and $p_obj->getType() == 30) {
                                 $break_out_timestamp = $p_obj->getTimeStamp();
                                 $break_punch_arr[$pair]['punch_id'] = $p_obj->getId();
                                 $out_for_break = TRUE;
                             } elseif ($out_for_break == TRUE and $p_obj->getStatus() == 10 and $p_obj->getType() == 30) {
                                 $break_punch_arr[$pair][20] = $break_out_timestamp;
                                 $break_punch_arr[$pair][10] = $p_obj->getTimeStamp();
                                 $out_for_break = FALSE;
                                 $pair++;
                                 unset($break_out_timestamp);
                             } else {
                                 $out_for_break = FALSE;
                             }
                         }
                         unset($pair);
                         //Get daily total time.
                         $daily_total_time = 0;
                         $udtlf = TTnew('UserDateTotalListFactory');
                         //$udtlf->getByUserDateIdAndStatusAndType( $user_date_id, 10, 10 );
                         $udtlf->getByUserDateId($user_date_id);
                         if ($udtlf->getRecordCount() > 0) {
                             foreach ($udtlf as $udt_obj) {
                                 if ($udt_obj->getTimeCategory() == 'worked_time') {
                                     $daily_total_time += $udt_obj->getTotalTime();
                                 }
                             }
                         }
                         Debug::text(' Daily Total Time: ' . $daily_total_time . ' User Date ID: ' . $user_date_id, __FILE__, __LINE__, __METHOD__, 10);
                         //Make sure we take into account how long they have currently worked, so we don't
                         //say too few breaks for 3hr shift that they employee took one break on.
                         //Trigger this exception if the employee doesn't take a break at all?
                         if (isset($break_punch_arr)) {
                             $total_breaks = count($break_punch_arr);
                             //Debug::Arr($break_punch_arr, 'Break Punch Array: ', __FILE__, __LINE__, __METHOD__,10);
                             foreach ($break_punch_arr as $pair => $time_stamp_arr) {
                                 if (isset($time_stamp_arr[10]) and isset($time_stamp_arr[20])) {
                                     $break_total_time = bcsub($time_stamp_arr[10], $time_stamp_arr[20]);
                                     Debug::text(' Break Total Time: ' . $break_total_time, __FILE__, __LINE__, __METHOD__, 10);
                                     if (!isset($scheduled_id_cache[$p_obj->getID()])) {
                                         $scheduled_id_cache[$p_obj->getID()] = $p_obj->findScheduleID(NULL, $user_date_obj->getUser());
                                     }
                                     //Check to see if they have a schedule policy
                                     $bplf = TTnew('BreakPolicyListFactory');
                                     if ($p_obj->setScheduleID($scheduled_id_cache[$p_obj->getID()]) == TRUE and is_object($p_obj->getScheduleObject()) == TRUE and is_object($p_obj->getScheduleObject()->getSchedulePolicyObject()) == TRUE) {
                                         $break_policy_ids = $p_obj->getScheduleObject()->getSchedulePolicyObject()->getBreakPolicy();
                                         $bplf->getByIdAndCompanyId($break_policy_ids, $user_date_obj->getUserObject()->getCompany());
                                     } else {
                                         //$bplf->getByPolicyGroupUserId( $user_date_obj->getUser() );
                                         $bplf->getByPolicyGroupUserIdAndDayTotalTime($user_date_obj->getUser(), $daily_total_time);
                                     }
                                     unset($break_policy_ids);
                                     $allowed_breaks = $bplf->getRecordCount();
                                     $add_exception = FALSE;
                                     if (strtolower($ep_obj->getType()) == 'b3' and $total_breaks > $allowed_breaks) {
                                         Debug::text(' Too many breaks taken...', __FILE__, __LINE__, __METHOD__, 10);
                                         $add_exception = TRUE;
                                     } elseif (strtolower($ep_obj->getType()) == 'b4' and $total_breaks < $allowed_breaks) {
                                         Debug::text(' Too few breaks taken...', __FILE__, __LINE__, __METHOD__, 10);
                                         $add_exception = TRUE;
                                     } else {
                                         Debug::text(' Proper number of breaks taken...', __FILE__, __LINE__, __METHOD__, 10);
                                     }
                                     if ($add_exception == TRUE and (strtolower($ep_obj->getType()) == 'b4' or strtolower($ep_obj->getType()) == 'b3' and $pair > $allowed_breaks - 1)) {
                                         Debug::text('Adding Exception! ' . $ep_obj->getType(), __FILE__, __LINE__, __METHOD__, 10);
                                         if (isset($time_stamp_arr['punch_id']) and strtolower($ep_obj->getType()) == 'b3') {
                                             $punch_id = $time_stamp_arr['punch_id'];
                                         } else {
                                             $punch_id = FALSE;
                                         }
                                         $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $punch_id, 'punch_control_id' => FALSE);
                                         unset($punch_id);
                                     } else {
                                         Debug::text('Not Adding Exception!', __FILE__, __LINE__, __METHOD__, 10);
                                     }
                                 }
                             }
                         }
                     }
                     break;
                 case 'b5':
                     //No Break
                     if ($plf->getRecordCount() > 0) {
                         //If they are scheduled or not, we can check for a break policy and base our
                         //decision off that. We don't want a No Break exception on a 3hr short shift though.
                         //Also ignore this exception if the break is auto-deduct.
                         //**Try to assign this exception to a specific punch control id, so we can do searches based on punch branch.
                         //Find break policy
                         //Use scheduled break policy first.
                         $break_policy_obj = NULL;
                         if ($slf->getRecordCount() > 0) {
                             Debug::text('Schedule Found...', __FILE__, __LINE__, __METHOD__, 10);
                             foreach ($slf as $s_obj) {
                                 if ($s_obj->getSchedulePolicyObject() !== FALSE) {
                                     $break_policy_ids = $s_obj->getSchedulePolicyObject()->getBreakPolicy();
                                     if (is_array($break_policy_ids)) {
                                         $bplf = TTNew('BreakPolicyListFactory');
                                         $bplf->getByIdAndCompanyId($break_policy_ids, $user_date_obj->getUserObject()->getCompany());
                                         if ($bplf->getRecordCount() > 0) {
                                             foreach ($bplf as $bp_obj) {
                                                 if ($bp_obj->getType() != 10) {
                                                     $break_policy_obj = $bp_obj;
                                                     break;
                                                 }
                                             }
                                         }
                                     }
                                 }
                                 unset($s_obj, $break_policy_ids, $bplf, $bp_obj);
                             }
                         } else {
                             Debug::text('No Schedule Found...', __FILE__, __LINE__, __METHOD__, 10);
                             //Check if they have a break policy, with no schedule.
                             $bplf = TTnew('BreakPolicyListFactory');
                             $bplf->getByPolicyGroupUserId($user_date_obj->getUser());
                             if ($bplf->getRecordCount() > 0) {
                                 Debug::text('Found UnScheduled Break Policy...', __FILE__, __LINE__, __METHOD__, 10);
                                 foreach ($bplf as $bp_obj) {
                                     if ($bp_obj->getType() != 10) {
                                         $break_policy_obj = $bp_obj;
                                         break;
                                     }
                                 }
                                 unset($bplf, $bp_obj);
                             } else {
                                 //There is no  break policy or schedule policy with a break policy assigned to it
                                 //With out this we could still apply No Break exceptions, but they will happen even on
                                 //a 2minute shift.
                                 Debug::text('No break policy, applying No break exception.', __FILE__, __LINE__, __METHOD__, 10);
                                 $break_policy_obj = TRUE;
                             }
                         }
                         if (is_object($break_policy_obj) or $break_policy_obj === TRUE) {
                             $punch_control_id = FALSE;
                             $daily_total_time = 0;
                             $udtlf = TTnew('UserDateTotalListFactory');
                             $udtlf->getByUserDateIdAndStatus($user_date_id, 20);
                             if ($udtlf->getRecordCount() > 0) {
                                 foreach ($udtlf as $udt_obj) {
                                     $daily_total_time += $udt_obj->getTotalTime();
                                     $punch_control_total_time[$udt_obj->getPunchControlID()] = $udt_obj->getTotalTime();
                                 }
                             }
                             Debug::text('Day Total Time: ' . $daily_total_time, __FILE__, __LINE__, __METHOD__, 10);
                             //Debug::Arr($punch_control_total_time, 'Punch Control Total Time: ', __FILE__, __LINE__, __METHOD__,10);
                             if ($daily_total_time > 0 and ($break_policy_obj === TRUE or $daily_total_time > $break_policy_obj->getTriggerTime())) {
                                 //Check for break punch.
                                 $break_punch = FALSE;
                                 $tmp_punch_total_time = 0;
                                 $tmp_punch_control_ids = array();
                                 foreach ($plf as $p_obj) {
                                     if ($p_obj->getType() == 30) {
                                         //30 = Break
                                         Debug::text('Found break Punch: ' . $p_obj->getTimeStamp(), __FILE__, __LINE__, __METHOD__, 10);
                                         $break_punch = TRUE;
                                         break;
                                     }
                                     if (isset($punch_control_total_time[$p_obj->getPunchControlID()]) and !isset($tmp_punch_control_ids[$p_obj->getPunchControlID()])) {
                                         $tmp_punch_total_time += $punch_control_total_time[$p_obj->getPunchControlID()];
                                         if ($punch_control_id === FALSE and ($break_policy_obj === TRUE or $tmp_punch_total_time > $break_policy_obj->getTriggerTime())) {
                                             Debug::text('Found punch control for exception: ' . $p_obj->getPunchControlID(), __FILE__, __LINE__, __METHOD__, 10);
                                             $punch_control_id = $p_obj->getPunchControlID();
                                             //Don't break the loop here, as we have to continue on and check for other breaks.
                                         }
                                     }
                                     $tmp_punch_control_ids[$p_obj->getPunchControlID()] = TRUE;
                                 }
                                 unset($tmp_punch_total_time, $tmp_punch_control_ids);
                                 if ($break_punch == FALSE) {
                                     Debug::text('Triggering No Break exception!', __FILE__, __LINE__, __METHOD__, 10);
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $punch_control_id);
                                 }
                             }
                         }
                     }
                     break;
                 case 'v1':
                     //TimeSheet Not Verified
                     //Get pay period schedule data, determine if timesheet verification is even enabled.
                     if (is_object($user_date_obj->getPayPeriodObject()) and is_object($user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject()) and $user_date_obj->getPayPeriodObject()->getPayPeriodScheduleObject()->getTimeSheetVerifyType() > 10) {
                         Debug::text('Verification enabled... Window Start: ' . TTDate::getDate('DATE+TIME', $user_date_obj->getPayPeriodObject()->getTimeSheetVerifyWindowStartDate()) . ' Grace Time: ' . $ep_obj->getGrace(), __FILE__, __LINE__, __METHOD__, 10);
                         //*Only* trigger this exception on the last day of the pay period, because when the pay period is verified it has to force the last day to be recalculated.
                         //Ignore timesheets without any time, (worked and absence). Or we could use the Watch Window to specify the minimum time required on
                         //a timesheet to trigger this instead?
                         //Make sure we are after the timesheet window start date + the grace period.
                         if ($user_date_obj->getPayPeriodObject()->getStatus() != 50 and $current_epoch >= $user_date_obj->getPayPeriodObject()->getTimeSheetVerifyWindowStartDate() + $ep_obj->getGrace() and TTDate::getBeginDayEpoch($user_date_obj->getDateStamp()) == TTDate::getBeginDayEpoch($user_date_obj->getPayPeriodObject()->getEndDate())) {
                             //Get pay period total time, include worked and paid absence time.
                             $udtlf = TTnew('UserDateTotalListFactory');
                             $total_time = $udtlf->getTimeSumByUserIDAndPayPeriodId($user_date_obj->getUser(), $user_date_obj->getPayPeriodObject()->getID());
                             if ($total_time > 0) {
                                 //Check to see if pay period has been verified or not yet.
                                 $pptsvlf = TTnew('PayPeriodTimeSheetVerifyListFactory');
                                 $pptsvlf->getByPayPeriodIdAndUserId($user_date_obj->getPayPeriodObject()->getId(), $user_date_obj->getUser());
                                 $pay_period_verified = FALSE;
                                 if ($pptsvlf->getRecordCount() > 0) {
                                     $pay_period_verified = $pptsvlf->getCurrent()->getAuthorized();
                                 }
                                 if ($pay_period_verified == FALSE) {
                                     //Always allow for emailing this exception because it can be triggered after a punch is modified and
                                     //any supervisor would need to be notified to verify the timesheet again.
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => FALSE, 'enable_email_notification' => TRUE);
                                 } else {
                                     Debug::text('TimeSheet has already been authorized!', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             } else {
                                 Debug::text('Timesheet does not have any worked or paid absence time...', __FILE__, __LINE__, __METHOD__, 10);
                             }
                             unset($udtlf, $total_time);
                         } else {
                             Debug::text('Not within timesheet verification window, or not after grace time.', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     } else {
                         Debug::text('No Pay Period Schedule or TimeSheet Verificiation disabled...', __FILE__, __LINE__, __METHOD__, 10);
                     }
                     break;
                 case 'j1':
                     //Not Allowed on Job
                     if (getTTProductEdition() >= TT_PRODUCT_CORPORATE and $plf->getRecordCount() > 0) {
                         foreach ($plf as $p_obj) {
                             if ($p_obj->getStatus() == 10) {
                                 //In punches
                                 if (is_object($p_obj->getPunchControlObject()) and $p_obj->getPunchControlObject()->getJob() > 0) {
                                     //Found job punch, check job settings.
                                     $jlf = TTnew('JobListFactory');
                                     $jlf->getById($p_obj->getPunchControlObject()->getJob());
                                     if ($jlf->getRecordCount() > 0) {
                                         $j_obj = $jlf->getCurrent();
                                         if ($j_obj->isAllowedUser($user_date_obj->getUser()) == FALSE) {
                                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $p_obj->getPunchControlId());
                                         } else {
                                             Debug::text('    User allowed on Job!', __FILE__, __LINE__, __METHOD__, 10);
                                         }
                                     } else {
                                         Debug::text('    Job not found!', __FILE__, __LINE__, __METHOD__, 10);
                                     }
                                 } else {
                                     //Debug::text('    Not a Job Punch...', __FILE__, __LINE__, __METHOD__,10);
                                 }
                             }
                         }
                         unset($j_obj);
                     }
                     break;
                 case 'j2':
                     //Not Allowed on Task
                     if (getTTProductEdition() >= TT_PRODUCT_CORPORATE and $plf->getRecordCount() > 0) {
                         foreach ($plf as $p_obj) {
                             if ($p_obj->getStatus() == 10) {
                                 //In punches
                                 if (is_object($p_obj->getPunchControlObject()) and $p_obj->getPunchControlObject()->getJob() > 0 and $p_obj->getPunchControlObject()->getJobItem() > 0) {
                                     //Found job punch, check job settings.
                                     $jlf = TTnew('JobListFactory');
                                     $jlf->getById($p_obj->getPunchControlObject()->getJob());
                                     if ($jlf->getRecordCount() > 0) {
                                         $j_obj = $jlf->getCurrent();
                                         if ($j_obj->isAllowedItem($p_obj->getPunchControlObject()->getJobItem()) == FALSE) {
                                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $p_obj->getPunchControlId());
                                         } else {
                                             Debug::text('    Job item allowed on job!', __FILE__, __LINE__, __METHOD__, 10);
                                         }
                                     } else {
                                         Debug::text('    Job not found!', __FILE__, __LINE__, __METHOD__, 10);
                                     }
                                 } else {
                                     //Debug::text('    Not a Job Punch...', __FILE__, __LINE__, __METHOD__,10);
                                 }
                             }
                         }
                         unset($j_obj);
                     }
                     break;
                 case 'j3':
                     //Job already completed
                     if (getTTProductEdition() >= TT_PRODUCT_CORPORATE and $plf->getRecordCount() > 0) {
                         foreach ($plf as $p_obj) {
                             if ($p_obj->getStatus() == 10) {
                                 //In punches
                                 if (is_object($p_obj->getPunchControlObject()) and $p_obj->getPunchControlObject()->getJob() > 0) {
                                     //Found job punch, check job settings.
                                     $jlf = TTnew('JobListFactory');
                                     $jlf->getById($p_obj->getPunchControlObject()->getJob());
                                     if ($jlf->getRecordCount() > 0) {
                                         $j_obj = $jlf->getCurrent();
                                         //Status is completed and the User Date Stamp is greater then the job end date.
                                         //If no end date is set, ignore this.
                                         if ($j_obj->getStatus() == 30 and $j_obj->getEndDate() != FALSE and $user_date_obj->getDateStamp() > $j_obj->getEndDate()) {
                                             $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => FALSE, 'punch_control_id' => $p_obj->getPunchControlId());
                                         } else {
                                             Debug::text('    Job Not Completed!', __FILE__, __LINE__, __METHOD__, 10);
                                         }
                                     } else {
                                         Debug::text('    Job not found!', __FILE__, __LINE__, __METHOD__, 10);
                                     }
                                 } else {
                                     Debug::text('    Not a Job Punch...', __FILE__, __LINE__, __METHOD__, 10);
                                 }
                             }
                         }
                         unset($j_obj);
                     }
                     break;
                 case 'j4':
                     //No Job or Task
                     if (getTTProductEdition() >= TT_PRODUCT_CORPORATE and $plf->getRecordCount() > 0) {
                         foreach ($plf as $p_obj) {
                             $add_exception = FALSE;
                             //In punches only
                             if ($p_obj->getStatus() == 10 and is_object($p_obj->getPunchControlObject())) {
                                 //If no Tasks are setup, ignore checking them.
                                 if ($p_obj->getPunchControlObject()->getJob() == '' or $p_obj->getPunchControlObject()->getJob() == 0 or $p_obj->getPunchControlObject()->getJob() == FALSE) {
                                     $add_exception = TRUE;
                                 }
                                 if ($p_obj->getPunchControlObject()->getJobItem() == '' or $p_obj->getPunchControlObject()->getJobItem() == 0 or $p_obj->getPunchControlObject()->getJobItem() == FALSE) {
                                     //Make sure at least one task exists before triggering exception.
                                     $jilf = TTNew('JobItemListFactory');
                                     $jilf->getByCompanyID($user_date_obj->getUserObject()->getCompany(), 1);
                                     //Limit to just 1 record.
                                     if ($jilf->getRecordCount() > 0) {
                                         $add_exception = TRUE;
                                     }
                                 }
                                 if ($add_exception === TRUE) {
                                     $current_exceptions[] = array('user_date_id' => $user_date_id, 'exception_policy_id' => $ep_obj->getId(), 'type_id' => $type_id, 'punch_id' => $p_obj->getId(), 'punch_control_id' => $p_obj->getPunchControlId());
                                 }
                             }
                         }
                     }
                     break;
                 default:
                     Debug::text('BAD, should never get here: ', __FILE__, __LINE__, __METHOD__, 10);
                     break;
             }
         }
     }
     unset($ep_obj);
     $exceptions = self::diffExistingAndCurrentExceptions($existing_exceptions, $current_exceptions);
     if (is_array($exceptions)) {
         if (isset($exceptions['create_exceptions']) and is_array($exceptions['create_exceptions']) and count($exceptions['create_exceptions']) > 0) {
             Debug::text('Creating new exceptions... Total: ' . count($exceptions['create_exceptions']), __FILE__, __LINE__, __METHOD__, 10);
             foreach ($exceptions['create_exceptions'] as $tmp_exception) {
                 $ef = TTnew('ExceptionFactory');
                 $ef->setUserDateID($tmp_exception['user_date_id']);
                 $ef->setExceptionPolicyID($tmp_exception['exception_policy_id']);
                 $ef->setType($tmp_exception['type_id']);
                 if (isset($tmp_exception['punch_control_id']) and $tmp_exception['punch_control_id'] != '') {
                     $ef->setPunchControlId($tmp_exception['punch_control_id']);
                 }
                 if (isset($tmp_exception['punch_id']) and $tmp_exception['punch_id'] != '') {
                     $ef->setPunchId($tmp_exception['punch_id']);
                 }
                 $ef->setEnableDemerits(TRUE);
                 if ($ef->isValid()) {
                     $ef->Save(FALSE);
                     //Save exception prior to emailing it, otherwise we can't save audit logs.
                     if ($enable_premature_exceptions == TRUE or isset($tmp_exception['enable_email_notification']) and $tmp_exception['enable_email_notification'] == TRUE) {
                         $eplf = TTnew('ExceptionPolicyListFactory');
                         $eplf->getById($tmp_exception['exception_policy_id']);
                         if ($eplf->getRecordCount() == 1) {
                             $ep_obj = $eplf->getCurrent();
                             $ef->emailException($user_date_obj->getUserObject(), $user_date_obj, isset($tmp_exception['punch_obj']) ? $tmp_exception['punch_obj'] : NULL, isset($tmp_exception['schedule_obj']) ? $tmp_exception['schedule_obj'] : NULL, $ep_obj);
                         }
                     } else {
                         Debug::text('Not emailing new exception: User Date ID: ' . $tmp_exception['user_date_id'] . ' Type ID: ' . $tmp_exception['type_id'] . ' Enable PreMature: ' . (int) $enable_premature_exceptions, __FILE__, __LINE__, __METHOD__, 10);
                     }
                 }
                 unset($ef);
             }
         }
         if (isset($exceptions['delete_exceptions']) and is_array($exceptions['delete_exceptions']) and count($exceptions['delete_exceptions']) > 0) {
             Debug::Text('Deleting no longer valid exceptions... Total: ' . count($exceptions['delete_exceptions']), __FILE__, __LINE__, __METHOD__, 10);
             $ef = TTnew('ExceptionFactory');
             $ef->bulkDelete($exceptions['delete_exceptions']);
         }
     }
     $profiler->stopTimer("ExceptionPolicy::calcExceptions()");
     return TRUE;
 }
Пример #6
0
 function preSave()
 {
     if ($this->isNew()) {
         Debug::text(' Setting Original TimeStamp: ' . $this->getTimeStamp(), __FILE__, __LINE__, __METHOD__, 10);
         $this->setOriginalTimeStamp($this->getTimeStamp());
     }
     if ($this->getDeleted() == FALSE) {
         if ($this->isNew() and $this->getTransfer() == TRUE and $this->getEnableAutoTransfer() == TRUE) {
             Debug::text(' Transfer is Enabled, automatic punch out of last punch pair...', __FILE__, __LINE__, __METHOD__, 10);
             //Use actual time stamp, not rounded timestamp. This should only be called on new punches as well, otherwise Actual Time Stamp could be incorrect.
             $p_obj = $this->getPreviousPunchObject($this->getActualTimeStamp());
             if (is_object($p_obj)) {
                 Debug::text(' Found Last Punch: ', __FILE__, __LINE__, __METHOD__, 10);
                 if ($p_obj->getStatus() == 10) {
                     Debug::text(' Last Punch was in. Auto Punch Out now: ', __FILE__, __LINE__, __METHOD__, 10);
                     //Make sure the current punch status is IN
                     $this->setStatus(10);
                     //In
                     $this->setType(10);
                     //Normal (can't transfer in/out of lunches?)
                     $pf = TTnew('PunchFactory');
                     $pf->setUser($this->getUser());
                     $pf->setEnableAutoTransfer(FALSE);
                     $pf->setPunchControlID($p_obj->getPunchControlID());
                     $pf->setTransfer(TRUE);
                     $pf->setType($p_obj->getNextType());
                     $pf->setStatus(20);
                     //Out
                     $pf->setTimeStamp($this->getTimeStamp(), FALSE);
                     //Disable rounding.
                     $pf->setActualTimeStamp($this->getTimeStamp());
                     //$pf->setOriginalTimeStamp( $this->getTimeStamp() ); //set in preSave()
                     $pf->setLongitude($this->getLongitude());
                     $pf->setLatitude($this->getLatitude());
                     if ($pf->isValid()) {
                         if ($pf->Save(FALSE) == TRUE) {
                             $p_obj->getPunchControlObject()->setEnableCalcTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcSystemTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcUserDateTotal(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcException(TRUE);
                             $p_obj->getPunchControlObject()->setEnablePreMatureException(TRUE);
                             if ($p_obj->getPunchControlObject()->isValid()) {
                                 $p_obj->getPunchControlObject()->Save();
                             } else {
                                 Debug::text(' aError saving auto out punch...', __FILE__, __LINE__, __METHOD__, 10);
                             }
                         } else {
                             Debug::text(' bError saving auto out punch...', __FILE__, __LINE__, __METHOD__, 10);
                         }
                     } else {
                         Debug::text(' cError saving auto out punch...', __FILE__, __LINE__, __METHOD__, 10);
                     }
                 } else {
                     Debug::text(' Last Punch was out. No Auto Punch out needed, removing transfer flag from this punch...', __FILE__, __LINE__, __METHOD__, 10);
                     $this->setTransfer(FALSE);
                 }
             }
             unset($p_obj, $pf);
         }
         //Split punch at midnight.
         //This has to be an Out punch, and the previous punch has to be an in punch in order for the split to occur.
         //Check to make sure there is an open punch pair.
         //Make sure this punch isn't right at midnight either, as no point in splitting a punch at that time.
         //FIXME: What happens if a supervisor edits a 11:30PM punch and makes it 5:00AM the next day?
         //		We can't split punches when editing existing punches, because we have to split punch_control_ids prior to saving etc...
         //		But we can split when supervisors are adding new punches.
         //Debug::text('Split at Midnight Enabled: '. $this->getEnableSplitAtMidnight() .' IsNew: '. $this->isNew() .' Status: '. $this->getStatus() .' TimeStamp: '. $this->getTimeStamp() .' Punch Control ID: '. $this->getPunchControlID(), __FILE__, __LINE__, __METHOD__,10);
         if ($this->isNew() == TRUE and $this->getStatus() == 20 and $this->getEnableSplitAtMidnight() == TRUE and $this->getTimeStamp() != TTDate::getBeginDayEpoch($this->getTimeStamp()) and (is_object($this->getPunchControlObject()) and is_object($this->getPunchControlObject()->getPayPeriodScheduleObject()) and $this->getPunchControlObject()->getPayPeriodScheduleObject()->getShiftAssignedDay() == 40)) {
             $plf = TTnew('PunchListFactory');
             $plf->getPreviousPunchByUserIdAndEpoch($this->getUser(), $this->getTimeStamp());
             if ($plf->getRecordCount() > 0) {
                 $p_obj = $plf->getCurrent();
                 Debug::text(' Found Last Punch... ID: ' . $p_obj->getId() . ' Timestamp: ' . $p_obj->getTimeStamp(), __FILE__, __LINE__, __METHOD__, 10);
                 if ($p_obj->getStatus() == 10 and TTDate::doesRangeSpanMidnight($this->getTimeStamp(), $p_obj->getTimeStamp())) {
                     Debug::text(' Last Punch was in and this is an out punch that spans midnight. Split Punch at midnight now: ', __FILE__, __LINE__, __METHOD__, 10);
                     //FIXME: This will fail if a shift spans multiple days!
                     //Make sure the current punch status is OUT
                     //But we can split LUNCH/Break punches, because someone could punch in at 8PM, then out for lunch at 1:00AM, this would need to be split.
                     $this->setStatus(20);
                     //Out
                     //Reduce the out punch by 60 seconds, and increase the current punch by 60seconds so no time is lost.
                     $this->setTimeStamp($this->getTimeStamp());
                     //FIXME: May need to use ActualTimeStamp here so we aren't double rounding.
                     //Get new punch control ID for the midnight punch and this one.
                     $new_punch_control_id = $this->getPunchControlObject()->getNextInsertId();
                     //Since we need to change the PunchControlID, copy the current punch_control object to work around getGenericObject() checking the
                     //IDs and not returning the object anymore.
                     $tmp_punch_control_obj = $this->getPunchControlObject();
                     $this->setPunchControlID($new_punch_control_id);
                     Debug::text(' Split Punch: Punching out just before midnight yesterday...', __FILE__, __LINE__, __METHOD__, 10);
                     //
                     //Punch out just before midnight
                     //The issue with this is that if rounding is enabled this will ignore it, and the shift for this day may total: 3.98hrs.
                     //when they want it to total 4.00hrs. Why don't we split shifts at exactly midnight with no gap at all?
                     //Split shifts right at midnight causes additional issues when editing those punches, TimeTrex will want to combine them on the same day again.
                     $pf = TTnew('PunchFactory');
                     $pf->setUser($this->getUser());
                     $pf->setEnableSplitAtMidnight(FALSE);
                     $pf->setTransfer(FALSE);
                     $pf->setEnableAutoTransfer(FALSE);
                     $pf->setType(10);
                     //Normal
                     $pf->setStatus(20);
                     //Out
                     //We used to have to make this punch 60seconds before midnight, but getShiftData() was modified to support punch at exactly midnight.
                     $before_midnight_timestamp = TTDate::getBeginDayEpoch($this->getTimeStamp());
                     $pf->setTimeStamp($before_midnight_timestamp, FALSE);
                     //Disable rounding.
                     $pf->setActualTimeStamp($before_midnight_timestamp);
                     //$pf->setOriginalTimeStamp( $before_midnight_timestamp ); //set in preSave()
                     $pf->setPunchControlID($p_obj->getPunchControlID());
                     if ($pf->isValid()) {
                         if ($pf->Save(FALSE) == TRUE) {
                             $p_obj->getPunchControlObject()->setEnableCalcTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcSystemTotalTime(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcUserDateTotal(TRUE);
                             $p_obj->getPunchControlObject()->setEnableCalcException(TRUE);
                             $p_obj->getPunchControlObject()->setEnablePreMatureException(TRUE);
                             if ($p_obj->getPunchControlObject()->isValid()) {
                                 $p_obj->getPunchControlObject()->Save();
                             }
                         }
                     }
                     unset($pf, $p_obj, $before_midnight_timestamp);
                     Debug::text(' Split Punch: Punching int at midnight today...', __FILE__, __LINE__, __METHOD__, 10);
                     //
                     //Punch in again right at midnight.
                     //
                     $pf = TTnew('PunchFactory');
                     $pf->setUser($this->getUser());
                     $pf->setEnableSplitAtMidnight(FALSE);
                     $pf->setTransfer(FALSE);
                     $pf->setEnableAutoTransfer(FALSE);
                     $pf->setType(10);
                     //Normal
                     $pf->setStatus(10);
                     //In
                     $at_midnight_timestamp = TTDate::getBeginDayEpoch($this->getTimeStamp());
                     $pf->setTimeStamp($at_midnight_timestamp, FALSE);
                     //Disable rounding.
                     $pf->setActualTimeStamp($at_midnight_timestamp);
                     //$pf->setOriginalTimeStamp( $at_midnight_timestamp ); //set in preSave()
                     $pf->setPunchControlID($new_punch_control_id);
                     if ($pf->isValid()) {
                         if ($pf->Save(FALSE) == TRUE) {
                             $pcf = TTnew('PunchControlFactory');
                             $pcf->setId($pf->getPunchControlID());
                             $pcf->setPunchObject($pf);
                             $pcf->setBranch($tmp_punch_control_obj->getBranch());
                             $pcf->setDepartment($tmp_punch_control_obj->getDepartment());
                             $pcf->setJob($tmp_punch_control_obj->getJob());
                             $pcf->setJobItem($tmp_punch_control_obj->getJobItem());
                             $pcf->setOtherID1($tmp_punch_control_obj->getOtherID1());
                             $pcf->setOtherID2($tmp_punch_control_obj->getOtherID2());
                             $pcf->setOtherID3($tmp_punch_control_obj->getOtherID3());
                             $pcf->setOtherID4($tmp_punch_control_obj->getOtherID4());
                             $pcf->setOtherID5($tmp_punch_control_obj->getOtherID5());
                             $pcf->setEnableStrictJobValidation(TRUE);
                             $pcf->setEnableCalcUserDateID(TRUE);
                             $pcf->setEnableCalcTotalTime(TRUE);
                             $pcf->setEnableCalcSystemTotalTime(TRUE);
                             $pcf->setEnableCalcWeeklySystemTotalTime(TRUE);
                             $pcf->setEnableCalcUserDateTotal(TRUE);
                             $pcf->setEnableCalcException(TRUE);
                             if ($pcf->isValid() == TRUE) {
                                 $pcf->Save(TRUE, TRUE);
                                 //Force isNEW() lookup.
                             }
                         }
                     }
                     unset($pf, $at_midnight_timestamp, $new_punch_control_id, $tmp_punch_control_obj);
                 } else {
                     Debug::text(' Last Punch was out. No Auto Punch ', __FILE__, __LINE__, __METHOD__, 10);
                 }
             }
         }
     }
     return TRUE;
 }