<?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\PayStub
 */
class PayStubEntryAccountFactory extends Factory {
	protected $table = 'pay_stub_entry_account';
	protected $pk_sequence_name = 'pay_stub_entry_account_id_seq'; //PK Sequence name

	var $pay_stub_entry_account_link_obj = null;

	/**
	 * @param $name
	 * @param null $parent
	 * @return array|null
	 */
	function _getFactoryOptions( $name, $parent = null ) {

		$retval = null;
		switch ( $name ) {
			case 'status':
				$retval = [
						10 => TTi18n::gettext( 'Enabled' ),
						20 => TTi18n::gettext( 'Disabled' ),
				];
				break;
			case 'type':
				$retval = [
						10 => TTi18n::gettext( 'Earning' ),
						20 => TTi18n::gettext( 'Employee Deduction' ),
						30 => TTi18n::gettext( 'Employer Deduction' ),
						40 => TTi18n::gettext( 'Total' ),
						50 => TTi18n::gettext( 'Accrual' ),
						//60 => TTi18n::gettext('Advance Earning'),
						//65 => TTi18n::gettext('Advance Deduction'),
						80 => TTi18n::gettext( 'Miscellaneous' ), //Neither earnings or deductions, just for record keeping, ie: Employer parts of RRSP's, or other items that employees need to see.

				];
				break;
			case 'accrual_type':
				$retval = [
					//May need to add 6 more options here for every permutation of Add/Subtract across just ER Ded and Misc. Since Earning & ER Deduction should always be opposite.
					10 => TTi18n::gettext( 'Earning/Misc Subtracts, EE/ER Deduction Adds' ),
					//12 => TTi18n::gettext('Earning/ER Deduction Subtracts, EE Deduction/Misc Adds'),
					//14 => TTi18n::gettext('Earning/ER Deduction/Misc Subtracts, EE Deduction Adds'),
					//16 => TTi18n::gettext('Earning Subtracts, EE & ER Deduction/Misc Adds'),
					20 => TTi18n::gettext( 'Earning/Misc Adds, EE/ER Deduction Subtracts' ),
					//22 => TTi18n::gettext('Earning/ER Deduction Adds, EE Deduction/Misc Subtracts'),
					//24 => TTi18n::gettext('Earning/ER Deduction/Misc Adds, EE Subtracts'),
					//26 => TTi18n::gettext('Earning Adds, EE & ER Deduction/Misc Subtracts'),
				];
				break;
			case 'type_calculation_order':
				//If any of these exceed 3 digits, need to update CalculatePayStub->getDeductionObjectSortValue() to handle more digits.
				$retval = [
						10 => 40,
						20 => 50,
						30 => 60,
						40 => 70,
						50 => 30,
						60 => 10,
						65 => 20,
						80 => 65,
				];
				break;
			case 'columns':
				$retval = [
						'-1010-status' => TTi18n::gettext( 'Status' ),
						'-1020-type'   => TTi18n::gettext( 'Type' ),
						'-1030-name'   => TTi18n::gettext( 'Name' ),

						'-1140-ps_order'       => TTi18n::gettext( 'Order' ),
						'-1150-debit_account'  => TTi18n::gettext( 'Debit Account' ),
						'-1150-credit_account' => TTi18n::gettext( 'Credit Account' ),

						'-1900-in_use' => TTi18n::gettext( 'In Use' ),

						'-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;
			case 'list_columns':
				$retval = Misc::arrayIntersectByKey( $this->getOptions( 'default_display_columns' ), Misc::trimSortPrefix( $this->getOptions( 'columns' ) ) );
				break;
			case 'default_display_columns': //Columns that are displayed by default.
				$retval = [
						'status',
						'type',
						'name',
						'ps_order',
						'debit_account',
						'credit_account',
				];
				break;
			case 'unique_columns': //Columns that are unique, and disabled for mass editing.
				$retval = [
						'name',
				];
				break;
			case 'linked_columns': //Columns that are linked together, mainly for Mass Edit, if one changes, they all must.
				$retval = [
						'type',
						'accrual',
				];
				break;
		}

		return $retval;
	}

	/**
	 * @param $data
	 * @return array
	 */
	function _getVariableToFunctionMap( $data ) {
		$variable_function_map = [
				'id'                                => 'ID',
				'company_id'                        => 'Company',
				'status_id'                         => 'Status',
				'status'                            => false,
				'type_id'                           => 'Type',
				'type'                              => false,
				'name'                              => 'Name',
				'ps_order'                          => 'Order',
				'debit_account'                     => 'DebitAccount',
				'credit_account'                    => 'CreditAccount',
				'accrual_pay_stub_entry_account_id' => 'Accrual',
				'accrual_type_id'                   => 'AccrualType',
				'in_use'                            => false,
				'deleted'                           => 'Deleted',
		];

		return $variable_function_map;
	}

	/**
	 * @return bool|null
	 */
	function getPayStubEntryAccountLinkObject() {
		if ( is_object( $this->pay_stub_entry_account_link_obj ) ) {
			return $this->pay_stub_entry_account_link_obj;
		} else {
			$pseallf = TTnew( 'PayStubEntryAccountLinkListFactory' ); /** @var PayStubEntryAccountLinkListFactory $pseallf */
			$pseallf->getByCompanyId( $this->getCompany() );
			if ( $pseallf->getRecordCount() > 0 ) {
				$this->pay_stub_entry_account_link_obj = $pseallf->getCurrent();

				return $this->pay_stub_entry_account_link_obj;
			}

			return false;
		}
	}

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

	/**
	 * @param string $value UUID
	 * @return bool
	 */
	function setCompany( $value ) {
		$value = TTUUID::castUUID( $value );
		Debug::Text( 'Company ID: ' . $value, __FILE__, __LINE__, __METHOD__, 10 );

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

	/**
	 * @return int
	 */
	function getStatus() {
		return $this->getGenericDataValue( 'status_id' );
	}

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

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

	/**
	 * Returns the order in which accounts should be calculated
	 * given a circular dependency scenario
	 * @return bool
	 */
	function getTypeCalculationOrder() {
		if ( $this->getType() !== false ) {
			$order_arr = $this->getOptions( 'type_calculation_order' );

			if ( isset( $order_arr[$this->getType()] ) ) {
				return $order_arr[$this->getType()];
			}
		}

		return false;
	}

	/**
	 * @param string $id UUID
	 * @return bool
	 */
	function isInUse( $id ) {
		$pslf = new PayStubListFactory();
		$pself = new PayStubEntryListFactory();
		$psalf = new PayStubAmendmentListFactory();

		$ph = [
				'pay_stub_account_id'  => (string)$id,
				'pay_stub_account_idb' => (string)$id,
		];

		$query = '
					select	a.id
					from	' . $pself->getTable() . ' as a
						LEFT JOIN ' . $pslf->getTable() . ' as b ON ( a.pay_stub_id = b.id )
					where	a.pay_stub_entry_name_id = ?
						AND ( a.deleted = 0 AND b.deleted = 0 )
					UNION ALL
					select	a.id
					from	' . $psalf->getTable() . ' as a
					where	a.pay_stub_entry_name_id = ? AND a.deleted = 0
					LIMIT 1';

		$retval = $this->db->GetOne( $query, $ph );
		Debug::Arr( $retval, 'In Use... ID: ' . $id, __FILE__, __LINE__, __METHOD__, 10 );

		if ( $retval === false ) {
			return false;
		}

		return true;
	}

	/**
	 * Check to see if this PS account is linked in the PayStubEntryAccountLink so we can prevent it from being deleted if it is.
	 * @param string $id UUID
	 * @return bool
	 */
	function isInPSEAccountLink( $id ) {
		$id = (string)$id; //UUID

		$pseal_obj = $this->getPayStubEntryAccountLinkObject();
		if ( is_object( $pseal_obj ) ) {
			if ( in_array( $id, [ (string)$pseal_obj->getRegularTime(), (string)$pseal_obj->getTotalGross(), (string)$pseal_obj->getTotalEmployeeDeduction(), (string)$pseal_obj->getTotalEmployerDeduction(), (string)$pseal_obj->getTotalNetPay() ], true ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Check to see if this PS account is linked by another PS account as an accrual.
	 * @param string $id UUID
	 * @return bool
	 */
	function isInPSEAccountAccrual( $id ) {
		$psealf = new PayStubEntryAccountListFactory();

		$ph = [
				'pay_stub_account_id' => (string)$id,
		];

		$query = '
					select	a.id
					from	' . $psealf->getTable() . ' as a
					where	a.accrual_pay_stub_entry_account_id = ?
						AND ( a.deleted = 0 )
					LIMIT 1';

		$retval = $this->db->GetOne( $query, $ph );
		Debug::Arr( $retval, 'In Use... ID: ' . $id, __FILE__, __LINE__, __METHOD__, 10 );

		if ( $retval === false ) {
			return false;
		}

		return true;
	}

	/**
	 * @param string $id UUID
	 * @return bool
	 */
	function getCurrentType( $id ) {
		$psealf = TTNew( 'PayStubEntryAccountListFactory' ); /** @var PayStubEntryAccountListFactory $psealf */
		$psealf->getByIdAndCompanyId( $id, $this->getCompany() );
		if ( $psealf->getRecordCount() == 1 ) {
			$retval = $psealf->getCurrent()->getType();
			Debug::Text( 'Current Type: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );

			return $retval;
		}

		return false;
	}

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

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

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

	/**
	 * @param $name
	 * @return bool
	 */
	function isUniqueName( $name ) {
		$name = trim( $name );
		if ( $name == '' ) {
			return false;
		}

		$ph = [
				'company_id' => TTUUID::castUUID( $this->getCompany() ),
				'type_id'    => (int)$this->getType(),
				'name'       => TTi18n::strtolower( $name ),
		];

		$query = 'select id from ' . $this->getTable() . ' where company_id = ? AND type_id = ? AND lower(name) = ? AND deleted=0';
		$id = $this->db->GetOne( $query, $ph );
		Debug::Arr( $id, 'Unique Pay Stub Account: ' . $name, __FILE__, __LINE__, __METHOD__, 10 );

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

		return false;
	}

	/**
	 * @return bool|string
	 */
	function getName() {
		if ( $this->getGenericDataValue( 'name' ) !== false ) {
			/*I18n:	apply gettext in the result of this function
					to be use in the getByIdArray() function in
					the PayStubEntryAccountListFactory.class.php
					file.
			*/
			return TTi18n::gettext( $this->getGenericDataValue( 'name' ) );
		}

		return false;
	}

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

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

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

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

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

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

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

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

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

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

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

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

	/**
	 * @param string $value UUID
	 * @return bool
	 */
	function setAccrual( $value ) {
		$value = TTUUID::castUUID( $value );
		Debug::Text( 'ID: ' . $value, __FILE__, __LINE__, __METHOD__, 10 );

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

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

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

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

	/**
	 * @param bool $ignore_warning
	 * @return bool
	 */
	function Validate( $ignore_warning = true ) {
		//
		// BELOW: Validation code moved from set*() functions.
		//
		// Company
		$clf = TTnew( 'CompanyListFactory' ); /** @var CompanyListFactory $clf */
		$this->Validator->isResultSetWithRows( 'company',
											   $clf->getByID( $this->getCompany() ),
											   TTi18n::gettext( 'Company is invalid' )
		);
		// Status
		if ( $this->getStatus() !== false ) {
			$this->Validator->inArrayKey( 'status',
										  $this->getStatus(),
										  TTi18n::gettext( 'Incorrect Status' ),
										  $this->getOptions( 'status' )
			);
		}
		// Type
		if ( $this->getType() !== false ) {
			$this->Validator->inArrayKey( 'type_id',
										  $this->getType(),
										  TTi18n::gettext( 'Incorrect Type' ),
										  $this->getOptions( 'type' )
			);
			if ( $this->Validator->isError( 'type_id' ) == false ) {
				Debug::Text( 'Type: ' . $this->getType() . ' isNew: ' . (int)$this->isNew(), __FILE__, __LINE__, __METHOD__, 10 );
				if ( $this->isNew() == false && $this->getCurrentType( $this->getId() ) != $this->getType() && $this->isInUse( $this->getId() ) == true ) {
					$this->Validator->isTrue( 'type_id',
											  false,
											  TTi18n::gettext( 'Type cannot be modified when Pay Stub Account is in use' )
					);
				}
			}
		}
		// Name
		if ( $this->getName() !== false ) {
			$this->Validator->isLength( 'name',
										$this->getName(),
										TTi18n::gettext( 'Name is too short or too long' ),
										2,
										100
			);
			if ( $this->Validator->isError( 'name' ) == false ) {
				$this->Validator->isTrue( 'name',
										  $this->isUniqueName( $this->getName() ),
										  TTi18n::gettext( 'Name is already in use' )
				);
			}
		}
		// Order
		if ( $this->Validator->getValidateOnly() == false && $this->getOrder() == '' ) {
			$this->Validator->isTRUE( 'ps_order',
									  false,
									  TTi18n::gettext( 'Order must be specified' )
			);
		}
		if ( $this->getOrder() != '' && $this->Validator->isError( 'ps_order' ) == false ) {
			$this->Validator->isNumeric( 'ps_order',
										 $this->getOrder(),
										 TTi18n::gettext( 'Invalid Order' )
			);
		}
		// Debit Account
		if ( $this->getDebitAccount() != '' ) {
			$this->Validator->isLength( 'debit_account',
										$this->getDebitAccount(),
										TTi18n::gettext( 'Invalid Debit Account' ),
										2,
										1000
			);
		}
		// Credit Account
		if ( $this->getCreditAccount() != '' ) {
			$this->Validator->isLength( 'credit_account',
										$this->getCreditAccount(),
										TTi18n::gettext( 'Invalid Credit Account' ),
										2,
										1000
			);
		}
		// Accrual Account
		if ( $this->getAccrual() !== false && $this->getAccrual() != TTUUID::getZeroID() ) {
			$psealf = TTnew( 'PayStubEntryAccountListFactory' ); /** @var PayStubEntryAccountListFactory $psealf */
			$psealf->getByID( $this->getAccrual() );
			if ( $psealf->getRecordCount() > 0 ) {
				if ( $psealf->getCurrent()->getType() != 50 ) {
					//Reset Result set so an error occurs.
					$psealf = TTnew( 'PayStubEntryAccountListFactory' ); /** @var PayStubEntryAccountListFactory $psealf */
				}
			}
			$this->Validator->isResultSetWithRows( 'accrual_pay_stub_entry_account_id',
												   $psealf,
												   TTi18n::gettext( 'Accrual Account is invalid' )
			);
		}
		// Accrual Type
		if ( $this->getAccrualType() !== false ) {
			$this->Validator->inArrayKey( 'accrual_type_id',
										  $this->getAccrualType(),
										  TTi18n::gettext( 'Incorrect Accrual Type' ),
										  $this->getOptions( 'accrual_type' )
			);
		}

		//
		// ABOVE: Validation code moved from set*() functions.
		//

		if ( $this->getType() == 50 ) {
			//If the PSE account is an accrual, it can't link to one as well.
			$this->setAccrual( null );
		}

		//Make sure this account doesn't point to itself as an accrual.
		if ( $this->isNew() == false && $this->getAccrual() == $this->getId() ) {
			$this->Validator->isTrue( 'accrual',
									  false,
									  TTi18n::gettext( 'Accrual account is invalid' )
			);
		}

		if ( $this->getDeleted() == true ) {
			if ( $this->getType() == 10 || $this->getType() == 40 ) { //10=Earning (for Regular Time PSA for salaried employees) 40=Total
				$this->Validator->isTRUE( 'in_use',
						( $this->isInPSEAccountLink( $this->getId() ) == true ? false : true ),
										  TTi18n::gettext( 'This pay stub account is currently desiginated as a critical system account, unable to delete' ) );
			}

			if ( $this->getType() == 50 ) { //50=Accrual
				$this->Validator->isTRUE( 'in_use',
						( $this->isInPSEAccountAccrual( $this->getId() ) == true ? false : true ),
										  TTi18n::gettext( 'This pay stub account is currently linked to others as an accrual account, unable to delete' ) );
			}

			//Check to make sure nothing else references this policy, so we can be sure its okay to delete it.
			// The isInUse() check in preSave() already looks for pay stubs, pay stub amendments, and if those exist it should never get here.
			$pclf = TTnew( 'PayCodeListFactory' ); /** @var PayCodeListFactory $pclf */
			$pclf->getByCompanyIdAndPayStubEntryAccountID( $this->getCompany(), $this->getId(), 1 );
			if ( $pclf->getRecordCount() > 0 ) {
				$this->Validator->isTRUE( 'in_use',
										  false,
										  TTi18n::gettext( 'This account is currently in use' ) . ' ' . TTi18n::gettext( 'by pay codes' ) );
			}

			$cdlf = TTnew( 'CompanyDeductionListFactory' ); /** @var CompanyDeductionListFactory $cdlf */
			$cdlf->getByCompanyIdAndPayStubEntryAccountId( $this->getCompany(), $this->getId(), 1 );
			if ( $cdlf->getRecordCount() > 0 ) {
				$this->Validator->isTRUE( 'in_use',
										  false,
										  TTi18n::gettext( 'This account is currently in use' ) . ' ' . TTi18n::gettext( 'by Tax/Deductions' ) );
			}
		}

		//Make sure PS order is correct, in that types can't be separated by total or accrual accounts.
		if ( $this->getDeleted() == false && $this->getOrder() != '' && $this->Validator->isError( 'ps_order' ) == false ) {
			$pseallf = TTnew( 'PayStubEntryAccountLinkListFactory' ); /** @var PayStubEntryAccountLinkListFactory $pseallf */
			$pseallf->getByCompanyId( $this->getCompany() );
			if ( $pseallf->getRecordCount() > 0 ) {
				$pseal_obj = $pseallf->getCurrent();

				$psea_map = [];
				$psealf = TTnew( 'PayStubEntryAccountListFactory' ); /** @var PayStubEntryAccountListFactory $psealf */
				$psealf->getByCompanyIdAndTypeId( $this->getCompany(), 40 );
				if ( $psealf->getRecordCount() > 0 ) {
					foreach ( $psealf as $psea_obj ) {
						$psea_map[$psea_obj->getId()] = $psea_obj->getOrder();
					}
					unset( $psea_obj );
				}

				switch ( $this->getType() ) {
					case 10: //Earning
						//Greater the 0, less then Total Gross Account
						if ( isset( $psea_map[$pseal_obj->getTotalGross()] ) ) {
							$min_ps_order = 0;
							$max_ps_order = $psea_map[$pseal_obj->getTotalGross()];
						}
						break;
					case 20: //EE Deduction
						//Greater then Total Gross Account, less then Total Employee Deduction
						if ( isset( $psea_map[$pseal_obj->getTotalGross()] ) && isset( $psea_map[$pseal_obj->getTotalEmployeeDeduction()] ) ) {
							$min_ps_order = $psea_map[$pseal_obj->getTotalGross()];
							$max_ps_order = $psea_map[$pseal_obj->getTotalEmployeeDeduction()];
						}
						break;
					case 30: //ER Deduction
						//Greater then Net Pay Account, less then Total Employer Deduction
						if ( isset( $psea_map[$pseal_obj->getTotalNetPay()] ) && isset( $psea_map[$pseal_obj->getTotalEmployerDeduction()] ) ) {
							$min_ps_order = $psea_map[$pseal_obj->getTotalNetPay()];
							$max_ps_order = $psea_map[$pseal_obj->getTotalEmployerDeduction()];
						}
						break;
					case 50: //Accrual
					case 80: //Misc
						//Greater then Total Employer Deduction
						if ( isset( $psea_map[$pseal_obj->getTotalEmployerDeduction()] ) ) {
							$min_ps_order = $psea_map[$pseal_obj->getTotalEmployerDeduction()];
							$max_ps_order = 10001;
						}
						break;
				}

				if ( isset( $min_ps_order ) && isset( $max_ps_order ) && ( $this->getOrder() <= $min_ps_order || $this->getOrder() >= $max_ps_order ) ) {
					Debug::text( 'PS Order... Min: ' . $min_ps_order . ' Max: ' . $max_ps_order, __FILE__, __LINE__, __METHOD__, 10 );
					$this->Validator->isTrue( 'ps_order',
											  false,
											  TTi18n::gettext( 'Order is invalid for this type of account, it must be between' ) . ' ' . ( $min_ps_order + 1 ) . ' ' . TTi18n::gettext( 'and' ) . ' ' . ( $max_ps_order - 1 ) );
				}
			}
		}

		return true;
	}

	/**
	 * @param string $company_id  UUID
	 * @param string[] $src_ids   UUID
	 * @param string $dst_id      UUID
	 * @param int $effective_date EPOCH
	 * @return bool
	 */
	function migrate( $company_id, $src_ids, $dst_id, $effective_date ) {
		$dst_id = TTUUID::castUUID( $dst_id );
		$src_ids = array_unique( (array)$src_ids );

		if ( empty( $dst_id ) || $dst_id == TTUUID::getZeroID() ) {
			return false;
		}

		Debug::Arr( $src_ids, 'Attempting to migrate to: ' . $dst_id, __FILE__, __LINE__, __METHOD__, 10 );

		$current_epoch = time();

		$pself = TTNew( 'PayStubEntryListFactory' ); /** @var PayStubEntryListFactory $pself */

		//Loop over just ACTIVE employees.
		$ulf = TTNew( 'UserListFactory' ); /** @var UserListFactory $ulf */

		//Get names of all Pay Stub Accounts
		$psealf = TTNew( 'PayStubEntryAccountListFactory' ); /** @var PayStubEntryAccountListFactory $psealf */
		$psealf->getByCompanyId( $company_id );
		$pay_stub_account_arr = $psealf->getArrayByListFactory( $psealf, false );

		$ulf->StartTransaction();

		$ulf->getByCompanyIdAndStatus( $company_id, 10 );
		if ( is_array( $pay_stub_account_arr ) && count( $pay_stub_account_arr ) > 0 && $ulf->getRecordCount() > 0 ) {
			foreach ( $ulf as $u_obj ) {
				//Get current YTD values assigned to the src_ids.
				foreach ( $src_ids as $src_id ) {
					$pse_row = $pself->getYTDAmountSumByUserIdAndEntryNameIdAndDate( $u_obj->getId(), $src_id, $current_epoch );
					if ( isset( $pse_row['amount'] ) && $pse_row['amount'] != 0 ) {
						Debug::Text( 'Found existing YTD amount for User ID: ' . $u_obj->getID() . ' PayStubEntryNameID: ' . $src_id . ' Amount: ' . $pse_row['amount'], __FILE__, __LINE__, __METHOD__, 10 );

						if ( isset( $pay_stub_account_arr[$dst_id] ) ) {
							$from_description = TTi18n::getText( 'Migrated YTD Amount to' ) . ': ' . $pay_stub_account_arr[$dst_id];
						} else {
							$from_description = TTi18n::getText( 'Migrated YTD Amount to other account' );
						}

						if ( isset( $pay_stub_account_arr[$src_id] ) ) {
							$to_description = TTi18n::getText( 'Migrated YTD Amount from' ) . ': ' . $pay_stub_account_arr[$src_id];
						} else {
							$to_description = TTi18n::getText( 'Migrated YTD Amount from other account' );
						}
						Debug::Text( 'Description: From: ' . $from_description . ' To: ' . $to_description, __FILE__, __LINE__, __METHOD__, 10 );

						//Create Pay Stub Amendments to reduce current values to 0.
						$psaf = TTNew( 'PayStubAmendmentFactory' ); /** @var PayStubAmendmentFactory $psaf */
						$psaf->setStatus( 50 );
						$psaf->setType( 10 );
						$psaf->setUser( $u_obj->getID() );
						$psaf->setPayStubEntryNameId( $src_id );
						$psaf->setAmount( ( $pse_row['amount'] * -1 ) );
						$psaf->setEffectiveDate( $effective_date );
						$psaf->setDescription( $from_description );
						if ( $psaf->isValid() ) {
							$psaf->Save();
						}

						//Create Pay Stub Amendments to copy amounts to new dst_id
						$psaf = TTNew( 'PayStubAmendmentFactory' ); /** @var PayStubAmendmentFactory $psaf */
						$psaf->setStatus( 50 );
						$psaf->setType( 10 );
						$psaf->setUser( $u_obj->getID() );
						$psaf->setPayStubEntryNameId( $dst_id );
						$psaf->setAmount( $pse_row['amount'] );
						$psaf->setEffectiveDate( $effective_date );
						$psaf->setDescription( $to_description );
						if ( $psaf->isValid() ) {
							$psaf->Save();
						}
					}
				}
			}
		}

		$ulf->CommitTransaction();

		return true;
	}


	/**
	 * @return bool
	 */
	function preSave() {
		if ( $this->getDeleted() == true ) {
			//Validate() checks for pay codes, Tax/Deductions etc...
			Debug::text( 'Attempting to delete PSE Account', __FILE__, __LINE__, __METHOD__, 10 );
			if ( $this->isInUse( $this->getId() ) ) {
				Debug::text( 'PSE Account is in use by Pay Stubs... Disabling instead.', __FILE__, __LINE__, __METHOD__, 10 );
				$this->setDeleted( false ); //Can't delete, account is in use.
				$this->setStatus( 20 );     //Disable instead
			} else {
				Debug::text( 'aPSE Account is NOT in use... Deleting...', __FILE__, __LINE__, __METHOD__, 10 );
			}
		} else {
			if ( $this->getAccrualType() == '' ) {
				$this->setAccrualType( 10 );
			}
		}

		return true;
	}

	function postSave() {
		$this->removeCache( $this->getId() );
		$this->removeCache( null, $this->getTable( true ) . $this->getCompany() ); //PayStubEntryAccountListFactory has several functions that cache data in this group, so clear them all.
	}

	/**
	 * @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 ) {
						default:
							if ( method_exists( $this, $function ) ) {
								$this->$function( $data[$key] );
							}
							break;
					}
				}
			}

			$this->setCreatedAndUpdatedColumns( $data );

			return true;
		}

		return false;
	}

	/**
	 * @param null $include_columns
	 * @return array
	 */
	function getObjectAsArray( $include_columns = null ) {
		$variable_function_map = $this->getVariableToFunctionMap();
		$data = [];
		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 'in_use':
							$data[$variable] = $this->getColumn( $variable );
							break;
						case 'status':
						case 'type':
						case 'accrual_type':
							$function = 'get' . $variable;
							if ( method_exists( $this, $function ) ) {
								$data[$variable] = Option::getByKey( $this->$function(), $this->getOptions( $variable ) );
							}
							break;
						default:
							if ( method_exists( $this, $function ) ) {
								$data[$variable] = $this->$function();
							}
							break;
					}
				}
			}
			$this->getCreatedAndUpdatedColumns( $data, $include_columns );
		}

		return $data;
	}

	/**
	 * @param $log_action
	 * @return bool
	 */
	function addLog( $log_action ) {
		return TTLog::addEntry( $this->getId(), $log_action, TTi18n::getText( 'Pay Stub Account' ), null, $this->getTable(), $this );
	}
}

?>