function test_DST() { $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('03-Nov-12 10:00PM'), strtotime('04-Nov-12 1:59AM')), FALSE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('03-Nov-12 10:00PM'), strtotime('04-Nov-12 2:00AM')), TRUE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('03-Nov-12 10:00PM'), strtotime('04-Nov-12 2:01AM')), TRUE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('04-Nov-12 12:30AM'), strtotime('04-Nov-12 1:59AM')), FALSE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('04-Nov-12 12:30AM'), strtotime('04-Nov-12 2:00AM')), TRUE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('04-Nov-12 1:00AM'), strtotime('04-Nov-12 6:30AM')), TRUE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('09-Mar-13 10:00PM'), strtotime('10-Mar-13 1:59AM')), FALSE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('09-Mar-13 10:00PM'), strtotime('10-Mar-13 2:00AM')), TRUE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('09-Mar-13 10:00PM'), strtotime('10-Mar-13 2:01AM')), TRUE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('10-Mar-13 12:30AM'), strtotime('10-Mar-13 1:59AM')), FALSE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('10-Mar-13 12:30AM'), strtotime('10-Mar-13 2:00AM')), TRUE); $this->assertEquals(TTDate::doesRangeSpanDST(strtotime('10-Mar-13 1:30AM'), strtotime('10-Mar-13 6:30AM')), TRUE); $this->assertEquals(TTDate::getDSTOffset(strtotime('03-Nov-12 10:00PM'), strtotime('04-Nov-12 1:59AM')), 0); $this->assertEquals(TTDate::getDSTOffset(strtotime('03-Nov-12 10:00PM'), strtotime('04-Nov-12 2:00AM')), -3600); $this->assertEquals(TTDate::getDSTOffset(strtotime('03-Nov-12 10:00PM'), strtotime('04-Nov-12 2:01AM')), -3600); $this->assertEquals(TTDate::getDSTOffset(strtotime('04-Nov-12 12:30AM'), strtotime('04-Nov-12 1:59AM')), 0); $this->assertEquals(TTDate::getDSTOffset(strtotime('04-Nov-12 12:30AM'), strtotime('04-Nov-12 2:00AM')), -3600); $this->assertEquals(TTDate::getDSTOffset(strtotime('04-Nov-12 1:00AM'), strtotime('04-Nov-12 6:30AM')), -3600); $this->assertEquals(TTDate::getDSTOffset(strtotime('09-Mar-13 10:00PM'), strtotime('10-Mar-13 1:59AM')), 0); $this->assertEquals(TTDate::getDSTOffset(strtotime('09-Mar-13 10:00PM'), strtotime('10-Mar-13 2:00AM')), 3600); $this->assertEquals(TTDate::getDSTOffset(strtotime('09-Mar-13 10:00PM'), strtotime('10-Mar-13 2:01AM')), 3600); $this->assertEquals(TTDate::getDSTOffset(strtotime('10-Mar-13 12:30AM'), strtotime('10-Mar-13 1:59AM')), 0); $this->assertEquals(TTDate::getDSTOffset(strtotime('10-Mar-13 12:30AM'), strtotime('10-Mar-13 2:00AM')), 3600); $this->assertEquals(TTDate::getDSTOffset(strtotime('10-Mar-13 1:30AM'), strtotime('10-Mar-13 6:30AM')), 3600); }
function Validate() { Debug::text('Validating...', __FILE__, __LINE__, __METHOD__, 10); //Call this here so getShiftData can get the correct total time, before we call findUserDate. if ($this->getEnableCalcTotalTime() == TRUE) { $this->calcTotalTime(); } if (is_object($this->getPunchObject())) { $this->findUserDate(); } Debug::text('User Date Id: ' . $this->getUserDateID(), __FILE__, __LINE__, __METHOD__, 10); //Don't check for a valid pay period here, do that in PunchFactory->Validate(), as we need to allow users to delete punches that were created outside pay periods in legacy versions. if ($this->getDeleted() == FALSE and $this->getUserDateObject() == FALSE) { $this->Validator->isTRUE('date_stamp', FALSE, TTi18n::gettext('Date/Time is incorrect, or pay period does not exist for this date. Please create a pay period schedule and assign this employee to it if you have not done so already')); } elseif (is_object($this->getUserDateObject()) and is_object($this->getUserDateObject()->getPayPeriodObject()) and $this->getUserDateObject()->getPayPeriodObject()->getIsLocked() == TRUE) { $this->Validator->isTRUE('date_stamp', FALSE, TTi18n::gettext('Pay Period is Currently Locked')); } //Make sure the user isn't entering punches before the employees hire or after termination date, as its likely they wouldn't have a wage //set for that anyways and wouldn't get paid for it. if ($this->getDeleted() == FALSE and (is_object($this->getPunchObject()) and $this->getPunchObject()->getDeleted() == FALSE) and is_object($this->getUserDateObject()) and is_object($this->getUserDateObject()->getUserObject())) { if ($this->getUserDateObject()->getUserObject()->getHireDate() != '' and TTDate::getBeginDayEpoch($this->getUserDateObject()->getDateStamp()) < TTDate::getBeginDayEpoch($this->getUserDateObject()->getUserObject()->getHireDate())) { $this->Validator->isTRUE('date_stamp', FALSE, TTi18n::gettext('Punch is before employees hire date')); } if ($this->getUserDateObject()->getUserObject()->getTerminationDate() != '' and TTDate::getEndDayEpoch($this->getUserDateObject()->getDateStamp()) > TTDate::getEndDayEpoch($this->getUserDateObject()->getUserObject()->getTerminationDate())) { $this->Validator->isTRUE('date_stamp', FALSE, TTi18n::gettext('Punch is after employees termination date')); } } //Skip these checks if they are deleting a punch. if (is_object($this->getPunchObject()) and $this->getPunchObject()->getDeleted() == FALSE) { $plf = $this->getPLFByPunchControlID(); if ($plf !== NULL and ($this->isNew() and $plf->getRecordCount() == 2 or $plf->getRecordCount() > 2)) { //TTi18n::gettext('Punch Control can not have more than two punches. Please use the Add Punch button instead') //They might be trying to insert a punch inbetween two others? $this->Validator->isTRUE('punch_control', FALSE, TTi18n::gettext('Time conflicts with another punch on this day (c)')); } //Sometimes shift data won't return all the punches to proper check for conflicting punches. //So we need to make sure other punches assigned to this punch_control record are proper. //This fixes the bug of having shifts: 2:00AM Lunch Out, 2:30AM Lunch In, 6:00AM Out, 10:00PM In (in that order), then trying to move the 10PM punch to the open IN slot before the 2AM punch. if ($plf->getRecordCount() > 0) { foreach ($plf as $p_obj) { if ($p_obj->getID() != $this->getPunchObject()->getID()) { if ($this->getPunchObject()->getStatus() == 10 and $p_obj->getStatus() == 10 and $this->getPunchObject()->getTimeStamp() > $p_obj->getTimeStamp()) { //Make sure we match on status==10 for both sides, otherwise this fails to catch the problem case. $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('In punches cannot occur after an out punch, in the same punch pair (a)')); } elseif ($this->getPunchObject()->getStatus() == 20 and $p_obj->getStatus() == 10 and $this->getPunchObject()->getTimeStamp() < $p_obj->getTimeStamp()) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Out punches cannot occur before an in punch, in the same punch pair (a)')); } } } } unset($p_obj); if ($this->Validator->isValid() == TRUE) { //Don't bother checking these resource intensive issues if there are already validation errors. $shift_data = $this->getShiftData(); if (is_array($shift_data) and $this->Validator->hasError('time_stamp') == FALSE) { foreach ($shift_data['punches'] as $punch_data) { //Make sure there aren't two In punches, or two Out punches in the same pair. //This fixes the bug where if you have an In punch, then click the blank cell below it //to add a new punch, but change the status from Out to In instead. if (isset($punches[$punch_data['punch_control_id']][$punch_data['status_id']])) { if ($punch_data['status_id'] == 10) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('In punches cannot occur twice in the same punch pair, you may want to make this an out punch instead')); } else { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Out punches cannot occur twice in the same punch pair, you may want to make this an in punch instead')); } } //Debug::text(' Current Punch Object: ID: '. $this->getPunchObject()->getId() .' TimeStamp: '. $this->getPunchObject()->getTimeStamp() .' Status: '. $this->getPunchObject()->getStatus(), __FILE__, __LINE__, __METHOD__,10); //Debug::text(' Looping Punch Object: ID: '. $punch_data['id'] .' TimeStamp: '. $punch_data['time_stamp'] .' Status: '.$punch_data['status_id'], __FILE__, __LINE__, __METHOD__,10); //Check for another punch that matches the timestamp and status. if ($this->getPunchObject()->getID() != $punch_data['id']) { if ($this->getPunchObject()->getTimeStamp() == $punch_data['time_stamp'] and $this->getPunchObject()->getStatus() == $punch_data['status_id']) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Time and status match that of another punch, this could be due to rounding') . ' (' . TTDate::getDate('DATE+TIME', $punch_data['time_stamp']) . ')'); break; //Break the loop on validation error, so we don't get multiple errors that may be confusing. } } //Check for another punch that matches the timestamp and NOT status in the SAME punch pair. if ($this->getPunchObject()->getID() != $punch_data['id'] and $this->getID() == $punch_data['punch_control_id']) { if ($this->getPunchObject()->getTimeStamp() == $punch_data['time_stamp'] and $this->getPunchObject()->getStatus() != $punch_data['status_id']) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Time matches another punch in the same punch pair, this could be due to rounding') . ' (' . TTDate::getDate('DATE+TIME', $punch_data['time_stamp']) . ')'); break; //Break the loop on validation error, so we don't get multiple errors that may be confusing. } } $punches[$punch_data['punch_control_id']][$punch_data['status_id']] = $punch_data; } unset($punch_data); if (isset($punches[$this->getID()])) { Debug::text('Current Punch ID: ' . $this->getPunchObject()->getId() . ' Punch Control ID: ' . $this->getID() . ' Status: ' . $this->getPunchObject()->getStatus(), __FILE__, __LINE__, __METHOD__, 10); //Debug::Arr($punches, 'Punches Arr: ', __FILE__, __LINE__, __METHOD__,10); if ($this->getPunchObject()->getStatus() == 10 and isset($punches[$this->getID()][20]) and $this->getPunchObject()->getTimeStamp() > $punches[$this->getID()][20]['time_stamp']) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('In punches cannot occur after an out punch, in the same punch pair')); } elseif ($this->getPunchObject()->getStatus() == 20 and isset($punches[$this->getID()][10]) and $this->getPunchObject()->getTimeStamp() < $punches[$this->getID()][10]['time_stamp']) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Out punches cannot occur before an in punch, in the same punch pair')); } else { Debug::text('bPunch does not match any other punch pair.', __FILE__, __LINE__, __METHOD__, 10); $punch_neighbors = Misc::getArrayNeighbors($punches, $this->getID(), 'both'); //Debug::Arr($punch_neighbors, ' Punch Neighbors: ', __FILE__, __LINE__, __METHOD__,10); if (isset($punch_neighbors['next']) and isset($punches[$punch_neighbors['next']])) { Debug::text('Found Next Punch...', __FILE__, __LINE__, __METHOD__, 10); if (isset($punches[$punch_neighbors['next']][10]) and $this->getPunchObject()->getTimeStamp() > $punches[$punch_neighbors['next']][10]['time_stamp'] or isset($punches[$punch_neighbors['next']][20]) and $this->getPunchObject()->getTimeStamp() > $punches[$punch_neighbors['next']][20]['time_stamp']) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Time conflicts with another punch on this day') . ' (a)'); } } if (isset($punch_neighbors['prev']) and isset($punches[$punch_neighbors['prev']])) { Debug::text('Found prev Punch...', __FILE__, __LINE__, __METHOD__, 10); //This needs to take into account DST. Specifically if punches are like this: //03-Nov-12: IN: 10:00PM //04-Nov-12: OUT: 1:00AM L //04-Nov-12: IN: 1:30AM L //04-Nov-12: OUT: 6:30AM L //Since the 1AM to 2AM occur twice due to the "fall back" DST change, we need to allow those punches to be entered. if (isset($punches[$punch_neighbors['prev']][10]) and ($this->getPunchObject()->getTimeStamp() < $punches[$punch_neighbors['prev']][10]['time_stamp'] and TTDate::doesRangeSpanDST($this->getPunchObject()->getTimeStamp(), $punches[$punch_neighbors['prev']][10]['time_stamp']) == FALSE) or isset($punches[$punch_neighbors['prev']][20]) and ($this->getPunchObject()->getTimeStamp() < $punches[$punch_neighbors['prev']][20]['time_stamp'] and TTDate::doesRangeSpanDST($this->getPunchObject()->getTimeStamp(), $punches[$punch_neighbors['prev']][20]['time_stamp']) == FALSE)) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Time conflicts with another punch on this day') . ' (b)'); } } } //Check to make sure punches don't exceed maximum shift time. $maximum_shift_time = $plf->getPayPeriodMaximumShiftTime($this->getPunchObject()->getUser()); Debug::text('Maximum shift time: ' . $maximum_shift_time, __FILE__, __LINE__, __METHOD__, 10); if ($shift_data['total_time'] > $maximum_shift_time) { $this->Validator->isTRUE('time_stamp', FALSE, TTi18n::gettext('Punch exceeds maximum shift time of') . ' ' . TTDate::getTimeUnit($maximum_shift_time) . ' ' . TTi18n::getText('hrs set for this pay period schedule')); } } unset($punches); } } } if (getTTProductEdition() >= TT_PRODUCT_CORPORATE and $this->getEnableStrictJobValidation() == TRUE) { if ($this->getJob() > 0) { $jlf = TTnew('JobListFactory'); $jlf->getById($this->getJob()); if ($jlf->getRecordCount() > 0) { $j_obj = $jlf->getCurrent(); if (is_object($this->getUserDateObject()) and $j_obj->isAllowedUser($this->getUserDateObject()->getUser()) == FALSE) { $this->Validator->isTRUE('job', FALSE, TTi18n::gettext('Employee is not assigned to this job')); } if ($j_obj->isAllowedItem($this->getJobItem()) == FALSE) { $this->Validator->isTRUE('job_item', FALSE, TTi18n::gettext('Task is not assigned to this job')); } } } } return TRUE; }