<?php
/*********************************************************************************
 *
 * TimeTrex is a Workforce Management program developed by
 * TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 * details.
 *
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program; if not, see http://www.gnu.org/licenses or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 *
 *
 * You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
 * #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
 *
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by TimeTrex" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by TimeTrex".
 *
 ********************************************************************************/


/**
 * @package Modules\Policy
 */
class ExceptionPolicyFactory extends Factory {
	protected $table = 'exception_policy';
	protected $pk_sequence_name = 'exception_policy_id_seq'; //PK Sequence name

	protected $enable_grace = [ 'S3', 'S4', 'S5', 'S6', 'S7', 'S8', 'S9', 'L1', 'L2', 'L4', 'B1', 'B2', 'V1', 'C1' ];
	protected $enable_watch_window = [ 'S3', 'S4', 'S5', 'S6', 'O1', 'O2' ];
	protected $enable_punch_notice = [ 'S2', 'S3', 'S4', 'S5', 'S6', 'B2', 'L2' ];

	//M1 - Missing In Punch is not considered pre-mature, as the employee can't go back and punch in after the fact anyways.
	protected static $premature_exceptions = [ 'M2', 'M3', 'M4', 'L3', 'L4', 'B4', 'B5', 'S8' ];

	protected static $premature_delay = 57600;

	protected $exception_policy_control_obj = null;

	/**
	 * @param $name
	 * @param null $parent
	 * @return array|null
	 */
	function _getFactoryOptions( $name, $parent = null ) {
		$retval = null;
		switch ( $name ) {
			case 'type':
				$retval = [
					//Schedule Exceptions
					'S1' => TTi18n::gettext( 'Unscheduled Absence' ),
					'S2' => TTi18n::gettext( 'Not Scheduled' ),
					'S3' => TTi18n::gettext( 'In Early' ),
					'S4' => TTi18n::gettext( 'In Late' ),
					'S5' => TTi18n::gettext( 'Out Early' ),
					'S6' => TTi18n::gettext( 'Out Late' ),

					//'SS' => TTi18n::gettext('In Early (Level 2)'), //Allow for 2 or 3 levels of schedule exceptions.
					//'ST' => TTi18n::gettext('In Late (Level 2)'),
					//'SU' => TTi18n::gettext('Out Early (Level 2)'),
					//'SV' => TTi18n::gettext('Out Late (Level 2)'),

					//'SW' => TTi18n::gettext('In Early (Level 3)'), //Allow for 2 or 3 levels of schedule exceptions.
					//'SX' => TTi18n::gettext('In Late (Level 3)'),
					//'SY' => TTi18n::gettext('Out Early (Level 3)'),
					//'SZ' => TTi18n::gettext('Out Late (Level 3)'),

					'S7' => TTi18n::gettext( 'Over Daily Scheduled Time' ),
					'S8' => TTi18n::gettext( 'Under Daily Scheduled Time' ),
					'S9' => TTi18n::gettext( 'Over Weekly Scheduled Time' ),
					//'SA' => TTi18n::gettext('Under Weekly Scheduled Time'), //Is this needed?

					'SB' => TTi18n::gettext( 'Not Scheduled Branch or Department' ),
					'SC' => TTi18n::gettext( 'Not Scheduled Job or Task' ),
					'ST' => TTi18n::gettext( 'Not Scheduled Punch Tags(s)' ),

					//Add setting to set some sort of "Grace" period, or early warning system? Approaching overtime?
					//Have exception where they can set the cutoff in hours, and it triggers once the employee has exceeded the weekly hours.
					'O1' => TTi18n::gettext( 'Over Daily Time' ),
					'O2' => TTi18n::gettext( 'Over Weekly Time' ),

					//Punch Exceptions
					'M1' => TTi18n::gettext( 'Missing In Punch' ),
					'M2' => TTi18n::gettext( 'Missing Out Punch' ),
					'M3' => TTi18n::gettext( 'Missing Lunch In/Out Punch' ),
					'M4' => TTi18n::gettext( 'Missing Break In/Out Punch' ),

					'L1' => TTi18n::gettext( 'Long Lunch' ),
					'L2' => TTi18n::gettext( 'Short Lunch' ),
					'L3' => TTi18n::gettext( 'No Lunch' ),
					'L4' => TTi18n::gettext( 'Late Lunch' ),
					//'L5' => TTi18n::gettext( 'Early Lunch' ),

					'B1' => TTi18n::gettext( 'Long Break' ),
					'B2' => TTi18n::gettext( 'Short Break' ),
					'B3' => TTi18n::gettext( 'Too Many Breaks' ),
					'B4' => TTi18n::gettext( 'Too Few Breaks' ),
					'B5' => TTi18n::gettext( 'No Break' ),
					//Worked too long without break/lunch, allow to set the time frame.
					//Make grace period the amount of time a break has to exceed, and watch window the longest they can work without a break?
					//No Break exception essentially handles this.
					//'B6' => TTi18n::gettext('Worked Too Long without Break')
					//'B6' => TTi18n::gettext( 'Late Break' ),

					//For security guards typically, must check in (punch/transfer punch) every X hours or this alert goes out.
					'C1' => TTi18n::gettext( 'Missed Check-In' ),

					//Branch/Department exceptions
					'D1' => TTi18n::gettext( 'No Branch or Department' ),
					'D2' => TTi18n::gettext( 'Not Allowed on Branch' ),
					'D3' => TTi18n::gettext( 'Not Allowed on Department' ),

					//TimeSheet verification exceptions.
					'V1' => TTi18n::gettext( 'TimeSheet Not Verified' ),

					//Job Exceptions
					'J1' => TTi18n::gettext( 'Not Allowed On Job' ),
					'J2' => TTi18n::gettext( 'Not Allowed On Task' ),
					'J3' => TTi18n::gettext( 'Job Completed' ),
					'J4' => TTi18n::gettext( 'No Job or Task' ),
					//'J5' => TTi18n::gettext('No Task'), //Make J4 No Job only?

					//Add location based exceptions, ie: Restricted Location.
					'G1' => TTi18n::gettext( 'GEO Fence Violation' ),

					//Punch Tag Exceptions
					'T1' => TTi18n::gettext( 'Not Allowed on Punch Tag(s)' ),
					'T2' => TTi18n::gettext( 'No Punch Tag(s)' ),

				];
				break;
			case 'severity':
				$retval = [
						10 => TTi18n::gettext( 'Low' ), //Black
						20 => TTi18n::gettext( 'Medium' ), //Blue
						25 => TTi18n::gettext( 'High' ), //Orange
						30 => TTi18n::gettext( 'Critical' ) //Rename to Critical: Red, was "High"
				];
				break;
			case 'email_notification':
				$retval = [
					0 => TTi18n::gettext( '-- None --' ),
					10  => TTi18n::gettext( 'Employee' ),
					20  => TTi18n::gettext( 'Supervisor' ),
					//20 => TTi18n::gettext('Immediate Supervisor'),
					//20 => TTi18n::gettext('All Supervisors'),
					100 => TTi18n::gettext( 'Both' ),
				];
				break;
			case 'punch_notification':
				$retval = [
					0 => TTi18n::gettext( '-- None --' ),
					10  => TTi18n::gettext( 'Warning' ),
					//12  => TTi18n::gettext( 'Warning (Contact)' ), //Message to contact supervisor
					20  => TTi18n::gettext( 'Lock-Out' ), //Message to try again later or contact supervisor.
					//20  => TTi18n::gettext( 'Lock-Out (Retry)' ), //Message to simply retry later.
					//22  => TTi18n::gettext( 'Lock-Out (Contact)' ), //Message to contact supervisor
				];
				break;
			case 'columns':
				$retval = [
						'-1010-active'             => TTi18n::gettext( 'Active' ),
						'-1020-severity'           => TTi18n::gettext( 'Severity' ),
						'-1030-grace'              => TTi18n::gettext( 'Grace' ),
						'-1040-watch_window'       => TTi18n::gettext( 'Watch Window' ),
						'-1050-email_notification' => TTi18n::gettext( 'Notify' ),
						'-1060-punch_notification' => TTi18n::gettext( 'Punch Notice' ),

						'-2000-created_by'   => TTi18n::gettext( 'Created By' ),
						'-2010-created_date' => TTi18n::gettext( 'Created Date' ),
						'-2020-updated_by'   => TTi18n::gettext( 'Updated By' ),
						'-2030-updated_date' => TTi18n::gettext( 'Updated Date' ),
				];
				break;
		}

		return $retval;
	}

	/**
	 * @param $data
	 * @return array
	 */
	function _getVariableToFunctionMap( $data ) {
		$variable_function_map = [
				'id'                          => 'ID',
				'exception_policy_control_id' => 'ExceptionPolicyControl',
				'name'                        => 'Name',
				'type_id'                     => 'Type',
				'severity_id'                 => 'Severity',
				'is_enabled_watch_window'     => 'isEnabledWatchWindow',
				'watch_window'                => 'WatchWindow',
				'is_enabled_grace'            => 'isEnabledGrace',
				'grace'                       => 'Grace',
				'is_enabled_punch_notice'     => 'isEnabledPunchNotice',
				'punch_notification_id'       => 'PunchNotification',
				'demerit'                     => 'Demerit',
				'email_notification_id'       => 'EmailNotification',
				'active'                      => 'Active',
				'deleted'                     => 'Deleted',
		];

		return $variable_function_map;
	}

	/**
	 * @return bool
	 */
	function getExceptionPolicyControlObject() {
		return $this->getGenericObject( 'ExceptionPolicyControlListFactory', $this->getExceptionPolicyControl(), 'exception_policy_control_obj' );
	}

	/**
	 * @return bool|mixed
	 */
	function getExceptionPolicyControl() {
		return $this->getGenericDataValue( 'exception_policy_control_id' );
	}

	/**
	 * @param string $value UUID
	 * @return bool
	 */
	function setExceptionPolicyControl( $value ) {
		$value = TTUUID::castUUID( $value );

		return $this->setGenericDataValue( 'exception_policy_control_id', $value );
	}

	/**
	 * @param $exclude_exceptions
	 * @param int $product_edition
	 * @return array
	 */
	function getExceptionTypeDefaultValues( $exclude_exceptions, $product_edition = 10 ) {
		if ( !is_array( $exclude_exceptions ) ) {
			$exclude_exceptions = [];
		}
		$type_options = $this->getTypeOptions( $product_edition );

		$retarr = [];

		foreach ( $type_options as $type_id => $exception_name ) {
			//Skip excluded exceptions
			if ( in_array( $type_id, $exclude_exceptions ) ) {
				continue;
			}

			switch ( $type_id ) {
				case 'S1': //UnSchedule Absence
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 25,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S2': //Not Scheduled
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 10,
							'demerit'                 => 10,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S3': //In Early
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 10,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 10,
							'demerit'                 => 2,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 7200,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S4': //In Late
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 25,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 10,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 7200,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S5': //Out Early
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 20,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0, //Don't default to warning, because depending on how they detect lunch/breaks it could cause problems.
							'demerit'                 => 10,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 7200,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S6': //Out Late
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 10,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 7200,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S7': //Over daily scheduled time
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S8': //Under daily scheduled time
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'S9': //Over Weekly Scheduled Time
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'SB': //Not Scheduled Branch/Department
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'SC': //Not Scheduled Job/Task
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'ST': //Not Scheduled Punch Tag by
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'O1': //Over Daily Time
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => ( 3600 * 8 ),
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'O2': //Over Weekly Time
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => ( 3600 * 40 ),
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'M1': //Missing In Punch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 30,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 20,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'M2': //Missing Out Punch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 30,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 20,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'M3': //Missing Lunch In/Out Punch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 30,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 18,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'M4': //Missing Break In/Out Punch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 30,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 17,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'C1': //Missed Check-in
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 25,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 7200,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'D1': //No Job Or Task
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'D2': //Not allowed on branch.
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 20,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'D3': //Not allowed on department.
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 20,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'L1': //Long Lunch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'L2': //Short Lunch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 10,
							'demerit'                 => 5,
							'grace'                   => 900,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'L3': //No Lunch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 7,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'L4': //Late Lunch
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 7,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'B1': //Long Break
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 300,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'B2': //Short Break
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 10,
							'demerit'                 => 5,
							'grace'                   => 300,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'B3': //Too Many Breaks
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'B4': //Too Few Breaks
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'B5': //No Break
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 20,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'V1': //TimeSheet Not Verified
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 25,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => ( 48 * 3600 ), //48hrs grace period
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'J1': //Not allowed on job
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 20,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'J2': //Not allowed on task
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 20,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'J3': //Job completed
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 20,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'J4': //No Job Or Task
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'G1': //GEOFence
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 25,
							'email_notification_id'   => 100,
							'punch_notification_id'   => 0,
							'demerit'                 => 15,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'T1': //Punch Tag Not Allowed
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => true,
							'severity_id'             => 20,
							'email_notification_id'   => 20,
							'punch_notification_id'   => 0,
							'demerit'                 => 2,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				case 'T2': //No Punch Tag
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 5,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
				default:
					$retarr[$type_id] = [
							'id'                      => -1,
							'type_id'                 => $type_id,
							'name'                    => $type_options[$type_id],
							'active'                  => false,
							'severity_id'             => 10,
							'email_notification_id'   => 0,
							'punch_notification_id'   => 0,
							'demerit'                 => 0,
							'grace'                   => 0,
							'is_enabled_grace'        => $this->isEnabledGrace( $type_id ),
							'watch_window'            => 0,
							'is_enabled_watch_window' => $this->isEnabledWatchWindow( $type_id ),
							'is_enabled_punch_notice' => $this->isEnabledPunchNotice( $type_id ),
					];
					break;
			}
		}
		unset( $exception_name ); // code standards

		return $retarr;
	}

	/**
	 * @return bool
	 */
	function getName() {
		return Option::getByKey( $this->getType(), $this->getTypeOptions( getTTProductEdition() ) );
	}

	/**
	 * @param int $product_edition
	 * @return bool
	 */
	function getTypeOptions( $product_edition = 10 ) {
		$options = $this->getOptions( 'type' );

		if ( getTTProductEdition() <= TT_PRODUCT_PROFESSIONAL || $product_edition <= TT_PRODUCT_PROFESSIONAL ) {
			$corporate_exceptions = [ 'J1', 'J2', 'J3', 'J4', 'SC', 'ST', 'C1', 'G1', 'T1', 'T2', 'D2', 'D3' ];
			foreach ( $corporate_exceptions as $corporate_exception ) {
				unset( $options[$corporate_exception] );
			}
		}

		return $options;
	}

	/**
	 * @return bool|string
	 */
	function getType() {
		return (string)$this->getGenericDataValue( 'type_id' );//Should not be cast to int.
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setType( $value ) {
		$value = trim( $value );

		return $this->setGenericDataValue( 'type_id', $value );
	}

	/**
	 * @return bool|int
	 */
	function getSeverity() {
		return $this->getGenericDataValue( 'severity_id' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setSeverity( $value ) {
		$value = (int)trim( $value );

		return $this->setGenericDataValue( 'severity_id', $value );
	}

	/**
	 * @return bool|mixed
	 */
	function getWatchWindow() {
		return $this->getGenericDataValue( 'watch_window' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setWatchWindow( $value ) {
		$value = trim( $value );

		return $this->setGenericDataValue( 'watch_window', $value );
	}

	/**
	 * @return bool|mixed
	 */
	function getGrace() {
		return $this->getGenericDataValue( 'grace' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setGrace( $value ) {
		$value = trim( $value );

		return $this->setGenericDataValue( 'grace', $value );
	}

	/**
	 * @return bool|mixed
	 */
	function getDemerit() {
		return $this->getGenericDataValue( 'demerit' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setDemerit( $value ) {
		return $this->setGenericDataValue( 'demerit', trim( $value ) );
	}

	/**
	 * @return bool|int
	 */
	function getPunchNotification() {
		return $this->getGenericDataValue( 'punch_notification_id' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setPunchNotification( $value ) {
		$value = (int)trim( $value );

		return $this->setGenericDataValue( 'punch_notification_id', $value );
	}

	/**
	 * @return bool|int
	 */
	function getEmailNotification() {
		return $this->getGenericDataValue( 'email_notification_id' );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setEmailNotification( $value ) {
		$value = (int)trim( $value );

		return $this->setGenericDataValue( 'email_notification_id', $value );
	}

	/**
	 * @return bool
	 */
	function getActive() {
		return $this->fromBool( $this->getGenericDataValue( 'active' ) );
	}

	/**
	 * @param $value
	 * @return bool
	 */
	function setActive( $value ) {
		return $this->setGenericDataValue( 'active', $this->toBool( $value ) );
	}

	/**
	 * @param null $code
	 * @return bool
	 */
	function isEnabledGrace( $code = null ) {
		if ( $code == null ) {
			$code = $this->getType();
		}

		if ( in_array( $code, $this->enable_grace ) ) {
			return true;
		}

		return false;
	}

	/**
	 * @param null $code
	 * @return bool
	 */
	function isEnabledWatchWindow( $code = null ) {
		if ( $code == null ) {
			$code = $this->getType();
		}

		if ( in_array( $code, $this->enable_watch_window ) ) {
			return true;
		}

		return false;
	}

	/**
	 * @param null $code
	 * @return bool
	 */
	function isEnabledPunchNotice( $code = null ) {
		if ( $code == null ) {
			$code = $this->getType();
		}

		if ( in_array( $code, $this->enable_punch_notice ) ) {
			return true;
		}

		return false;
	}

	/**
	 * @param $code
	 * @return bool
	 */
	static function isPreMature( $code ) {
		if ( in_array( $code, self::$premature_exceptions ) ) {
			return true;
		}

		return false;
	}

	/**
	 * This function needs to determine which new exceptions to create, and which old exceptions are no longer valid and to delete.
	 * @param $existing_exceptions
	 * @param $current_exceptions
	 * @return array
	 */
	static function diffExistingAndCurrentExceptions( $existing_exceptions, $current_exceptions ) {
		//Debug::Arr($existing_exceptions, 'Existing Exceptions: ', __FILE__, __LINE__, __METHOD__, 10);
		//Debug::Arr($current_exceptions, 'Current Exceptions: ', __FILE__, __LINE__, __METHOD__, 10);

		if ( is_array( $existing_exceptions ) && count( $existing_exceptions ) == 0 ) {
			//No existing exceptions, nothing to delete or compare, just create new exceptions.
			return [ 'create_exceptions' => $current_exceptions, 'delete_exceptions' => [] ];
		}

		$delete_exceptions = [];
		if ( is_array( $current_exceptions ) && count( $current_exceptions ) == 0 ) {
			//No current exceptions, delete all existing exceptions.
			foreach ( $existing_exceptions as $existing_exception ) {
				$delete_exceptions[] = $existing_exception['id'];
			}

			return [ 'create_exceptions' => [], 'delete_exceptions' => $delete_exceptions ];
		}

		$new_exceptions = $current_exceptions; //Copy array so we can work from the copy.

		//Remove any current exceptions that already exist as existing exceptions.
		foreach ( $current_exceptions as $current_key => $current_exception ) {
			foreach ( $existing_exceptions as $existing_exception ) {
				//Need to match all elements except 'id'.
				if ( TTUUID::castUUID( $current_exception['exception_policy_id'] ) == TTUUID::castUUID( $existing_exception['exception_policy_id'] )
						&&
						(int)$current_exception['type_id'] == (int)$existing_exception['type_id']
						&&
						TTUUID::castUUID( $current_exception['user_id'] ) == TTUUID::castUUID( $existing_exception['user_id'] )
						&&
						(int)$current_exception['date_stamp'] == (int)$existing_exception['date_stamp']
						&&
						TTUUID::castUUID( $current_exception['punch_control_id'] ) == TTUUID::castUUID( $existing_exception['punch_control_id'] )
						&&
						TTUUID::castUUID( $current_exception['punch_id'] ) == TTUUID::castUUID( $existing_exception['punch_id'] )
				) {
					//Debug::text('Removing current exception that matches existing exception: '. $current_key, __FILE__, __LINE__, __METHOD__, 10);
					unset( $new_exceptions[$current_key] );
				} //else { //Debug::text('NOT Removing current exception that matches existing exception: Current: '. $current_key .' Existing: '. $existing_key, __FILE__, __LINE__, __METHOD__, 10);
			}
		}

		//Mark any existing exceptions that are not in the current exception list for deletion.
		$delete_exceptions = [];
		$matched_key = [];

		$total_current_exceptions = count( $current_exceptions );
		foreach ( $existing_exceptions as $existing_exception ) {
			$match_count = $total_current_exceptions;

			foreach ( $current_exceptions as $current_key => $current_exception ) {
				if ( !isset( $matched_key[$current_key] ) && ( $current_exception['exception_policy_id'] == $existing_exception['exception_policy_id'] && $current_exception['type_id'] == $existing_exception['type_id'] && $current_exception['user_id'] == $existing_exception['user_id'] && $current_exception['date_stamp'] == $existing_exception['date_stamp'] && $current_exception['punch_control_id'] == $existing_exception['punch_control_id'] && $current_exception['punch_id'] == $existing_exception['punch_id'] ) ) {
					//Make sure we don't match the same exception twice and allow duplicate exceptions to persist.
					//This fixes the issue where two S1 exceptions may have been created, but only one should really exist.
					$matched_key[$current_key] = true;
				} else {
					$match_count--;
				}

				/*
				if ( !( $current_exception['exception_policy_id'] == $existing_exception['exception_policy_id'] AND $current_exception['type_id'] == $existing_exception['type_id'] AND $current_exception['user_id'] == $existing_exception['user_id'] AND $current_exception['date_stamp'] == $existing_exception['date_stamp'] AND $current_exception['punch_control_id'] == $existing_exception['punch_control_id'] AND $current_exception['punch_id'] == $existing_exception['punch_id'] ) ) {
					Debug::text('a1Determining if we should delete this exception... Match Count: '. $match_count .' Total: '. $total_current_exceptions .' Existing Key: '. $existing_key, __FILE__, __LINE__, __METHOD__, 10);
					$match_count--;
				}
				*/
				//Debug::text('aDetermining if we should delete this exception... Match Count: '. $match_count .' Total: '. $total_current_exceptions .' Existing Key: '. $existing_key, __FILE__, __LINE__, __METHOD__, 10);
			}

			if ( $match_count == 0 ) {
				//Debug::text('bDetermining if we should delete this exception... Match Count: '. $match_count .' Total: '. $total_current_exceptions .' Existing Key: '. $existing_key, __FILE__, __LINE__, __METHOD__, 10);
				$delete_exceptions[] = $existing_exception['id'];
			}
		}

		$retarr = [ 'create_exceptions' => $new_exceptions, 'delete_exceptions' => $delete_exceptions ];

		//Debug::Arr($retarr, 'RetArr Exceptions: ', __FILE__, __LINE__, __METHOD__, 10);
		return $retarr;
	}

	/**
	 * @param string $exception_policy_control_id UUID
	 * @param int $type_id
	 * @param string $id                          UUID
	 * @return bool
	 */
	function isUnique( $exception_policy_control_id, $type_id, $id ) {
		$ph = [
				'exception_policy_control_id' => $exception_policy_control_id,
				'type_id'                     => trim( strtoupper( $type_id ) ),
				'id'                          => TTUUID::castUUID( $id ),
		];

		$query = 'select id from ' . $this->getTable() . ' where exception_policy_control_id = ? AND type_id = ? AND id != ? AND deleted = 0';
		$id = $this->db->GetOne( $query, $ph );
		Debug::Arr( $id, 'Unique Exception Control ID: ' . $exception_policy_control_id . ' Type ID: ' . $type_id, __FILE__, __LINE__, __METHOD__, 10 );

		if ( $id === false ) {
			return true;
		} else {
			if ( $id == $this->getId() ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @param bool $ignore_warning
	 * @return bool
	 */
	function Validate( $ignore_warning = true ) {
		//
		// BELOW: Validation code moved from set*() functions.
		//
		// Exception Policy Control
		$epclf = TTnew( 'ExceptionPolicyControlListFactory' ); /** @var ExceptionPolicyControlListFactory $epclf */
		$this->Validator->isResultSetWithRows( 'exception_policy_control',
											   $epclf->getByID( $this->getExceptionPolicyControl() ),
											   TTi18n::gettext( 'Exception Policy Control is invalid' )
		);
		// Type
		$this->Validator->inArrayKey( 'type',
									  $this->getType(),
									  TTi18n::gettext( 'Incorrect Type' ),
									  $this->getOptions( 'type' )
		);
		// Severity
		$this->Validator->inArrayKey( 'severity',
									  $this->getSeverity(),
									  TTi18n::gettext( 'Incorrect Severity' ),
									  $this->getOptions( 'severity' )
		);
		// Watch Window
		if ( $this->getWatchWindow() != '' ) {
			$this->Validator->isNumeric( 'watch_window',
										 $this->getWatchWindow(),
										 TTi18n::gettext( 'Incorrect Watch Window' )
			);
		}
		// Grace value
		if ( $this->getGrace() != '' ) {
			$this->Validator->isNumeric( 'grace',
										 $this->getGrace(),
										 TTi18n::gettext( 'Incorrect grace value' )
			);
		}
		// Demerit value
		if ( $this->getDemerit() != '' ) {
			$this->Validator->isNumeric( 'demerit',
										 $this->getDemerit(),
										 TTi18n::gettext( 'Incorrect demerit value' )
			);
			$this->Validator->isGreaterThan( 'demerit',
											 $this->getDemerit(),
											 TTi18n::gettext( 'Demerit must be greater than -9999' ),
											 -9999
			);
			$this->Validator->isLessThan( 'demerit',
										  $this->getDemerit(),
										  TTi18n::gettext( 'Demerit must be less than 9999' ),
										  9999
			);
		}
		// Email Notification
		$this->Validator->inArrayKey( 'email_notification',
									  $this->getEmailNotification(),
									  TTi18n::gettext( 'Incorrect Email Notification' ),
									  $this->getOptions( 'email_notification' )
		);

		// Punch Notification
		$this->Validator->inArrayKey( 'punch_notification',
									  $this->getPunchNotification(),
									  TTi18n::gettext( 'Incorrect Punch Notification' ),
									  $this->getOptions( 'punch_notification' )
		);


		//
		// ABOVE: Validation code moved from set*() functions.
		//
		if ( $this->isUnique( $this->getExceptionPolicyControl(), $this->getType(), $this->getID() ) == false ) {
			$this->Validator->isTrue( 'type_id',
									  false,
									  TTi18n::gettext( 'Duplicate exception already exists' ) );
		}

		return true;
	}

	/**
	 * @param $data
	 * @return bool
	 */
	function setObjectFromArray( $data ) {
		if ( is_array( $data ) ) {
			$variable_function_map = $this->getVariableToFunctionMap();
			foreach ( $variable_function_map as $key => $function ) {
				if ( isset( $data[$key] ) ) {

					$function = 'set' . $function;
					switch ( $key ) {
						case 'demerit':
							$this->$function( TTi18n::parseFloat( $data[$key] ) );
							break;
						default:
							if ( method_exists( $this, $function ) ) {
								$this->$function( $data[$key] );
							}
							break;
					}
				}
			}

			$this->setCreatedAndUpdatedColumns( $data );

			return true;
		}

		return false;
	}

	/**
	 * @param null $include_columns
	 * @return array
	 * @noinspection PhpMissingBreakStatementInspection
	 */
	function getObjectAsArray( $include_columns = null ) {
		$data = [];
		$variable_function_map = $this->getVariableToFunctionMap();
		if ( is_array( $variable_function_map ) ) {
			foreach ( $variable_function_map as $variable => $function_stub ) {
				if ( $include_columns == null || ( isset( $include_columns[$variable] ) && $include_columns[$variable] == true ) ) {

					$function = 'get' . $function_stub;
					switch ( $variable ) {
						case 'demerit':
							$data[$variable] = Misc::removeTrailingZeros( $this->$function(), 2 );
							break;

						//These must go immediately above the default case, as the missing break is intentional.
						case 'is_enabled_watch_window':
						case 'is_enabled_grace':
						case 'is_enabled_punch_notice':
							$function = str_replace( '_', '', $variable );
							//Must not place a break here, as we just change the funtion name for the below default case.
						default:
							if ( method_exists( $this, $function ) ) {
								$data[$variable] = $this->$function();
							}
							break;
					}
				}
			}
			$this->getCreatedAndUpdatedColumns( $data, $include_columns );
		}

		return $data;
	}

	/**
	 * This is called for every record everytime, and doesn't help much because of that.
	 * This has to be enabled to properly log modifications though.
	 * @param $log_action
	 * @return bool
	 */
	function addLog( $log_action ) {
		return TTLog::addEntry( $this->getExceptionPolicyControl(), $log_action, TTi18n::getText( 'Exception Policy' ) . ' - ' . TTi18n::getText( 'Type' ) . ': ' . Option::getByKey( $this->getType(), $this->getOptions( 'type' ) ), null, $this->getTable(), $this );
	}
}

?>