<?php
/*********************************************************************************
 * The contents of this file are subject to the TimeTrex Public License Version
 * 1.1.0 ("License"); You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at http://www.TimeTrex.com/TPL
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * All copies of the Covered Code must include on each user interface screen:
 *    (i) the "Powered by TimeTrex" logo and
 *    (ii) the TimeTrex copyright notice
 * in the same form as they appear in the distribution.  See full license for
 * requirements.
 *
 * The Original Code is: TimeTrex Open Source
 * The Initial Developer of the Original Code is TimeTrex Payroll Services
 * Portions created by TimeTrex are Copyright (C) 2004-2007 TimeTrex Payroll Services;
 * All Rights Reserved.
 *
 ********************************************************************************/
/*
 * $Revision: 2237 $
 * $Id: PunchFactory.class.php 2237 2008-11-11 18:18:20Z ipso $
 * $Date: 2008-11-11 10:18:20 -0800 (Tue, 11 Nov 2008) $
 */

/**
 * @package Module_Punch
 */
class PunchFactory extends Factory {
	protected $table = 'punch';
	protected $pk_sequence_name = 'punch_id_seq'; //PK Sequence name

	var $punch_control_obj = NULL;
	protected $schedule_obj = NULL;
	var $tmp_data = NULL;


	function _getFactoryOptions( $name ) {

		$retval = NULL;
		switch( $name ) {
			case 'status':
				$retval = array(
										10 => TTi18n::gettext('In'),
										20 => TTi18n::gettext('Out'),
									);
				break;
			case 'type':
				$retval = array(
										10 => TTi18n::gettext('Normal'),
										20 => TTi18n::gettext('Lunch'),
										30 => TTi18n::gettext('Break'),
									);
				break;
			case 'transfer':
				$retval = array(
										0 => TTi18n::gettext('No'),
										1 => TTi18n::gettext('Yes'),
									);
				break;

		}

		return $retval;
	}


	function getPunchControlObject() {
		if ( is_object($this->punch_control_obj) ) {
			return $this->punch_control_obj;
		} else {
			if ( $this->getPunchControlID() !== FALSE ) {
				$pclf = new PunchControlListFactory();
				$this->punch_control_obj = $pclf->getById( $this->getPunchControlID() )->getCurrent();

				return $this->punch_control_obj;
			}

			return FALSE;
		}
	}
/*
	function getSchedulePolicyObject() {
		if ( is_object($this->schedule_policy_obj) ) {
			return $this->schedule_policy_obj;
		} else {
			$splf = new SchedulePolicyListFactory();
			$splf->getById( $this->getScheduleObject()->getSchedulePolicyID() );
			if ( $splf->getRecordCount() > 0 ) {
				$this->schedule_policy_obj = $splf->getCurrent();
				return $this->schedule_policy_obj;
			}

			return FALSE;
		}
	}
*/
	function getScheduleObject() {
		if ( is_object($this->schedule_obj) ) {
			return $this->schedule_obj;
		} else {
			if ( $this->getScheduleID() !== FALSE ) {
				$slf = new ScheduleListFactory();
				$slf->getById( $this->getScheduleID() );
				if ( $slf->getRecordCount() > 0 ) {
					$this->schedule_obj = $slf->getCurrent();
					return $this->schedule_obj;
				}
			}

			return FALSE;
		}
	}

	function getNextPunchControlID() {
		//This is normally the PREVIOUS punch,
		//so if it was IN (10), return its punch control ID
		//so the next OUT punch is a new punch_control_id.
		if ( $this->getStatus() == 10 ) {
			return $this->getPunchControlID();
		}

		return FALSE;
	}

	function getPunchControlID() {
		if ( isset($this->data['punch_control_id']) ) {
			return $this->data['punch_control_id'];
		}

		return FALSE;
	}
	function setPunchControlID($id) {
		$id = trim($id);

		$pclf = new PunchControlListFactory();

		if (  $this->Validator->isResultSetWithRows(	'punch_control',
														$pclf->getByID($id),
														TTi18n::gettext('Invalid Punch Control ID')
														) ) {
			$this->data['punch_control_id'] = $id;

			return TRUE;
		}

		return FALSE;
	}

	function getTransfer() {
		if ( isset($this->data['transfer']) ) {
			return $this->fromBool( $this->data['transfer'] );
		}

		return FALSE;
	}
	function setTransfer($bool) {
		$this->data['transfer'] = $this->toBool($bool);

		return TRUE;
	}

	function getNextStatus() {
		if ( $this->getStatus() == 10 ) {
			return 20;
		}

		return 10;
	}

	function getStatus() {
		if ( isset($this->data['status_id']) ) {
			return $this->data['status_id'];
		}

		return FALSE;
	}
	function setStatus($status) {
		$status = trim($status);

		Debug::text(' Status: '. $status , __FILE__, __LINE__, __METHOD__,10);

		$key = Option::getByValue($status, $this->getOptions('status') );
		if ($key !== FALSE) {
			$status = $key;
		}

		if ( $this->Validator->inArrayKey(	'status',
											$status,
											TTi18n::gettext('Incorrect Status'),
											$this->getOptions('status')) ) {

			$this->data['status_id'] = $status;

			return FALSE;
		}

		return FALSE;
	}

	function getNextType() {
		if ( $this->getStatus() == 10 ) { //In
			return 10;
		} else { //Out
			return $this->getType();
		}

		return FALSE;
	}

	function getTypeCode() {
		if ( $this->getType() != 10 ) {
			$options = $this->getOptions('type');
			return substr( $options[$this->getType()], 0, 1);
		}

		return FALSE;
	}

	function getType() {
		if ( isset($this->data['type_id']) ) {
			return $this->data['type_id'];
		}

		return FALSE;
	}
	function setType($value) {
		$value = trim($value);

		$key = Option::getByValue($value, $this->getOptions('type') );
		if ($key !== FALSE) {
			$value = $key;
		}

		if ( $this->Validator->inArrayKey(	'type',
											$value,
											TTi18n::gettext('Incorrect Type'),
											$this->getOptions('type')) ) {

			$this->data['type_id'] = $value;

			return FALSE;
		}

		return FALSE;
	}

	function getStation() {
		if ( isset($this->data['station_id']) ) {
			return $this->data['station_id'];
		}

		return FALSE;
	}
	function setStation($id) {
		$id = trim($id);

		$slf = new StationListFactory();
/*
		if (	$id == 0
				OR
				$this->Validator->isResultSetWithRows(		'station',
															$slf->getByID($id),
															TTi18n::gettext('Station does not exist')
															) ) {
*/
			$this->data['station_id'] = $id;

			return TRUE;
//		}

		return FALSE;
	}

	function roundTimeStamp($epoch) {
		$original_epoch = $epoch;

		Debug::text(' Rounding Timestamp: '. TTDate::getDate('DATE+TIME', $epoch ) .' Status ID: '. $this->getStatus() .' Type ID: '. $this->getType() , __FILE__, __LINE__, __METHOD__,10);

		if ( !is_object( $this->getPunchControlObject() ) ) {
			return FALSE;
		}

		//Check for rounding policies.
		$riplf = new RoundIntervalPolicyListFactory();
		$type_id = $riplf->getPunchTypeFromPunchStatusAndType( $this->getStatus(), $this->getType() );

		Debug::text(' Round Interval Punch Type: '. $type_id , __FILE__, __LINE__, __METHOD__,10);
		$riplf->getByPolicyGroupUserIdAndTypeId( $this->getPunchControlObject()->getUserDateObject()->getUser(), $type_id );
		if ( $riplf->getRecordCount() == 1 ) {
			$round_policy_obj = $riplf->getCurrent();
			Debug::text(' Found Rounding Policy: '. $round_policy_obj->getId() .' Punch Type: '. $round_policy_obj->getPunchType(), __FILE__, __LINE__, __METHOD__,10);

			//FIXME: It will only do proper total rounding if they edit the Lunch Out punch.
			//We need to account for cases when they edit just the Lunch In Punch.
			if ( $round_policy_obj->getPunchType() == 100 ) {
				Debug::text('Lunch Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);

				//On Lunch Punch In (back from lunch) do the total rounding.
				if ( $this->getStatus() == 10 AND $this->getType() == 20 ) {
					Debug::text('bLunch Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
					//If strict is set, round to scheduled lunch time?
					//Find Lunch Punch In.

					$plf = new PunchListFactory();
					$plf->getPreviousPunchByUserDateIdAndStatusAndTypeAndEpoch( $this->getPunchControlObject()->getUserDateID(), 20, 20, $epoch );
					if ( $plf->getRecordCount() == 1 ) {
						Debug::text('Found Lunch Punch Out: '. TTDate::getDate('DATE+TIME', $plf->getCurrent()->getTimeStamp() ), __FILE__, __LINE__, __METHOD__,10);

						$total_lunch_time = $epoch - $plf->getCurrent()->getTimeStamp();
						Debug::text('Total Lunch Time: '. $total_lunch_time, __FILE__, __LINE__, __METHOD__,10);

						//Set the ScheduleID
						$has_schedule = $this->setScheduleID( $epoch );

						if ( $has_schedule == TRUE AND $round_policy_obj->getGrace() > 0
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject() )
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject() ) )  {
							Debug::text(' Applying Grace Period: ', __FILE__, __LINE__, __METHOD__,10);
							$total_lunch_time = TTDate::graceTime($total_lunch_time, $round_policy_obj->getGrace(), $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject()->getAmount() );
							Debug::text('After Grace: '. $total_lunch_time, __FILE__, __LINE__, __METHOD__,10);
						}

						if ( $round_policy_obj->getInterval() > 0 )  {
							Debug::Text(' Rounding to interval: '. $round_policy_obj->getInterval(), __FILE__, __LINE__, __METHOD__,10);
							$total_lunch_time = TTDate::roundTime($total_lunch_time, $round_policy_obj->getInterval(), $round_policy_obj->getRoundType(), $round_policy_obj->getGrace() );
							Debug::text('After Rounding: '. $total_lunch_time, __FILE__, __LINE__, __METHOD__,10);
						}

						if (  $has_schedule == TRUE AND $round_policy_obj->getStrict() == TRUE
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject() )
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject() ) ) {
							Debug::Text(' Snap Time: Round Type: '. $round_policy_obj->getRoundType() , __FILE__, __LINE__, __METHOD__,10);
							if ( $round_policy_obj->getRoundType() == 10 ) {
								Debug::Text(' Snap Time DOWN ' , __FILE__, __LINE__, __METHOD__,10);
								$total_lunch_time = TTDate::snapTime($total_lunch_time, $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject()->getAmount(), 'DOWN');
							} elseif ( $round_policy_obj->getRoundType() == 30 ) {
								Debug::Text(' Snap Time UP' , __FILE__, __LINE__, __METHOD__,10);
								$total_lunch_time = TTDate::snapTime($total_lunch_time, $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject()->getAmount(), 'UP');
							} else {
								Debug::Text(' Not Snaping Time' , __FILE__, __LINE__, __METHOD__,10);
							}
						}

						$epoch = $plf->getCurrent()->getTimeStamp() + $total_lunch_time;
						Debug::text('Epoch after total rounding is: '. $epoch .' - '. TTDate::getDate('DATE+TIME', $epoch) , __FILE__, __LINE__, __METHOD__, 10);

					} else {
						Debug::text('DID NOT Find Lunch Punch Out: '. TTDate::getDate('DATE+TIME', $plf->getCurrent()->getTimeStamp() ), __FILE__, __LINE__, __METHOD__,10);
					}

				} else {
					Debug::text('Skipping Lunch Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
				}
			} elseif ( $round_policy_obj->getPunchType() == 110 ) { //Break Total
				Debug::text('break Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);

				//On break Punch In (back from break) do the total rounding.
				if ( $this->getStatus() == 10 AND $this->getType() == 30 ) {
					Debug::text('bbreak Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
					//If strict is set, round to scheduled break time?
					//Find break Punch In.

					$plf = new PunchListFactory();
					$plf->getPreviousPunchByUserDateIdAndStatusAndTypeAndEpoch( $this->getPunchControlObject()->getUserDateID(), 20, 30, $epoch );
					if ( $plf->getRecordCount() == 1 ) {
						Debug::text('Found break Punch Out: '. TTDate::getDate('DATE+TIME', $plf->getCurrent()->getTimeStamp() ), __FILE__, __LINE__, __METHOD__,10);

						$total_break_time = $epoch - $plf->getCurrent()->getTimeStamp();
						Debug::text('Total break Time: '. $total_break_time, __FILE__, __LINE__, __METHOD__,10);

						//Set the ScheduleID
						$has_schedule = $this->setScheduleID( $epoch );

						if ( $has_schedule == TRUE AND $round_policy_obj->getGrace() > 0
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject() )
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject() ) )  {
							Debug::text(' Applying Grace Period: ', __FILE__, __LINE__, __METHOD__,10);
							$total_break_time = TTDate::graceTime($total_break_time, $round_policy_obj->getGrace(), $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject()->getAmount() );
							Debug::text('After Grace: '. $total_break_time, __FILE__, __LINE__, __METHOD__,10);
						}

						if ( $round_policy_obj->getInterval() > 0 )  {
							Debug::Text(' Rounding to interval: '. $round_policy_obj->getInterval(), __FILE__, __LINE__, __METHOD__,10);
							$total_break_time = TTDate::roundTime($total_break_time, $round_policy_obj->getInterval(), $round_policy_obj->getRoundType(), $round_policy_obj->getGrace() );
							Debug::text('After Rounding: '. $total_break_time, __FILE__, __LINE__, __METHOD__,10);
						}

						if (  $has_schedule == TRUE AND $round_policy_obj->getStrict() == TRUE
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject() )
								AND is_object( $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject() ) ) {
							Debug::Text(' Snap Time: Round Type: '. $round_policy_obj->getRoundType() , __FILE__, __LINE__, __METHOD__,10);
							if ( $round_policy_obj->getRoundType() == 10 ) {
								Debug::Text(' Snap Time DOWN ' , __FILE__, __LINE__, __METHOD__,10);
								$total_break_time = TTDate::snapTime($total_break_time, $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject()->getAmount(), 'DOWN');
							} elseif ( $round_policy_obj->getRoundType() == 30 ) {
								Debug::Text(' Snap Time UP' , __FILE__, __LINE__, __METHOD__,10);
								$total_break_time = TTDate::snapTime($total_break_time, $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject()->getAmount(), 'UP');
							} else {
								Debug::Text(' Not Snaping Time' , __FILE__, __LINE__, __METHOD__,10);
							}
						}

						$epoch = $plf->getCurrent()->getTimeStamp() + $total_break_time;
						Debug::text('Epoch after total rounding is: '. $epoch .' - '. TTDate::getDate('DATE+TIME', $epoch) , __FILE__, __LINE__, __METHOD__, 10);

					} else {
						Debug::text('DID NOT Find break Punch Out: '. TTDate::getDate('DATE+TIME', $plf->getCurrent()->getTimeStamp() ), __FILE__, __LINE__, __METHOD__,10);
					}

				} else {
					Debug::text('Skipping break Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
				}

			} elseif ( $round_policy_obj->getPunchType() == 120 ) { //Day Total Rounding
				Debug::text('Day Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
				//On Lunch Punch In (back from lunch) do the total rounding.
				if ( $this->getStatus() == 20 AND $this->getType() == 10 ) { //Out, Type Normal
					Debug::text('bDay Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
					//If strict is set, round to scheduled time?
					//Find Lunch Punch In.

					$plf = new PunchListFactory();
					//$plf->getPreviousPunchByUserDateIdAndStatusAndTypeAndEpoch( $this->getPunchControlObject()->getUserDateID(), 20, 10, $epoch );
					$plf->getPreviousPunchByPunchControlId( $this->getPunchControlID() );
					if ( $plf->getRecordCount() == 1 ) {
						Debug::text('Found Normal Punch In: '. TTDate::getDate('DATE+TIME', $plf->getCurrent()->getTimeStamp() ), __FILE__, __LINE__, __METHOD__,10);

						//Get day total time prior to this punch control.
						$pclf = new PunchControlListFactory();
						$pclf->getByUserDateId( $this->getPunchControlObject()->getUserDateID() );
						if ( $pclf->getRecordCount() > 0 ) {
							$day_total_time = $epoch - $plf->getCurrent()->getTimeStamp();
							Debug::text('aDay Total Time: '. $day_total_time, __FILE__, __LINE__, __METHOD__,10);

							foreach( $pclf as $pc_obj ) {
								if ( $this->getPunchControlID() != $pc_obj->getID() ) {
									Debug::text('Punch Control Total Time: '. $pc_obj->getTotalTime() .' ID: '. $pc_obj->getId(), __FILE__, __LINE__, __METHOD__,10);
									$day_total_time += $pc_obj->getTotalTime();
								}
							}

							Debug::text('bDay Total Time: '. $day_total_time, __FILE__, __LINE__, __METHOD__,10);
							$original_day_total_time = $day_total_time;

							if ( $day_total_time > 0 ) {
								//Set the ScheduleID
								$has_schedule = $this->setScheduleID( $epoch );

								if ( $has_schedule == TRUE AND $round_policy_obj->getGrace() > 0
										AND is_object( $this->getScheduleObject()->getSchedulePolicyObject() )
										AND is_object( $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject() ) )  {
									Debug::text(' Applying Grace Period: ', __FILE__, __LINE__, __METHOD__,10);
									$day_total_time = TTDate::graceTime($day_total_time, $round_policy_obj->getGrace(), $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject()->getAmount() );
									Debug::text('After Grace: '. $day_total_time, __FILE__, __LINE__, __METHOD__,10);
								}

								if ( $round_policy_obj->getInterval() > 0 )  {
									Debug::Text(' Rounding to interval: '. $round_policy_obj->getInterval(), __FILE__, __LINE__, __METHOD__,10);
									$day_total_time = TTDate::roundTime($day_total_time, $round_policy_obj->getInterval(), $round_policy_obj->getRoundType(), $round_policy_obj->getGrace() );
									Debug::text('After Rounding: '. $day_total_time, __FILE__, __LINE__, __METHOD__,10);
								}

								if (  $has_schedule == TRUE AND $round_policy_obj->getStrict() == TRUE
										AND is_object( $this->getScheduleObject()->getSchedulePolicyObject() )
										AND is_object( $this->getScheduleObject()->getSchedulePolicyObject()->getMealPolicyObject() ) ) {
									Debug::Text(' Snap Time: Round Type: '. $round_policy_obj->getRoundType() , __FILE__, __LINE__, __METHOD__,10);
									if ( $round_policy_obj->getRoundType() == 10 ) {
										Debug::Text(' Snap Time DOWN ' , __FILE__, __LINE__, __METHOD__,10);
										$day_total_time = TTDate::snapTime($day_total_time, $this->getScheduleObject()->getTotalTime(), 'DOWN');
									} elseif ( $round_policy_obj->getRoundType() == 30 ) {
										Debug::Text(' Snap Time UP' , __FILE__, __LINE__, __METHOD__,10);
										$day_total_time = TTDate::snapTime($day_total_time, $this->getScheduleObject()->getTotalTime(), 'UP');
									} else {
										Debug::Text(' Not Snaping Time' , __FILE__, __LINE__, __METHOD__,10);
									}
								}

								Debug::text('cDay Total Time: '. $day_total_time, __FILE__, __LINE__, __METHOD__,10);

								$day_total_time_diff = $day_total_time - $original_day_total_time;
								Debug::text('Day Total Diff: '. $day_total_time_diff, __FILE__, __LINE__, __METHOD__,10);

								$epoch = $original_epoch + $day_total_time_diff;
							}
						}
					} else {
						Debug::text('DID NOT Find Normal Punch Out: '. TTDate::getDate('DATE+TIME', $plf->getCurrent()->getTimeStamp() ), __FILE__, __LINE__, __METHOD__,10);
					}

				} else {
					Debug::text('Skipping Lunch Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
				}
			} else {
				Debug::text('NOT Total Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);

				if ( $this->inScheduleStartStopWindow( $epoch ) AND $round_policy_obj->getGrace() > 0 )  {
					Debug::text(' Applying Grace Period: ', __FILE__, __LINE__, __METHOD__,10);
					$epoch = TTDate::graceTime($epoch, $round_policy_obj->getGrace(), $this->getScheduleWindowTime() );
					Debug::text('After Grace: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
				}

				$grace_time = $round_policy_obj->getGrace();
				//If strict scheduling is enabled, handle grace times differently.
				//Only apply them above if we are near the schedule start/stop time.
				//This allows for grace time to apply if an employee punches in late,
				//but afterwards not apply at all.
				if ( $round_policy_obj->getStrict() == TRUE ) {
					$grace_time = 0;
				}

				if ( $round_policy_obj->getInterval() > 0 )  {
					Debug::Text(' Rounding to interval: '. $round_policy_obj->getInterval(), __FILE__, __LINE__, __METHOD__,10);
					$epoch = TTDate::roundTime($epoch, $round_policy_obj->getInterval(), $round_policy_obj->getRoundType(), $grace_time );
					Debug::text('After Rounding: '. TTDate::getDate('DATE+TIME', $epoch ), __FILE__, __LINE__, __METHOD__,10);
				}

				//ONLY perform strict rounding on Normal punches, not break/lunch punches?
				//Modify the UI to restrict this as well perhaps?
				if ( $round_policy_obj->getStrict() == TRUE AND $this->getScheduleWindowTime() !== FALSE ) {
					Debug::Text(' Snap Time: Round Type: '. $round_policy_obj->getRoundType() , __FILE__, __LINE__, __METHOD__,10);
					if ( $round_policy_obj->getRoundType() == 10 ) {
						Debug::Text(' Snap Time DOWN ' , __FILE__, __LINE__, __METHOD__,10);
						$epoch = TTDate::snapTime($epoch, $this->getScheduleWindowTime(), 'DOWN');
					} elseif ( $round_policy_obj->getRoundType() == 30 ) {
						Debug::Text(' Snap Time UP' , __FILE__, __LINE__, __METHOD__,10);
						$epoch = TTDate::snapTime($epoch, $this->getScheduleWindowTime(), 'UP');
					} else {
						//If its an In Punch, snap up, if its out punch, snap down?
						Debug::Text(' Average rounding type, automatically determining snap direction.' , __FILE__, __LINE__, __METHOD__,10);
						if ( $this->getStatus() == 10 ) {
							Debug::Text(' Snap Time UP' , __FILE__, __LINE__, __METHOD__,10);
							$epoch = TTDate::snapTime($epoch, $this->getScheduleWindowTime(), 'UP');
						} else {
							Debug::Text(' Snap Time DOWN ' , __FILE__, __LINE__, __METHOD__,10);
							$epoch = TTDate::snapTime($epoch, $this->getScheduleWindowTime(), 'DOWN');
						}
					}
				}
			}

		} else {
			Debug::text(' NO Rounding Policy(s) Found', __FILE__, __LINE__, __METHOD__,10);
		}

		Debug::text(' Rounded TimeStamp: '. TTDate::getDate('DATE+TIME', $epoch ) .' Original TimeStamp: '. TTDate::getDate('DATE+TIME', $original_epoch ), __FILE__, __LINE__, __METHOD__,10);

		return $epoch;
	}

	function getTimeStamp( $raw = FALSE ) {
		if ( isset($this->data['time_stamp']) ) {
			if ( $raw === TRUE) {
				return $this->data['time_stamp'];
			} else {
				//return $this->db->UnixTimeStamp( $this->data['start_date'] );
				//strtotime is MUCH faster than UnixTimeStamp
				//Must use ADODB for times pre-1970 though.
				return TTDate::strtotime( $this->data['time_stamp'] );
			}
		}

		return FALSE;
	}
	function setTimeStamp($epoch, $enable_rounding = TRUE) {
		$epoch = $original_epoch = trim($epoch);

		if ( $enable_rounding == TRUE AND $this->getTransfer() == FALSE ) {
			$epoch = $this->roundTimeStamp($epoch);

			if ( is_object( $this->getPunchControlObject() ) AND TTDate::getDayOfMonth( $epoch ) != TTDate::getDayOfMonth( $original_epoch ) ) {
				Debug::text(' Rounding causing timestamp to rollover into new day, recalculate User Date ID... ', __FILE__, __LINE__, __METHOD__,10);
				$this->getPunchControlObject()->setOldUserDateID( $this->getPunchControlObject()->getUserDateID() );

				//Recalculate the user_date_id AFTER punch rounding has occured incase a 11:57PM punch gets rounded to 12:15AM.
				$this->getPunchControlObject()->findUserDate( $this->getPunchControlObject()->getUserDateObject()->getUser(), $epoch, $this->getStatus(), $this->getId() );
			}
		} else {
			Debug::text(' Rounding Disabled... ', __FILE__, __LINE__, __METHOD__,10);
		}

		//Always round to one min, no matter what. Even on a transfer.
		$epoch = TTDate::roundTime($epoch, 60);

		if 	(	$this->Validator->isDate(		'time_stamp',
												$epoch,
												TTi18n::gettext('Incorrect time stamp'))

			) {

			Debug::text(' Set: '. $epoch , __FILE__, __LINE__, __METHOD__,10);
			$this->data['time_stamp'] = $epoch;

			return TRUE;
		}

		return FALSE;
	}

	function getOriginalTimeStamp( $raw = FALSE ) {
		if ( isset($this->data['original_time_stamp']) ) {
			if ( $raw === TRUE ) {
				return $this->data['original_time_stamp'];
			} else {
				//return $this->db->UnixTimeStamp( $this->data['start_date'] );
				//strtotime is MUCH faster than UnixTimeStamp
				//Must use ADODB for times pre-1970 though.
				return TTDate::strtotime( $this->data['original_time_stamp'] );
			}
		}

		return FALSE;
	}
	function setOriginalTimeStamp($epoch) {
		$epoch = trim($epoch);

		if 	(	$this->Validator->isDate(		'original_time_stamp',
												$epoch,
												TTi18n::gettext('Incorrect original time stamp'))

			) {

			$this->data['original_time_stamp'] = $epoch;

			return TRUE;
		}

		return FALSE;
	}

	function getActualTimeStamp( $raw = FALSE ) {
		if ( isset($this->data['actual_time_stamp']) ) {
			if ( $raw === TRUE ) {
				return $this->data['actual_time_stamp'];
			} else {
				//return $this->db->UnixTimeStamp( $this->data['start_date'] );
				//strtotime is MUCH faster than UnixTimeStamp
				//Must use ADODB for times pre-1970 though.
				return TTDate::strtotime( $this->data['actual_time_stamp'] );
			}
		}

		return FALSE;
	}
	function setActualTimeStamp($epoch) {
		$epoch = trim($epoch);

		if 	(	$this->Validator->isDate(		'actual_time_stamp',
												$epoch,
												TTi18n::gettext('Incorrect actual time stamp'))

			) {

			$this->data['actual_time_stamp'] = $epoch;

			return TRUE;
		}

		return FALSE;
	}

	function getLongitude() {
		if ( isset($this->data['longitude']) ) {
			return (float)$this->data['longitude'];
		}

		return FALSE;
	}
	function setLongitude($value) {
		$value = trim((float)$value);

		if (	$value == 0
				OR
				$this->Validator->isFloat(	'longitude',
											$value,
											TTi18n::gettext('Longitude is invalid')
											) ) {
			$this->data['longitude'] = $value;

			return TRUE;
		}

		return FALSE;
	}

	function getLatitude() {
		if ( isset($this->data['latitude']) ) {
			return (float)$this->data['latitude'];
		}

		return FALSE;
	}
	function setLatitude($value) {
		$value = trim((float)$value);

		if (	$value == 0
				OR
				$this->Validator->isFloat(	'latitude',
											$value,
											TTi18n::gettext('Latitude is invalid')
											) ) {
			$this->data['latitude'] = $value;

			return TRUE;
		}

		return FALSE;
	}

	function getScheduleID() {
		if ( isset($this->tmp_data['schedule_id']) ) {
			return $this->tmp_data['schedule_id'];
		}

		return FALSE;
	}

	function setScheduleID( $epoch = NULL ) {
		Debug::text(' aFinding SchedulePolicyID for this Punch: '. $epoch, __FILE__, __LINE__, __METHOD__,10);
		if ( $epoch == '' ) {
			$epoch = $this->getTimeStamp();
		}

		if ( $epoch == FALSE ) {
			return FALSE;
		}
		Debug::text(' bFinding SchedulePolicyID for this Punch: '. $epoch, __FILE__, __LINE__, __METHOD__,10);

		//Each time this is called, clear the ScheduleObject() cache.
		$this->schedule_obj = NULL;

		//Check to see if this punch is within the start/stop window for the schedule.
		//We need to make sure we get schedules within about a 24hr
		//window of this punch, because if punch is at 11:55AM and the schedule starts at 12:30AM it won't
		//be found by a user_date_id.
		$slf = new ScheduleListFactory();
		//$slf->getByUserDateId( $this->getPunchControlObject()->getUserDateID() );
		$slf->getByUserIdAndStartDateAndEndDate( $this->getPunchControlObject()->getUserDateObject()->getUser(), ($epoch-43200), ($epoch+43200) );
		if ( $slf->getRecordCount() > 0 ) {
			//Check for schedule policy
			foreach ( $slf as $s_obj ) {
				Debug::text(' Checking Schedule ID: '. $s_obj->getID(), __FILE__, __LINE__, __METHOD__,10);
				if ( $s_obj->inSchedule( $epoch ) ) {
					Debug::text(' Within Start/Stop window. ', __FILE__, __LINE__, __METHOD__,10);
					$this->tmp_data['schedule_id'] = $s_obj->getId();
					return TRUE;
				} else {
					Debug::text(' NOT Within Start/Stop window.', __FILE__, __LINE__, __METHOD__,10);
					//Continue looping through all schedule shifts.
				}
			}
		} else {
			Debug::text(' Did not find Schedule...', __FILE__, __LINE__, __METHOD__,10);
		}

		return FALSE;
	}

	function getEnableCalcSystemTotalTime() {
		if ( isset($this->calc_system_total_time) ) {
			return $this->calc_system_total_time;
		}

		return FALSE;
	}
	function setEnableCalcSystemTotalTime($bool) {
		$this->calc_system_total_time = $bool;

		return TRUE;
	}

	function getEnableCalcWeeklySystemTotalTime() {
		if ( isset($this->calc_weekly_system_total_time) ) {
			return $this->calc_weekly_system_total_time;
		}

		return FALSE;
	}
	function setEnableCalcWeeklySystemTotalTime($bool) {
		$this->calc_weekly_system_total_time = $bool;

		return TRUE;
	}

	function getEnableCalcException() {
		if ( isset($this->calc_exception) ) {
			return $this->calc_exception;
		}

		return FALSE;
	}
	function setEnableCalcException($bool) {
		$this->calc_exception = $bool;

		return TRUE;
	}

	function getEnablePreMatureException() {
		if ( isset($this->premature_exception) ) {
			return $this->premature_exception;
		}

		return FALSE;
	}
	function setEnablePreMatureException($bool) {
		$this->premature_exception = $bool;

		return TRUE;
	}

	function getEnableCalcUserDateTotal() {
		if ( isset($this->calc_user_date_total) ) {
			return $this->calc_user_date_total;
		}

		return FALSE;
	}
	function setEnableCalcUserDateTotal($bool) {
		$this->calc_user_date_total = $bool;

		return TRUE;
	}

	function getEnableCalcTotalTime() {
		if ( isset($this->calc_total_time) ) {
			return $this->calc_total_time;
		}

		return FALSE;
	}
	function setEnableCalcTotalTime($bool) {
		$this->calc_total_time = $bool;

		return TRUE;
	}

	function getEnableAutoTransfer() {
		if ( isset($this->auto_transfer) ) {
			return $this->auto_transfer;
		}

		return TRUE;
	}
	function setEnableAutoTransfer($bool) {
		$this->auto_transfer = $bool;

		return TRUE;
	}

/*
	function getSchedulePolicyID() {
		if ( isset($this->tmp_data['schedule_policy_id']) ) {
			return $this->tmp_data['schedule_policy_id'];
		}

		return FALSE;
	}
*/
	function getScheduleWindowTime() {
		if ( isset($this->tmp_data['schedule_window_time']) ) {
			return $this->tmp_data['schedule_window_time'];
		}

		return FALSE;
	}

	function inScheduleStartStopWindow( $epoch ) {
		if ( $epoch == '' ) {
			return FALSE;
		}

		$this->setScheduleID( $epoch );

		if ( $this->getScheduleObject() == FALSE ) {
			return FALSE;
		}

		if ( $this->getScheduleObject()->inStartWindow( $epoch ) == TRUE ) {
			Debug::text(' Within Start window: '. $this->getScheduleObject()->getSchedulePolicyObject()->getStartStopWindow() .' Schedule Policy ID: '. $this->getScheduleObject()->getSchedulePolicyID() , __FILE__, __LINE__, __METHOD__,10);

			$this->tmp_data['schedule_window_time'] = $this->getScheduleObject()->getStartTime();

			return TRUE;
		} elseif ( $this->getScheduleObject()->inStopWindow( $epoch ) == TRUE ) {
			Debug::text(' Within Stop window: '. $this->getScheduleObject()->getSchedulePolicyObject()->getStartStopWindow() .' Schedule Policy ID: '. $this->getScheduleObject()->getSchedulePolicyID() , __FILE__, __LINE__, __METHOD__,10);

			$this->tmp_data['schedule_window_time'] = $this->getScheduleObject()->getEndTime();

			return TRUE;
		} else {
			Debug::text(' NOT Within Start/Stop window.', __FILE__, __LINE__, __METHOD__,10);
		}

		return FALSE;
	}

	function Validate() {
		if ( $this->Validator->hasError('punch_control') == FALSE
				AND $this->getPunchControlID() == FALSE ) {
			$this->Validator->isTRUE(	'punch_control',
										FALSE,
										TTi18n::gettext('Invalid Punch Control ID'));
		}

		$plf = new PunchListFactory();
		//Make sure there are never more than two punches per punch control ID.
		$plf->getByPunchControlID( $this->getPunchControlID() );
		if ( ( $this->isNew() AND $plf->getRecordCount() == 2 )
				OR $plf->getRecordCount() > 2 ) {
			$this->Validator->isTRUE(	'punch_control',
										FALSE,
										TTi18n::gettext('Punch Control can not have more than two punches. Please use the Add Punch button instead'));
		}

		$plf->getByUserDateId( $this->getPunchControlObject()->getUserDateId() );
		if ( $plf->getRecordCount() > 0 ) {
			foreach ( $plf as $p_obj ) {
				$punches[$p_obj->getPunchControlId()][$p_obj->getStatus()] = $p_obj;
			}
			unset($p_obj);

			//Make sure two punches with the same punch_control never match.
			//An In Punch can never match another In Punch,
			//And an Out Punch can never match another Out Punch.
			//Both In/Out punches in a pair can't match each other, as that would be 0 total time.
			if ( isset($punches[$this->getPunchControlId()]) ) {
				if ( $this->isNew()
						OR ( !$this->isNew()
								AND isset($punches[$this->getPunchControlId()][$this->getStatus()]) AND $this->getId() != $punches[$this->getPunchControlId()][$this->getStatus()]->getId() ) ) {
					if (
						( isset($punches[$this->getPunchControlId()][10]) AND $punches[$this->getPunchControlId()][10]->getTimeStamp() == $this->getTimeStamp() )
						OR
						( isset($punches[$this->getPunchControlId()][20]) AND $punches[$this->getPunchControlId()][20]->getTimeStamp() == $this->getTimeStamp() ) ) {
						$this->Validator->isTRUE(	'time_stamp',
													FALSE,
													TTi18n::gettext('Time and status match that of another punch, this could be due to rounding'));
					}
				} else {
					Debug::text('aPunch does not match any other punch pair.', __FILE__, __LINE__, __METHOD__,10);
				}

				//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 ( $this->isNew() AND $this->getStatus() == 10 AND isset($punches[$this->getPunchControlId()][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'));
				} elseif ( $this->isNew() AND $this->getStatus() == 20 AND isset($punches[$this->getPunchControlId()][20]) ) {
					$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'));
				}

				if ( $this->getStatus() == 10 AND isset($punches[$this->getPunchControlId()][20]) AND $this->getTimeStamp() > $punches[$this->getPunchControlId()][20]->getTimeStamp() ) {
						$this->Validator->isTRUE(	'time_stamp',
													FALSE,
													TTi18n::gettext('In punches cannot occur after an out punch, in the same punch pair'));
				} elseif ( $this->getStatus() == 20 AND isset($punches[$this->getPunchControlId()][10]) AND $this->getTimeStamp() < $punches[$this->getPunchControlId()][10]->getTimeStamp() ) {
						$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->getPunchControlID(), '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->getTimeStamp() > $punches[$punch_neighbors['next']][10]->getTimeStamp() )
									OR ( isset($punches[$punch_neighbors['next']][20]) AND $this->getTimeStamp() > $punches[$punch_neighbors['next']][20]->getTimeStamp() ) ) {
							$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);
						if ( ( isset($punches[$punch_neighbors['prev']][10]) AND $this->getTimeStamp() < $punches[$punch_neighbors['prev']][10]->getTimeStamp() )
									OR ( isset($punches[$punch_neighbors['prev']][20]) AND $this->getTimeStamp() < $punches[$punch_neighbors['prev']][20]->getTimeStamp() ) ) {
							$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->getPunchControlObject()->getUserDateObject()->getUser() );
				if ( ( $this->getStatus() == 10 AND isset($punches[$this->getPunchControlId()][20]) AND ( $punches[$this->getPunchControlId()][20]->getTimeStamp()-$this->getTimeStamp() ) > $maximum_shift_time )
						OR
						( $this->getStatus() == 20 AND isset($punches[$this->getPunchControlId()][10]) AND ( $this->getTimeStamp()-$punches[$this->getPunchControlId()][10]->getTimeStamp() ) > $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') );
				}
			} else {
				Debug::text(' First Punch in the Pair!', __FILE__, __LINE__, __METHOD__,10);
				foreach( $punches as $punch_control_id => $punch_pair_arr ) {
					//Debug::Arr($punch_pair_arr, ' Incomplete Punch Pair: ', __FILE__, __LINE__, __METHOD__,10);
					if ( isset($punch_pair_arr[10]) AND isset($punch_pair_arr[20]) ) {
						//Found complete punch pair.
						if ( $this->getTimeStamp() > $punch_pair_arr[10]->getTimeStamp()
								AND $this->getTimeStamp() < $punch_pair_arr[20]->getTimeStamp() ) {
							$this->Validator->isTRUE(	'time_stamp',
														FALSE,
														TTi18n::gettext('Time conflicts with another punch on this day').' (c)');
							break;
						}
					}
				}
			}
		}

		return TRUE;
	}

	function preSave() {
		if ( $this->isNew() ) {
			//Debug::text(' Setting Original TimeStamp: '. $this->getTimeStamp(), __FILE__, __LINE__, __METHOD__,10);
			$this->setOriginalTimeStamp( $this->getTimeStamp() );
		}

		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->getPunchControlObject()->getUserDateObject()->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->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() ) {

						$pf->setEnableCalcTotalTime( TRUE );
						$pf->setEnableCalcSystemTotalTime( TRUE );
						$pf->setEnableCalcUserDateTotal( TRUE );
						$pf->setEnableCalcException( TRUE );
						$pf->setEnablePreMatureException( TRUE );

						$pf->Save();
					}
				} else {
					Debug::text(' Last Punch was out. No Auto Punch ', __FILE__, __LINE__, __METHOD__,10);
				}
			}

		}
		return TRUE;
	}

	function postSave() {

		if ( $this->getDeleted() == TRUE ) {
			//Check to see if any other punches are assigned to this punch_control_id
			$plf = new PunchListFactory();
			$plf->getByPunchControlId( $this->getPunchControlID() );
			if ( $plf->getRecordCount() == 0 ) {
				Debug::text(' Deleted Last Punch for Punch Control Object.', __FILE__, __LINE__, __METHOD__,10);
				$this->getPunchControlObject()->setDeleted(TRUE);
			}
		}

		//Update PunchControl
		$this->getPunchControlObject()->setEnableCalcSystemTotalTime( $this->getEnableCalcSystemTotalTime() );
		$this->getPunchControlObject()->setEnableCalcWeeklySystemTotalTime( $this->getEnableCalcWeeklySystemTotalTime() );
		$this->getPunchControlObject()->setEnableCalcException( $this->getEnableCalcException() );
		$this->getPunchControlObject()->setEnablePreMatureException( $this->getEnablePreMatureException() );
		$this->getPunchControlObject()->setEnableCalcUserDateTotal( $this->getEnableCalcUserDateTotal() );
		$this->getPunchControlObject()->setEnableCalcTotalTime( $this->getEnableCalcTotalTime() );
		$this->getPunchControlObject()->Save();

		return TRUE;
	}

	function addLog( $log_action ) {
		return TTLog::addEntry( $this->getId(), $log_action,  TTi18n::getText('Punch - Employee').': '. $this->getPunchControlObject()->getUserDateObject()->getUserObject()->getFullName() . TTi18n::getText(' Timestamp').': '. TTDate::getDate('DATE+TIME', $this->getTimeStamp() ) , NULL, $this->getTable() );
	}
}
?>
