<?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 API\PayStub
 */
class APIPayStub extends APIFactory {
	protected $main_class = 'PayStubFactory';

	/**
	 * APIPayStub constructor.
	 */
	public function __construct() {
		parent::__construct(); //Make sure parent constructor is always called.

		return true;
	}

	/**
	 * overridden to get different columns based on permissions.
	 * @param bool $name
	 * @param null $parent
	 * @return bool|object
	 */
	function getOptions( $name = false, $parent = null ) {
		if ( $name == 'columns'
				&& ( !$this->getPermissionObject()->Check( 'pay_stub', 'enabled' )
						|| !( $this->getPermissionObject()->Check( 'pay_stub', 'view' ) || $this->getPermissionObject()->Check( 'pay_stub', 'view_child' ) ) ) ) {
			$name = 'list_columns';
		}

		return parent::getOptions( $name, $parent );
	}

	/**
	 * Get default paystub_entry_account data for creating new paystub_entry_accountes.
	 * @return array
	 */
	function getPayStubDefaultData() {
		$company_obj = $this->getCurrentCompanyObject();
		$user_obj = $this->getCurrentUserObject();

		Debug::Text( 'Getting pay stub entry default data...', __FILE__, __LINE__, __METHOD__, 10 );

		//Get earliest OPEN pay period.
		$pplf = TTNew( 'PayPeriodListFactory' ); /** @var PayPeriodListFactory $pplf */
		$pplf->getLastPayPeriodByCompanyIdAndPayPeriodScheduleIdAndDate( $company_obj->getId(), null, time() );
		if ( $pplf->getRecordCount() > 0 ) {
			$pp_obj = $pplf->getCurrent();

			$pay_period_id = $pp_obj->getId();
			$start_date = TTDate::getDate( 'DATE', $pp_obj->getStartDate() );
			$end_date = TTDate::getDate( 'DATE', $pp_obj->getEndDate() );
			$transaction_date = TTDate::getDate( 'DATE', $pp_obj->getTransactionDate() );
		} else {
			$pay_period_id = TTUUID::getZeroID();
			$start_date = TTDate::getDate( 'DATE', time() );
			$end_date = TTDate::getDate( 'DATE', time() );
			$transaction_date = TTDate::getDate( 'DATE', time() );
		}

		$run_id = $this->stripReturnHandler( $this->getCurrentPayRun( $pay_period_id ) );

		$data = [
				'company_id'       => $company_obj->getId(),
				'user_id'          => $user_obj->getId(),
				'currency_id'      => $user_obj->getCurrency(),
				'pay_period_id'    => $pay_period_id,
				'run_id'           => $run_id,
				'start_date'       => $start_date,
				'end_date'         => $end_date,
				'transaction_date' => $transaction_date,
		];

		return $this->returnHandler( $data );
	}

	/**
	 * Get pay_stub data for one or more pay_stubes.
	 * @param array $data filter data
	 * @param bool $disable_paging
	 * @param bool $format
	 * @param bool $hide_employer_rows
	 * @return array|bool
	 */
	function getPayStub( $data = null, $disable_paging = false, $format = false, $hide_employer_rows = true ) {
		$data = $this->initializeFilterAndPager( $data, $disable_paging );

		if ( $this->getPermissionObject()->checkAuthenticationType( 700 ) == false ) { //700=HTTP Auth with username/password
			return $this->getPermissionObject()->AuthenticationTypeDenied();
		}

		if ( !$this->getPermissionObject()->Check( 'pay_stub', 'enabled' )
				|| !( $this->getPermissionObject()->Check( 'pay_stub', 'view' ) || $this->getPermissionObject()->Check( 'pay_stub', 'view_own' ) || $this->getPermissionObject()->Check( 'pay_stub', 'view_child' ) ) ) {
			return $this->getPermissionObject()->PermissionDenied();
		}

		$format = Misc::trimSortPrefix( $format );
		$data['filter_data']['permission_children_ids'] = $this->getPermissionObject()->getPermissionChildren( 'pay_stub', 'view' );

		if ( $this->getPermissionObject()->Check( 'pay_stub', 'view' ) == false && $this->getPermissionObject()->Check( 'pay_stub', 'view_child' ) == false ) {
			//Only display PAID pay stubs that are not opening balance ones.
			$data['filter_data']['status_id'] = [ 40 ]; //40=Paid
			$data['filter_data']['type_id'] = [ 10, 20 ]; //10=Normal In-Cycle, 20=Out-of-Cycle
		}

		//Always hide employer rows unless they have permissions to view all pay stubs.
		if ( $this->getPermissionObject()->Check( 'pay_stub', 'view' ) == false ) {
			$hide_employer_rows = true;
		}

		//Make sure we start the progress bar with more than 1 iteration before the 1st SQL query, so if for some reason the query takes a long time the user at least has some progress bar.
		$this->getProgressBarObject()->setDefaultKey( $this->getAPIMessageID() );
		$this->getProgressBarObject()->start( $this->getAPIMessageID(), 2, null, TTi18n::getText('Retrieving data...') );

		if ( ( $format == 'export_transactions' ) && $this->getPermissionObject()->Check( 'pay_stub', 'view' ) == true ) {
			//Always enable debug logging during transaction export.
			Debug::setEnable( true );
			Debug::setBufferOutput( true );
			Debug::setEnableLog( true );
			Debug::setVerbosity( 10 );

			if ( isset( $data['filter_data']['time_period'] ) && is_array( $data['filter_data']['time_period'] ) ) {
				$report_obj = TTnew( 'Report' ); /** @var Report $report_obj */
				$report_obj->setUserObject( $this->getCurrentUserObject() );
				$report_obj->setPermissionObject( $this->getPermissionObject() );
				Debug::Text( 'Found TimePeriod...', __FILE__, __LINE__, __METHOD__, 10 );
				$data['filter_data'] = array_merge( $data['filter_data'], (array)$report_obj->convertTimePeriodToStartEndDate( $data['filter_data']['time_period'] ) );
				unset( $report_obj );
			}

			//These filters are also in APIPayStubTransaction->getPayPeriodTransactionSummary().
			$data['filter_data']['transaction_status_id'] = [ 10, 200 ]; //10=Pending, 200=ReIssue
			$data['filter_data']['transaction_type_id'] = 10;            //10=Valid (Enabled)
			if ( isset( $data['filter_data']['id'] ) ) {
				$data['filter_data']['pay_stub_id'] = $data['filter_data']['id'];
			}
			unset( $data['filter_data']['id'] );

			//Specific sort order to ensure consistent transaction order in the EFT files. Keep in mind exportPayStubTransaction() sorts the transactions again too, but this helps.
			$data['filter_sort'] = [ 'lef.id' => 'asc', 'rsaf.id' => 'asc', 'psf.transaction_date' => 'asc', 'destination_user_last_name' => 'asc', 'destination_user_first_name' => 'asc', 'rdaf.id' => 'asc' ];

			$pslf = TTnew( 'PayStubTransactionListFactory' ); /** @var PayStubTransactionListFactory $pslf */
			$pslf->setProgressBarObject( $this->getProgressBarObject() ); //Expose progress bar object to pay stub object.
			$pslf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentUserObject()->getCompany(), $data['filter_data'], $data['filter_items_per_page'], $data['filter_page'], null, $data['filter_sort'] );
			$this->getProgressBarObject()->set( $this->getAPIMessageID(), 1 );
			Debug::Text( 'PSTLF Record Count: ' . $pslf->getRecordCount() . ' Format: ' . $format, __FILE__, __LINE__, __METHOD__, 10 );
		} else {
			$pslf = TTnew( 'PayStubListFactory' ); /** @var PayStubListFactory $pslf */
			$pslf->setProgressBarObject( $this->getProgressBarObject() ); //Expose progress bar object to pay stub object.
			$pslf->getAPISearchByCompanyIdAndArrayCriteria( $this->getCurrentCompanyObject()->getId(), $data['filter_data'], $data['filter_items_per_page'], $data['filter_page'], null, $data['filter_sort'] );
			$this->getProgressBarObject()->set( $this->getAPIMessageID(), 1 );
			Debug::Text( 'PSLF Record Count: ' . $pslf->getRecordCount() . ' Format: ' . $format, __FILE__, __LINE__, __METHOD__, 10 );
		}

		if ( $format == 'pdf' ) {
			if ( $pslf->getRecordCount() > 0 ) {
				$this->getProgressBarObject()->setDefaultKey( $this->getAPIMessageID() );
				$this->getProgressBarObject()->start( $this->getAPIMessageID(), $pslf->getRecordCount(), null, TTi18n::getText('Generating Pay Stubs...') );
				$pslf->setProgressBarObject( $this->getProgressBarObject() ); //Expose progress bar object to pay stub object.

				$output = $pslf->getPayStub( $pslf, (bool)$hide_employer_rows );

				$this->getProgressBarObject()->stop( $this->getAPIMessageID() );

				if ( $output != '' ) {
					return Misc::APIFileDownload( 'pay_stub.pdf', 'application/pdf', $output );
				} else {
					return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'ERROR: No data to export...' ) );
				}
			}
		} else if ( ( $format == 'export_transactions' ) && $this->getPermissionObject()->Check( 'pay_stub', 'view' ) == true ) {
			if ( $pslf->getRecordCount() > 0 ) {
				$this->getProgressBarObject()->setDefaultKey( $this->getAPIMessageID() );
				$this->getProgressBarObject()->start( $this->getAPIMessageID(), $pslf->getRecordCount(), null, TTi18n::getText('Exporting Transactions...') );
				$pslf->setProgressBarObject( $this->getProgressBarObject() ); //Expose progress bar object to pay stub object.

				$pslf->StartTransaction();
				$output = $pslf->exportPayStubTransaction( $pslf, null, $data['setup_last_check_number'] );

				if ( is_array( $output ) && count( $output ) > 0 ) {
					//Transmit agency reports to TimeTrex Payment Services
					$export_agency_report_retval = $pslf->exportPayStubRemittanceAgencyReports( $pslf );
					$pslf->CommitTransaction();

					$this->getProgressBarObject()->stop( $this->getAPIMessageID() );

					if ( $export_agency_report_retval == true ) {
						$filename = 'pay_stub_transactions_' . TTDate::getDate( 'DATE', time() ) . '.zip';
						$zip_file = Misc::zip( $output, $filename, true );
						if ( is_array( $zip_file ) && isset( $zip_file['file_name'] ) && isset( $zip_file['mime_type'] ) && isset( $zip_file['data'] ) ) { //Was just: $zip_file !== FALSE
							return Misc::APIFileDownload( $zip_file['file_name'], $zip_file['mime_type'], $zip_file['data'] );
						} else {
							//FIXME: Return UserGenericStatus ID instead? Or at least some message showing success.
							Debug::Arr( $output, 'No Zip file to download, perhaps transactions were processed with PaymentServices API?', __FILE__, __LINE__, __METHOD__, 10 );

							return true;
						}
					} else {
						return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'ERROR: Unable to handle remittance agency report...' ) );
					}
				} else {
					$this->getProgressBarObject()->stop( $this->getAPIMessageID() );

					$pslf->CommitTransaction();
					return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'ERROR: No data to export...' ) );
				}
			} else {
				return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'All transactions have already been processed' ) );
			}
		} else {
			if ( $pslf->getRecordCount() > 0 ) {
				$this->getProgressBarObject()->start( $this->getAPIMessageID(), $pslf->getRecordCount() );

				$this->setPagerObject( $pslf );

				$retarr = [];
				foreach ( $pslf as $ps_obj ) {
					$retarr[] = $ps_obj->getObjectAsArray( $data['filter_columns'], $data['filter_data']['permission_children_ids'] );

					$this->getProgressBarObject()->set( $this->getAPIMessageID(), $pslf->getCurrentRow() );
				}

				$this->getProgressBarObject()->stop( $this->getAPIMessageID() );

				return $this->returnHandler( $retarr );
			}

			return $this->returnHandler( true ); //No records returned.
		}

		return $this->returnHandler( false );
	}

	/**
	 * Export data to csv
	 * @param string $format file format (csv)
	 * @param array $data    filter data
	 * @param bool $disable_paging
	 * @return array
	 */
	function exportPayStub( $format = 'csv', $data = null, $disable_paging = true ) {
		$result = $this->stripReturnHandler( $this->getPayStub( $data, $disable_paging ) );

		return $this->exportRecords( $format, 'export_pay_stub', $result, ( ( isset( $data['filter_columns'] ) ) ? $data['filter_columns'] : null ) );
	}

	/**
	 * Get only the fields that are common across all records in the search criteria. Used for Mass Editing of records.
	 * @param array $data filter data
	 * @return array
	 */
	function getCommonPayStubData( $data ) {
		return Misc::arrayIntersectByRow( $this->stripReturnHandler( $this->getPayStub( $data, true ) ) );
	}

	/**
	 * Validate pay_stub data for one or more pay_stubes.
	 * @param array $data pay_stub data
	 * @return array
	 */
	function validatePayStub( $data ) {
		return $this->setPayStub( $data, true );
	}

	/**
	 * Set pay_stub data for one or more pay_stubes.
	 * @param array $data pay_stub data
	 * @param bool $validate_only
	 * @param bool $ignore_warning
	 * @return array|bool
	 */
	function setPayStub( $data, $validate_only = false, $ignore_warning = true ) {
		$validate_only = (bool)$validate_only;
		$ignore_warning = (bool)$ignore_warning;

		if ( !is_array( $data ) ) {
			return $this->returnHandler( false );
		}

		if ( $this->getPermissionObject()->checkAuthenticationType( 700 ) == false ) { //700=HTTP Auth with username/password
			return $this->getPermissionObject()->AuthenticationTypeDenied();
		}

		if ( !$this->getPermissionObject()->Check( 'pay_stub', 'enabled' )
				|| !( $this->getPermissionObject()->Check( 'pay_stub', 'edit' ) || $this->getPermissionObject()->Check( 'pay_stub', 'edit_own' ) || $this->getPermissionObject()->Check( 'pay_stub', 'edit_child' ) || $this->getPermissionObject()->Check( 'pay_stub', 'add' ) ) ) {
			return $this->getPermissionObject()->PermissionDenied();
		}

		if ( $validate_only == true ) {
			Debug::Text( 'Validating Only!', __FILE__, __LINE__, __METHOD__, 10 );
		}

		[ $data, $total_records ] = $this->convertToMultipleRecords( $data );
		Debug::Text( 'Received data for: ' . $total_records . ' PayStubs', __FILE__, __LINE__, __METHOD__, 10 );
		Debug::Arr( $data, 'Data: ', __FILE__, __LINE__, __METHOD__, 10 );

		$validator_stats = [ 'total_records' => $total_records, 'valid_records' => 0 ];
		$validator = $save_result = []; $key = false;
		if ( is_array( $data ) && $total_records > 0 ) {
			$this->getProgressBarObject()->start( $this->getAPIMessageID(), $total_records );

			foreach ( $data as $key => $row ) {
				$primary_validator = new Validator();
				$lf = TTnew( 'PayStubListFactory' ); /** @var PayStubListFactory $lf */
				$lf->StartTransaction();
				if ( isset( $row['id'] ) && $row['id'] != '' ) {
					//Modifying existing object.
					//Get pay_stub object, so we can only modify just changed data for specific records if needed.
					$lf->getByIdAndCompanyId( $row['id'], $this->getCurrentCompanyObject()->getId() );
					if ( $lf->getRecordCount() == 1 ) {
						//Object exists, check edit permissions
						if (
								$validate_only == true
								||
								(
										$this->getPermissionObject()->Check( 'pay_stub', 'edit' )
										|| ( $this->getPermissionObject()->Check( 'pay_stub', 'edit_own' ) && $this->getPermissionObject()->isOwner( $lf->getCurrent()->getCreatedBy(), $lf->getCurrent()->getUser() ) === true )
								) ) {

							Debug::Text( 'Row Exists, getting current data for ID: ' . $row['id'], __FILE__, __LINE__, __METHOD__, 10 );
							$lf = $lf->getCurrent();
							$row = array_merge( $lf->getObjectAsArray(), $row );
						} else {
							$primary_validator->isTrue( 'permission', false, TTi18n::gettext( 'Edit permission denied' ) );
						}
					} else {
						//Object doesn't exist.
						$primary_validator->isTrue( 'id', false, TTi18n::gettext( 'Edit permission denied, record does not exist' ) );
					}
				} else {
					//Adding new object, check ADD permissions.
					$primary_validator->isTrue( 'permission', $this->getPermissionObject()->Check( 'pay_stub', 'add' ), TTi18n::gettext( 'Add permission denied' ) );

					//Because this class has sub-classes that depend on it, when adding a new record we need to make sure the ID is set first,
					//so the sub-classes can depend on it. We also need to call Save( TRUE, TRUE ) to force a lookup on isNew()
					$row['id'] = $lf->getNextInsertId();
				}
				Debug::Arr( $row, 'Data: ', __FILE__, __LINE__, __METHOD__, 10 );

				$is_valid = $primary_validator->isValid();
				if ( $is_valid == true ) { //Check to see if all permission checks passed before trying to save data.
					Debug::Text( 'Setting object data...', __FILE__, __LINE__, __METHOD__, 10 );

					$lf->setObjectFromArray( $row );

					if ( ( isset( $row['entries'] ) && is_array( $row['entries'] ) && count( $row['entries'] ) > 0 ) ) {
						Debug::Text( ' Found modified entries!', __FILE__, __LINE__, __METHOD__, 10 );

						//Load previous pay stub
						$lf->loadPreviousPayStub();

						//Delete all entries, so they can be re-added.
						//$lf->deleteEntries( TRUE );

						//When editing pay stubs we can't re-process linked accruals.
						$lf->setEnableLinkedAccruals( false );

						$processed_entries = 0;
						foreach ( $row['entries'] as $pay_stub_entry ) {
							if ( (
											( isset( $pay_stub_entry['id'] ) && TTUUID::isUUID( $pay_stub_entry['id'] ) && $pay_stub_entry['id'] != TTUUID::getZeroID() && $pay_stub_entry['id'] != TTUUID::getNotExistID() )
											||
											( isset( $pay_stub_entry['pay_stub_entry_name_id'] ) && TTUUID::isUUID( $pay_stub_entry['pay_stub_entry_name_id'] ) )
									)
									&&
									(
											!isset( $pay_stub_entry['type'] )
											||
											( isset( $pay_stub_entry['type'] ) && $pay_stub_entry['type'] != 40 )
									)
									&& isset( $pay_stub_entry['amount'] )
							) {
								Debug::Text( 'Pay Stub Entry ID: ' . ( ( isset($pay_stub_entry['id']) ) ? $pay_stub_entry['id'] : 'N/A' ) . ' Amount: ' . $pay_stub_entry['amount'] . ' Pay Stub ID: ' . $row['id'], __FILE__, __LINE__, __METHOD__, 10 );

								//Populate $pay_stub_entry_obj so we can find validation errors before postSave() is called.
								if ( isset( $pay_stub_entry['id'] ) && $pay_stub_entry['id'] != '' && TTUUID::isUUID( $pay_stub_entry['id'] ) ) {
									$pself = TTnew( 'PayStubEntryListFactory' ); /** @var PayStubEntryListFactory $pself */
									$pself->getById( $pay_stub_entry['id'] );
									if ( $pself->getRecordCount() > 0 ) {
										$pay_stub_entry_obj = $pself->getCurrent();
									} else {
										$pay_stub_entry_obj = TTnew( 'PayStubEntryListFactory' ); /** @var PayStubEntryListFactory $pay_stub_entry_obj */
									}
								} else {
									$pay_stub_entry_obj = TTnew( 'PayStubEntryListFactory' ); /** @var PayStubEntryListFactory $pay_stub_entry_obj */
									//$pay_stub_entry_obj->setPayStub( $lf->getId() ); //Don't set this here as it will cause validation failures. Its handled in addEntry instead.
								}

								if ( isset( $pay_stub_entry['deleted'] ) && $pay_stub_entry['deleted'] == 1 ) {
									// Deleted is set instead of populating the object to provide for the case where a
									// user enters invalid data then deletes the row, removing it from the UI
									$pay_stub_entry_obj->setDeleted( true );
								} else {
									if ( isset( $pay_stub_entry['pay_stub_entry_name_id'] ) && $pay_stub_entry['pay_stub_entry_name_id'] != '' ) {
										$pay_stub_entry_obj->setPayStubEntryNameId( $pay_stub_entry['pay_stub_entry_name_id'] );
									}

									if ( isset( $pay_stub_entry['pay_stub_amendment_id'] ) && $pay_stub_entry['pay_stub_amendment_id'] != '' ) {
										$pay_stub_entry_obj->setPayStubAmendment( $pay_stub_entry['pay_stub_amendment_id'], $lf->getStartDate(), $lf->getEndDate() );
									}

									if ( isset( $pay_stub_entry['rate'] ) && $pay_stub_entry['rate'] != '' ) {
										$pay_stub_entry_obj->setRate( $pay_stub_entry['rate'] );
									}
									if ( isset( $pay_stub_entry['units'] ) && $pay_stub_entry['units'] != '' ) {
										$pay_stub_entry_obj->setUnits( $pay_stub_entry['units'] );
									}

									if ( isset( $pay_stub_entry['amount'] ) && $pay_stub_entry['amount'] != '' ) {
										$pay_stub_entry_obj->setAmount( $pay_stub_entry['amount'] );
									}

									if ( !isset( $pay_stub_entry['units'] ) || $pay_stub_entry['units'] == '' ) {
										$pay_stub_entry['units'] = 0;
									}
									if ( !isset( $pay_stub_entry['rate'] ) || $pay_stub_entry['rate'] == '' ) {
										$pay_stub_entry['rate'] = 0;
									}
									if ( !isset( $pay_stub_entry['description'] ) || $pay_stub_entry['description'] == '' ) {
										$pay_stub_entry['description'] = null;
									}
									if ( !isset( $pay_stub_entry['pay_stub_amendment_id'] ) || $pay_stub_entry['pay_stub_amendment_id'] == '' ) {
										$pay_stub_entry['pay_stub_amendment_id'] = null;
									}
									if ( !isset( $pay_stub_entry['user_expense_id'] ) || $pay_stub_entry['user_expense_id'] == '' ) {
										$pay_stub_entry['user_expense_id'] = null;
									}

									$ytd_adjustment = false;
									if ( TTUUID::isUUID( $pay_stub_entry['pay_stub_amendment_id'] ) && $pay_stub_entry['pay_stub_amendment_id'] != TTUUID::getZeroID() && $pay_stub_entry['pay_stub_amendment_id'] != TTUUID::getNotExistID() ) {
										$psamlf = TTNew( 'PayStubAmendmentListFactory' ); /** @var PayStubAmendmentListFactory $psamlf */
										$psamlf->getByIdAndCompanyId( TTUUID::castUUID( $pay_stub_entry['pay_stub_amendment_id'] ), $this->getCurrentCompanyObject()->getId() );
										if ( $psamlf->getRecordCount() > 0 ) {
											$ytd_adjustment = $psamlf->getCurrent()->getYTDAdjustment();
										}
										Debug::Text( ' Pay Stub Amendment Id: ' . $pay_stub_entry['pay_stub_amendment_id'] . ' YTD Adjusment: ' . (int)$ytd_adjustment, __FILE__, __LINE__, __METHOD__, 10 );
									}
								}

								if ( $pay_stub_entry_obj->isValid() == true ) {
									if ( $pay_stub_entry_obj->getDeleted() == true ) {
										//Since addEntry() doesn't get passed an object, it can't delete entries, so we need to handle it outside of that function instead.
										$pay_stub_entry_obj->Save();
									} else {
										$lf->addEntry( $pay_stub_entry['pay_stub_entry_name_id'], $pay_stub_entry['amount'], $pay_stub_entry['units'], $pay_stub_entry['rate'], $pay_stub_entry['description'], $pay_stub_entry['pay_stub_amendment_id'], null, null, $ytd_adjustment, $pay_stub_entry['user_expense_id'] );
									}
									$processed_entries++;
								} else {
									Debug::Text( ' ERROR: Unable to save PayStubEntry... ', __FILE__, __LINE__, __METHOD__, 10 );
									$tmp_pay_stub_entry_account_name = TTi18n::getText( 'N/A' );
									if ( is_object( $pay_stub_entry_obj->getPayStubEntryAccountObject() ) ) {
										$tmp_pay_stub_entry_account_name = $pay_stub_entry_obj->getPayStubEntryAccountObject()->getName();
									}

									$lf->Validator->isTrue( 'pay_stub_entry', false, TTi18n::getText( '%1 entry for amount: %2 is invalid', [ $tmp_pay_stub_entry_account_name, Misc::MoneyFormat( $pay_stub_entry['amount'] ) ] ) );
								}
							} else {
								Debug::Text( ' Skipping Total Entry. ', __FILE__, __LINE__, __METHOD__, 10 );
							}
							unset( $pay_stub_entry_obj );
						}
						unset( $pay_stub_entry );

						if ( $processed_entries > 0 ) {
							$lf->setTainted( true ); //Make sure tainted flag is set when any entries are processed.
							$lf->setEnableCalcYTD( true );
							$lf->setEnableProcessEntries( true );
							$lf->processEntries();
						}
					} else {
						Debug::Text( ' Skipping ALL Entries... ', __FILE__, __LINE__, __METHOD__, 10 );
					}

					if ( ( isset( $row['transactions'] ) && is_array( $row['transactions'] ) && count( $row['transactions'] ) > 0 ) ) {
						Debug::Text( ' Found modified transactions!', __FILE__, __LINE__, __METHOD__, 10 );
						$processed_transactions = 0;
						if ( count( $row['transactions'] ) > 0 ) {
							foreach ( $row['transactions'] as $pay_stub_transaction ) {
								//Debug::Arr($pay_stub_transaction,'Paystub transaction row...', __FILE__, __LINE__, __METHOD__, 10);
								if ( $pay_stub_transaction['amount'] == 0 && $pay_stub_transaction['remittance_destination_account_id'] == TTUUID::getZeroID() ) { //Skip any transactions of $0.00
									continue;
								}

								$pst_obj = TTnew( 'PayStubTransactionFactory' ); /** @var PayStubTransactionFactory $pst_obj */
								//$pst_obj->setPayStub( $lf->getId() ); //Don't set this here as it will cause validation failures. Its handled in addTransaction() instead.

								if ( isset( $pay_stub_transaction['id'] )
										&& TTUUID::isUUID( $pay_stub_transaction['id'] ) && $pay_stub_transaction['id'] != TTUUID::getZeroID() && $pay_stub_transaction['id'] != TTUUID::getNotExistID() ) {
									$pstlf = TTnew( 'PayStubTransactionListFactory' ); /** @var PayStubTransactionListFactory $pstlf */
									$pstlf->getByIdAndCompanyId( $pay_stub_transaction['id'], $this->getCurrentCompanyObject()->getId() );
									if ( $pstlf->getRecordCount() > 0 ) {
										$pst_obj = $pstlf->getCurrent();
									}
									unset( $pstlf );
								}

								$pst_obj->setType( 10 ); //10=Valid

								if ( isset( $pay_stub_transaction['status_id'] ) && $pay_stub_transaction['status_id'] != '' ) {
									$pst_obj->setStatus( $pay_stub_transaction['status_id'] );
								} else {
									$pst_obj->setStatus( 10 ); //10=Pending
								}

								if ( isset( $pay_stub_transaction['deleted'] ) && $pay_stub_transaction['deleted'] == 1 ) {
									// Deleted is set instead of populating the object to provide for the case where a
									// user enters invalid data then deletes the row, removing it from the UI
									$pst_obj->setDeleted( true );
								} else {
									if ( isset( $pay_stub_transaction['remittance_destination_account_id'] ) ) {
										$pst_obj->setRemittanceDestinationAccount( $pay_stub_transaction['remittance_destination_account_id'] );
									}

									//Make sure remittance source account and currency is set so we don't have to rely on preSave(), which causes issues with validation.
									if ( is_object( $pst_obj->getRemittanceDestinationAccountObject() ) ) {
										$pst_obj->setRemittanceSourceAccount( $pst_obj->getRemittanceDestinationAccountObject()->getRemittanceSourceAccount() );
									}

									if ( $pst_obj->getCurrency() == false ) {
										if ( is_object( $pst_obj->getRemittanceSourceAccountObject() ) ) {
											$pst_obj->setCurrency( $pst_obj->getRemittanceSourceAccountObject()->getCurrency() );
										} else if ( is_object( $pst_obj->getPayStubObject() ) ) {
											$pst_obj->setCurrency( $pst_obj->getPayStubObject()->getCurrency() );
										}
									}

									if ( isset( $pay_stub_transaction['transaction_date'] ) ) {
										$pst_obj->setTransactionDate( TTDate::parseDateTime( $pay_stub_transaction['transaction_date'] ) );
									}

									if ( isset( $pay_stub_transaction['amount'] ) ) {
										$pst_obj->setAmount( $pay_stub_transaction['amount'] );
									} else {
										$pst_obj->setAmount( 0 );
									}

									if ( isset( $pay_stub_transaction['note'] ) ) {
										$pst_obj->setNote( $pay_stub_transaction['note'] );
									}
								}

								if ( $pst_obj->isValid() ) {
									$lf->addTransaction( $pst_obj );
									$processed_transactions++;
								} else {
									if ( isset( $pay_stub_transaction['deleted'] ) == false || $pay_stub_transaction['deleted'] == 0 ) {
										$tmp_remittance_destination_account_name = TTi18n::getText( 'N/A' );
										if ( is_object( $pst_obj->getRemittanceDestinationAccountObject() ) ) {
											$tmp_remittance_destination_account_name = $pst_obj->getRemittanceDestinationAccountObject()->getName();
										}
										$lf->Validator->isTrue( 'pay_stub_transaction', false, TTi18n::getText( '%1 transaction for amount: %2 is invalid', [ $tmp_remittance_destination_account_name, Misc::MoneyFormat( $pst_obj->getAmount() ) ] ) );
										unset( $tmp_remittance_destination_account_name );
									}
								}
								unset( $pst_obj );
							}
						}

						if ( $processed_transactions > 0 ) {
							$lf->setTainted( true ); //Make sure tainted flag is set when any entries are processed.
							$lf->setEnableSyncPendingPayStubTransactionDates( true );
							$lf->setEnableProcessTransactions( true );
							//$lf->processTransactions();
						}
					} else {
						Debug::Text( ' Skipping ALL transactions... ', __FILE__, __LINE__, __METHOD__, 10 );
						$lf->setEnableSyncPendingPayStubTransactionDates( true );
					}

					$lf->Validator->setValidateOnly( $validate_only );

					$is_valid = $lf->isValid( $ignore_warning );
					if ( $is_valid == true ) {
						Debug::Text( 'Saving data...', __FILE__, __LINE__, __METHOD__, 10 );
						if ( $validate_only == true ) {
							$save_result[$key] = true;
						} else {
							$save_result[$key] = $lf->Save( true, true );
						}
						$validator_stats['valid_records']++;
					}
				}

				if ( $is_valid == false ) {
					Debug::Text( 'Data is Invalid...', __FILE__, __LINE__, __METHOD__, 10 );

					$lf->FailTransaction(); //Just rollback this single record, continue on to the rest.

					$validator[$key] = $this->setValidationArray( [ $primary_validator, $lf ], ( ( $total_records > 1 && is_object( $lf->getUserObject() ) ) ? $lf->getUserObject()->getFullName() .' ('. TTi18n::getText('Transaction Date') .': '. TTDate::getDate('DATE', $lf->getTransactionDate() ) .')' : null ) );
				} else if ( $validate_only == true ) {
					$lf->FailTransaction();
				}

				$lf->CommitTransaction();

				$this->getProgressBarObject()->set( $this->getAPIMessageID(), $key );
			}

			$this->getProgressBarObject()->stop( $this->getAPIMessageID() );

			return $this->handleRecordValidationResults( $validator, $validator_stats, $key, $save_result );
		}

		return $this->returnHandler( false );
	}

	/**
	 * Delete one or more pay_stubs.
	 * @param array $data pay_stub data
	 * @return array|bool
	 */
	function deletePayStub( $data ) {
		if ( !is_array( $data ) ) {
			$data = [ $data ];
		}

		if ( !is_array( $data ) ) {
			return $this->returnHandler( false );
		}

		if ( $this->getPermissionObject()->checkAuthenticationType( 700 ) == false ) { //700=HTTP Auth with username/password
			return $this->getPermissionObject()->AuthenticationTypeDenied();
		}

		if ( !$this->getPermissionObject()->Check( 'pay_stub', 'enabled' )
				|| !( $this->getPermissionObject()->Check( 'pay_stub', 'delete' ) || $this->getPermissionObject()->Check( 'pay_stub', 'delete_own' ) || $this->getPermissionObject()->Check( 'pay_stub', 'delete_child' ) ) ) {
			return $this->getPermissionObject()->PermissionDenied();
		}

		Debug::Text( 'Received data for: ' . count( $data ) . ' PayStubs', __FILE__, __LINE__, __METHOD__, 10 );
		Debug::Arr( $data, 'Data: ', __FILE__, __LINE__, __METHOD__, 10 );

		$total_records = count( $data );
		$validator = $save_result = []; $key = false;
		$validator_stats = [ 'total_records' => $total_records, 'valid_records' => 0 ];
		if ( is_array( $data ) && $total_records > 0 ) {
			$this->getProgressBarObject()->start( $this->getAPIMessageID(), $total_records );

			foreach ( $data as $key => $id ) {
				$primary_validator = new Validator();
				$lf = TTnew( 'PayStubListFactory' ); /** @var PayStubListFactory $lf */
				$lf->StartTransaction();
				if ( $id != '' ) {
					//Modifying existing object.
					//Get pay_stub object, so we can only modify just changed data for specific records if needed.
					$lf->getByIdAndCompanyId( $id, $this->getCurrentCompanyObject()->getId() );
					if ( $lf->getRecordCount() == 1 ) {
						//Object exists, check edit permissions
						if ( $this->getPermissionObject()->Check( 'pay_stub', 'delete' )
								|| ( $this->getPermissionObject()->Check( 'pay_stub', 'delete_own' ) && $this->getPermissionObject()->isOwner( $lf->getCurrent()->getCreatedBy(), $lf->getCurrent()->getUser() ) === true ) ) {
							Debug::Text( 'Record Exists, deleting record ID: ' . $id, __FILE__, __LINE__, __METHOD__, 10 );
							$lf = $lf->getCurrent();
						} else {
							$primary_validator->isTrue( 'permission', false, TTi18n::gettext( 'Delete permission denied' ) );
						}
					} else {
						//Object doesn't exist.
						$primary_validator->isTrue( 'id', false, TTi18n::gettext( 'Delete permission denied, record does not exist' ) );
					}
				} else {
					$primary_validator->isTrue( 'id', false, TTi18n::gettext( 'Delete permission denied, record does not exist' ) );
				}

				//Debug::Arr($lf, 'AData: ', __FILE__, __LINE__, __METHOD__, 10);

				$is_valid = $primary_validator->isValid();
				if ( $is_valid == true ) { //Check to see if all permission checks passed before trying to save data.
					Debug::Text( 'Attempting to delete record...', __FILE__, __LINE__, __METHOD__, 10 );
					$lf->setDeleted( true );

					$is_valid = $lf->isValid();
					if ( $is_valid == true ) {
						Debug::Text( 'Record Deleted...', __FILE__, __LINE__, __METHOD__, 10 );
						$save_result[$key] = $lf->Save();
						$validator_stats['valid_records']++;
					}
				}

				if ( $is_valid == false ) {
					Debug::Text( 'Data is Invalid...', __FILE__, __LINE__, __METHOD__, 10 );

					$lf->FailTransaction(); //Just rollback this single record, continue on to the rest.

					$validator[$key] = $this->setValidationArray( [ $primary_validator, $lf ], ( ( $total_records > 1 && is_object( $lf->getUserObject() ) ) ? $lf->getUserObject()->getFullName() .' ('. TTi18n::getText('Transaction Date') .': '. TTDate::getDate('DATE', $lf->getTransactionDate() ) .')' : null ) );
				}

				$lf->CommitTransaction();

				$this->getProgressBarObject()->set( $this->getAPIMessageID(), $key );
			}

			$this->getProgressBarObject()->stop( $this->getAPIMessageID() );

			return $this->handleRecordValidationResults( $validator, $validator_stats, $key, $save_result );
		}

		return $this->returnHandler( false );
	}

	/**
	 * @param string $pay_period_ids UUID
	 * @param string $user_ids       UUID
	 * @param bool $enable_correction
	 * @param bool $run_id
	 * @param int $type_id
	 * @param int $transaction_date  EPOCH
	 * @return array|bool
	 */
	function generatePayStubs( $pay_period_ids, $user_ids = null, $enable_correction = false, $run_id = false, $type_id = 10, $transaction_date = null ) {
		global $profiler;
		Debug::Text( 'Generate Pay Stubs! Run ID: '. $run_id, __FILE__, __LINE__, __METHOD__, 10 );

		if ( $this->getPermissionObject()->checkAuthenticationType( 700 ) == false ) { //700=HTTP Auth with username/password
			return $this->getPermissionObject()->AuthenticationTypeDenied();
		}

		if ( $this->getCurrentUserObject()->getStatus() != 10 ) { //10=Active -- Make sure user record is active as well.
			return $this->getPermissionObject()->PermissionDenied( false, TTi18n::getText( 'Employee status must be Active to Generate Pay Stubs' ) );
		}

		if ( !( $this->getPermissionObject()->Check( 'pay_period_schedule', 'enabled' )
				&& ( $this->getPermissionObject()->Check( 'pay_period_schedule', 'edit' ) || $this->getPermissionObject()->Check( 'pay_period_schedule', 'edit_own' ) )
				&& ( $this->getPermissionObject()->Check( 'pay_stub', 'view' ) || $this->getPermissionObject()->Check( 'pay_stub', 'view_child' ) ) ) ) {
			return $this->getPermissionObject()->PermissionDenied();
		}

		if ( !is_array( $pay_period_ids ) ) {
			$pay_period_ids = [ $pay_period_ids ];
		}
		$pay_period_ids = array_unique( $pay_period_ids );


		if ( $user_ids !== null && !is_array( $user_ids ) && $user_ids != '' ) {
			$user_ids = [ $user_ids ];
		} else if ( is_array( $user_ids ) && isset( $user_ids[0] ) && $user_ids[0] == TTUUID::getZeroID() ) {
			$user_ids = null;
		}

		if ( is_array( $user_ids ) ) {
			$user_ids = array_unique( $user_ids );
		}

		if ( $type_id == 5 ) { //Post-Adjustment Carry-Forward, enable correction and force type to Normal.
			$enable_correction = true;
			$type_id = 10;
		}

		global $config_vars;
		$user_generic_status_batch_id = false;

		$pplf = TTnew( 'PayPeriodListFactory' ); /** @var PayPeriodListFactory $pplf */
		$pplf->getByIdAndCompanyId( $pay_period_ids, $this->getCurrentCompanyObject()->getId(), null, [ 'start_date' => 'asc' ] ); //Make sure pay periods are ordered by start date asc so they are calculated in order if the user happens to calculate multiple pay periods over a long period of time.
		if ( $pplf->getRecordCount() > 0 ) {
			foreach ( $pplf as $pay_period_obj ) { /** @var PayPeriodFactory $pay_period_obj */
				$epoch = TTDate::getTime();

				Debug::text( 'Pay Period ID: ' . $pay_period_obj->getID() . ' Schedule ID: ' . $pay_period_obj->getPayPeriodSchedule() . ' Start Date: ' . TTDate::getDate( 'DATE', $pay_period_obj->getStartDate() ) . ' Run ID: ' . $run_id, __FILE__, __LINE__, __METHOD__, 10 );
				if ( PRODUCTION == false || $pay_period_obj->isPreviousPayPeriodClosed() == true ) { //Allow generating pay stubs without closing each pay period when not in production.
					$pslf = TTnew( 'PayStubListFactory' ); /** @var PayStubListFactory $pslf */

					if ( (int)$run_id == 0 ) {
						//Since we could be generating pay stubs for multiple pay periods at the same time, we can't overwrite $run_id here, as it will be used for the next pay period and that may be incorrect for that pay period.
						// So unless a specific run_id is specified from the user, we need get the next run_id for each pay period in this loop.
						$tmp_run_id = PayStubListFactory::getCurrentPayRun( $this->getCurrentCompanyObject()->getId(), $pay_period_obj->getId() );
					} else {
						$tmp_run_id = $run_id;
					}
					Debug::text( '  Using Run ID: ' . $tmp_run_id, __FILE__, __LINE__, __METHOD__, 10 );

					//Check to make sure pay stubs with a transaction date before today are not open, as that can cause the payroll run number to be incorrectly determined on its own.
					$open_pay_stub_transaction_date = ( TTDate::getMiddleDayEpoch( $epoch ) >= TTDate::getMiddleDayEpoch( $pay_period_obj->getTransactionDate() ) ) ? $pay_period_obj->getTransactionDate() : TTDate::getBeginDayEpoch( $epoch );
					$pslf->getByCompanyIdAndPayPeriodIdAndStatusIdAndTransactionDateBeforeDate( $this->getCurrentCompanyObject()->getId(), $pay_period_obj->getID(), [ 25 ], $open_pay_stub_transaction_date, 1 );
					if ( $pslf->getRecordCount() > 0 ) {
						UserGenericStatusFactory::queueGenericStatus( TTi18n::gettext( 'ERROR' ), 10, TTi18n::gettext( 'Pay Stubs with a transaction date before today are still OPEN, all pay stubs must be PAID on or before their transaction date' ), null );
						continue;
					}
					unset( $open_pay_stub_transaction_date );

					if ( $tmp_run_id > 1 ) {                                                                                                                                                //Check to make sure prior payroll runs are marked as PAID.
						$pslf->getByCompanyIdAndPayPeriodIdAndStatusIdAndNotRun( $this->getCurrentCompanyObject()->getId(), $pay_period_obj->getId(), [ 10, 20, 25, 30 ], $tmp_run_id, 1 ); //Only need to return 1 record.
						if ( $pslf->getRecordCount() > 0 ) {
							$tmp_pay_stub_obj = $pslf->getCurrent();
							Debug::text( 'Pay Stub ID: ' . $tmp_pay_stub_obj->getID() . ' Run: ' . $tmp_pay_stub_obj->getRun() . ' Transaction Date: ' . TTDate::getDate( 'DATE', $tmp_pay_stub_obj->getTransactionDate() ), __FILE__, __LINE__, __METHOD__, 10 );
							UserGenericStatusFactory::queueGenericStatus( TTi18n::gettext( 'ERROR' ), 10, TTi18n::gettext( 'Payroll Run #%1 of Pay Period %2 is still OPEN, all pay stubs must be PAID before starting a new payroll run.', [ $tmp_pay_stub_obj->getRun(), TTDate::getDate( 'DATE', $pay_period_obj->getStartDate() ) . ' -> ' . TTDate::getDate( 'DATE', $pay_period_obj->getEndDate() ) ] ), null );
							unset( $tmp_pay_stub_obj );
							continue;
						}
					}
					unset( $pslf );

					//Grab all users for pay period
					$ppsulf = TTnew( 'PayPeriodScheduleUserListFactory' ); /** @var PayPeriodScheduleUserListFactory $ppsulf */
					if ( is_array( $user_ids ) && count( $user_ids ) > 0 && !in_array( TTUUID::getNotExistID(), $user_ids ) ) {
						Debug::text( 'Generating pay stubs for specific users...', __FILE__, __LINE__, __METHOD__, 10 );
						TTLog::addEntry( $this->getCurrentCompanyObject()->getId(), 500, TTi18n::gettext( 'Calculating Company Pay Stubs for Pay Period' ) . ': ' . TTDate::getDate( 'DATE', $pay_period_obj->getStartDate() ) . ' -> ' . TTDate::getDate( 'DATE', $pay_period_obj->getEndDate() ), $this->getCurrentUserObject()->getId(), 'pay_stub' ); //Notice
						$ppsulf->getByCompanyIDAndPayPeriodScheduleIdAndUserID( $this->getCurrentCompanyObject()->getId(), $pay_period_obj->getPayPeriodSchedule(), $user_ids );
					} else {
						Debug::text( 'Generating pay stubs for all users...', __FILE__, __LINE__, __METHOD__, 10 );
						TTLog::addEntry( $this->getCurrentCompanyObject()->getId(), 500, TTi18n::gettext( 'Calculating Employee Pay Stub for Pay Period' ) . ': ' . TTDate::getDate( 'DATE', $pay_period_obj->getStartDate() ) . ' -> ' . TTDate::getDate( 'DATE', $pay_period_obj->getEndDate() ), $this->getCurrentUserObject()->getId(), 'pay_stub' );
						$ppsulf->getByCompanyIDAndPayPeriodScheduleId( $this->getCurrentCompanyObject()->getId(), $pay_period_obj->getPayPeriodSchedule() );
					}
					$total_pay_stubs = $ppsulf->getRecordCount();

					if ( $total_pay_stubs > 0 ) {
						if ( ( !isset( $config_vars['other']['enable_job_queue'] ) || $config_vars['other']['enable_job_queue'] == true ) && $total_pay_stubs > 200 ) {
							Debug::text( '  Generating pay stubs in background: ' . $total_pay_stubs, __FILE__, __LINE__, __METHOD__, 10 );

							//Queue jobs so we can run them in the background.
							$this->getProgressBarObject()->start( $this->getAPIMessageID(), $total_pay_stubs, null, TTi18n::getText( 'Generating Paystubs...' ) );

							$user_generic_status_batch_id = TTUUID::generateUUID();
							foreach ( $ppsulf as $pay_period_schdule_user_obj ) {
								Debug::text( 'Queuing Calculate Pay Stub Job for User ID: ' . $pay_period_schdule_user_obj->getUser(), __FILE__, __LINE__, __METHOD__, 10 );
								SystemJobQueue::Add( TTi18n::getText( 'Calculating Pay Stubs' ), $user_generic_status_batch_id, 'CalculatePayStub', 'calculateForJobQueue', [ $this->getCurrentCompanyObject()->getId(), $this->getCurrentUserObject()->getId(), $pay_period_schdule_user_obj->getUser(), $type_id, $pay_period_obj->getId(), $pay_period_obj->getStartDate(), $pay_period_obj->getEndDate(), $tmp_run_id, $transaction_date, $enable_correction ], 25 );
							}

							SystemJobQueue::waitUntilBatchCompleted( $user_generic_status_batch_id, $this->getAPIMessageID(), 5, 7200 ); //Run up to 2hrs.
							unset( $batch_status );
						} else {
							$this->getProgressBarObject()->start( $this->getAPIMessageID(), $total_pay_stubs, null, TTi18n::getText( 'Generating Paystubs...' ) );

							//FIXME: If a pay stub already exists, it is deleted first, but then if the new pay stub fails to generate, the original one is
							//  still deleted, so that can catch some people off guard if they don't fix the problem and re-generate the paystubs again.
							//  This can be useful in some cases though, as the opposite problem may arise.

							//Delete existing pay stub. Make sure we only
							//delete pay stubs that are the same as what we're creating.
							$pslf = TTnew( 'PayStubListFactory' ); /** @var PayStubListFactory $pslf */
							$pslf->getByCompanyIdAndPayPeriodIdAndRun( $this->getCurrentCompanyObject()->getId(), $pay_period_obj->getId(), $tmp_run_id );
							if ( $pslf->getRecordCount() > 0 ) {
								foreach ( $pslf as $pay_stub_obj ) {
									if ( is_array( $user_ids ) && count( $user_ids ) > 0 && !in_array( TTUUID::getNotExistID(), $user_ids ) && in_array( $pay_stub_obj->getUser(), $user_ids ) == false ) {
										continue; //Only generating pay stubs for individual employees, skip ones not in the list.
									}
									Debug::text( 'Existing Pay Stub: ' . $pay_stub_obj->getId(), __FILE__, __LINE__, __METHOD__, 10 );

									//Check PS End Date to match with PP End Date
									//So if an ROE was generated, it won't get deleted when they generate all other Pay Stubs later on.
									//Unless the ROE used the exact same dates as the pay period? To avoid this, only delete pay stubs for employees with no termination date, or with a termination date after the pay period start date.
									if ( $pay_stub_obj->getStatus() <= 25
											&& $pay_stub_obj->getTainted() === false
											&& TTDate::getMiddleDayEpoch( $pay_stub_obj->getEndDate() ) == TTDate::getMiddleDayEpoch( $pay_period_obj->getEndDate() )
											&& ( is_object( $pay_stub_obj->getUserObject() ) && ( $pay_stub_obj->getUserObject()->getTerminationDate() == '' || TTDate::getMiddleDayEpoch( $pay_stub_obj->getUserObject()->getTerminationDate() ) >= TTDate::getMiddleDayEpoch( $pay_period_obj->getStartDate() ) ) ) ) {
										Debug::text( 'Deleting pay stub: ' . $pay_stub_obj->getId(), __FILE__, __LINE__, __METHOD__, 10 );
										$pay_stub_obj->setDeleted( true );
										if ( $pay_stub_obj->isValid() == true ) { //Make sure we validate on delete, in case there are paid transactions.
											$pay_stub_obj->Save();
										} else {
											Debug::text( 'ERROR: Unable to delete old pay stub to regenerate it...', __FILE__, __LINE__, __METHOD__, 10 );
										}
									} else {
										Debug::text( 'Pay stub does not need regenerating, or it is LOCKED! ID: ' . $pay_stub_obj->getID() . ' Status: ' . $pay_stub_obj->getStatus() . ' Tainted: ' . (int)$pay_stub_obj->getTainted() . ' Pay Stub End Date: ' . $pay_stub_obj->getEndDate() . ' Pay Period End Date: ' . $pay_period_obj->getEndDate(), __FILE__, __LINE__, __METHOD__, 10 );
									}
								}
							}

							$i = 1;
							foreach ( $ppsulf as $pay_period_schdule_user_obj ) {
								Debug::text( 'Pay Period User ID: ' . $pay_period_schdule_user_obj->getUser(), __FILE__, __LINE__, __METHOD__, 10 );
								Debug::text( 'Total Pay Stubs: ' . $total_pay_stubs . ' - ' . ceil( 1 / ( 100 / $total_pay_stubs ) ), __FILE__, __LINE__, __METHOD__, 10 );

								$profiler->startTimer( 'Calculating Pay Stub' );
								//Calc paystubs.
								$cps = new CalculatePayStub();
								$cps->setEnableCorrection( (bool)$enable_correction );
								if ( $type_id == 90 ) {                                //Allow YTD Adjustment pay stubs to be after the termination date. They must have a $0 net pay anyways, so this makes it easy for Employer Taxes and such to be corrected after termination.
									$cps->setEnablePostTerminationCalculation( true ); //Allow calculating pay stubs after termination date.
								}
								$cps->setUser( $pay_period_schdule_user_obj->getUser() );
								$cps->setPayPeriod( $pay_period_obj->getId() );
								$cps->setType( $type_id );
								$cps->setRun( $tmp_run_id );
								if ( $transaction_date != '' ) {
									$cps->setTransactionDate( TTDate::parseDateTime( $transaction_date ) );
								}
								$cps->calculate();
								unset( $cps );
								$profiler->stopTimer( 'Calculating Pay Stub' );

								$this->getProgressBarObject()->set( $this->getAPIMessageID(), $i );

								//sleep(1); /////////////////////////////// FOR TESTING ONLY //////////////////

								$i++;
							}
							unset( $ppsulf );

							$this->getProgressBarObject()->stop( $this->getAPIMessageID() );
						}
					} else {
						Debug::text( 'ERROR: User not assigned to pay period schedule...', __FILE__, __LINE__, __METHOD__, 10 );
						UserGenericStatusFactory::queueGenericStatus( TTi18n::gettext( 'ERROR' ), 10, TTi18n::gettext( 'Unable to generate pay stub(s), employee(s) may not be assigned to a pay period schedule.' ), null );
					}
				} else {
					UserGenericStatusFactory::queueGenericStatus( TTi18n::gettext( 'ERROR' ), 10, TTi18n::gettext( 'Pay period prior to %1 is not closed, please close all previous pay periods and try again...', [ TTDate::getDate( 'DATE', $pay_period_obj->getStartDate() ) . ' -> ' . TTDate::getDate( 'DATE', $pay_period_obj->getEndDate() ) ] ), null );
				}
			}
		}

		if ( UserGenericStatusFactory::isStaticQueue() == true ) {
			$ugsf = TTnew( 'UserGenericStatusFactory' ); /** @var UserGenericStatusFactory $ugsf */
			$ugsf->setUser( $this->getCurrentUserObject()->getId() );
			$ugsf->setBatchID( $ugsf->getNextBatchId() );
			$ugsf->setQueue( UserGenericStatusFactory::getStaticQueue() );
			$ugsf->saveQueue();
			$user_generic_status_batch_id = $ugsf->getBatchID();
		}
		unset( $ugsf );

		return $this->returnHandler( true, true, false, false, false, $user_generic_status_batch_id );
	}

	/**
	 * @param string $pay_period_ids UUID
	 * @return int
	 */
	function getCurrentPayRun( $pay_period_ids ) {
		$retval = 1;
		if ( is_array( $pay_period_ids ) && count( $pay_period_ids ) > 0 ) {
			$retval = PayStubListFactory::getCurrentPayRun( $this->getCurrentCompanyObject()->getId(), $pay_period_ids );
		}

		Debug::Text( '  Current Run ID: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );

		return $retval;
	}
}

?>