<?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 Core
 */
class Wage {
	var $user_id = null;
	var $pay_period_id = null;
	var $advance = false;

	var $user_date_total_arr = null;

	var $user_obj = null;
	var $user_tax_obj = null;
	var $user_wage_obj = null;
	var $user_pay_period_total_obj = null;
	var $pay_stub_entry_account_link_obj = null;

	var $pay_period_obj = null;
	var $pay_period_schedule_obj = null;

	var $labor_standard_obj = null;
	var $holiday_obj = null;

	/**
	 * Wage constructor.
	 * @param string $user_id       UUID
	 * @param string $pay_period_id UUID
	 */
	function __construct( $user_id, $pay_period_id ) {
		$this->user_id = $user_id;
		$this->pay_period_id = $pay_period_id;

		return true;
	}

	/**
	 * @return null
	 */
	function getUser() {
		return $this->user_id;
	}

	/**
	 * @return null
	 */
	function getPayPeriod() {
		return $this->pay_period_id;
	}

	/**
	 * @return bool
	 */
	function getAdvance() {
		if ( isset( $this->advance ) ) {
			return $this->advance;
		}

		return false;
	}

	/**
	 * @param $bool
	 * @return bool
	 */
	function setAdvance( $bool ) {
		$this->advance = $bool;

		return true;
	}

	//Because this class doesn't extend the original Factory class, we have to duplicate the getGenericObject() code here.

	/**
	 * @return bool|null
	 */
	function getUserObject() {
		if ( isset( $this->user_obj ) && is_object( $this->user_obj ) && $this->getUser() == $this->user_obj->getID() ) {
			return $this->user_obj;
		} else {
			$lf = TTnew( 'UserListFactory' ); /** @var UserListFactory $lf */
			$lf->getById( $this->getUser() );
			if ( $lf->getRecordCount() == 1 ) {
				$this->user_obj = $lf->getCurrent();

				return $this->user_obj;
			}

			return false;
		}
	}

	/**
	 * @return bool|null
	 */
	function getPayPeriodObject() {
		if ( isset( $this->pay_period_obj ) && is_object( $this->pay_period_obj ) && $this->getPayPeriod() == $this->pay_period_obj->getID() ) {
			return $this->pay_period_obj;
		} else {
			$lf = TTnew( 'PayPeriodListFactory' ); /** @var PayPeriodListFactory $lf */
			$lf->getById( $this->getPayPeriod() );
			if ( $lf->getRecordCount() == 1 ) {
				$this->pay_period_obj = $lf->getCurrent();

				return $this->pay_period_obj;
			}

			return false;
		}
	}

	/**
	 * @return bool|null
	 */
	function getPayPeriodScheduleObject() {
		$pay_period_schedule_id = TTUUID::getZeroID();
		if ( is_object( $this->getPayPeriodObject() ) ) {
			$pay_period_schedule_id = $this->getPayPeriodObject()->getPayPeriodSchedule();
		}
		if ( isset( $this->pay_period_schedule_obj ) && is_object( $this->pay_period_schedule_obj ) && $pay_period_schedule_id == $this->pay_period_schedule_obj->getID() ) {
			return $this->pay_period_schedule_obj;
		} else {
			$lf = TTnew( 'PayPeriodScheduleListFactory' ); /** @var PayPeriodScheduleListFactory $lf */
			$lf->getById( $pay_period_schedule_id );
			if ( $lf->getRecordCount() == 1 ) {
				$this->pay_period_schedule_obj = $lf->getCurrent();

				return $this->pay_period_schedule_obj;
			}

			return false;
		}
	}

	/**
	 * @return bool|null
	 */
	function getPayStubEntryAccountLinkObject() {
		$company_id = TTUUID::getZeroID();
		if ( is_object( $this->getUserObject() ) ) {
			$company_id = $this->getUserObject()->getCompany();
		}

		if ( isset( $this->pay_stub_entry_account_link_obj ) && is_object( $this->pay_stub_entry_account_link_obj ) && $company_id == $this->pay_stub_entry_account_link_obj->getCompany() ) {
			return $this->pay_stub_entry_account_link_obj;
		} else {
			$lf = TTnew( 'PayStubEntryAccountLinkListFactory' ); /** @var PayStubEntryAccountLinkListFactory $lf */
			$lf->getByCompanyId( $company_id );
			if ( $lf->getRecordCount() == 1 ) {
				$this->pay_stub_entry_account_link_obj = $lf->getCurrent();

				return $this->pay_stub_entry_account_link_obj;
			}

			return false;
		}
	}

	/**
	 * @param $seconds
	 * @param $rate
	 * @return int|string
	 */
	function getWage( $seconds, $rate ) {
		if ( $seconds == '' || empty( $seconds ) ) {
			return 0;
		}

		if ( $rate == '' || empty( $rate ) ) {
			return 0;
		}

		return bcmul( TTDate::getHours( $seconds ), $rate );
	}

	/**
	 * @param object $user_wage_obj
	 * @return string
	 */
	function getMaximumPayPeriodWage( $user_wage_obj ) {
		if ( is_object( $user_wage_obj ) && is_object( $this->getPayPeriodScheduleObject() ) && $this->getPayPeriodScheduleObject()->getAnnualPayPeriods() > 0 ) {
			$maximum_pay_period_wage = bcdiv( $user_wage_obj->getAnnualWage(), $this->getPayPeriodScheduleObject()->getAnnualPayPeriods() );
			Debug::text( 'Absolute Maximum Pay Period (NO Advance): Wage: ' . $maximum_pay_period_wage . ' User Wage ID: ' . $user_wage_obj->getId() . ' Annual Wage: ' . $user_wage_obj->getAnnualWage() . ' Annual Pay Periods: ' . $this->getPayPeriodScheduleObject()->getAnnualPayPeriods(), __FILE__, __LINE__, __METHOD__, 10 );
		} else {
			Debug::text( 'WARNING: Pay Period Schedule does not exist, or annual pay periods is 0...', __FILE__, __LINE__, __METHOD__, 10 );
			$maximum_pay_period_wage = 0;
		}

		return $maximum_pay_period_wage;
	}

	/**
	 * @return string
	 */
	function getPayStubAmendmentEarnings() {
		//Get pay stub amendments here.
		$psalf = TTnew( 'PayStubAmendmentListFactory' ); /** @var PayStubAmendmentListFactory $psalf */

		if ( $this->getAdvance() == true ) {
			//For advances, any PS amendment effective BEFORE the advance end date is considered in full.
			//Any AFTER the advance end date, is considered half.

			//$pay_period_end_date = $this->getPayPeriodObject()->getAdvanceEndDate();
			$advance_pos_sum = $psalf->getAmountSumByUserIdAndTypeIdAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 10, true, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getAdvanceEndDate() );
			Debug::text( 'Pay Stub Amendment Advance Earnings: ' . $advance_pos_sum, __FILE__, __LINE__, __METHOD__, 10 );

			$full_pos_sum = $psalf->getAmountSumByUserIdAndTypeIdAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 10, true, $this->getPayPeriodObject()->getAdvanceEndDate(), $this->getPayPeriodObject()->getEndDate() );
			Debug::text( 'Pay Stub Amendment Full Earnings: ' . $full_pos_sum, __FILE__, __LINE__, __METHOD__, 10 );
			//Take the full amount of PS amendments BEFORE the advance end date, and half of any AFTER the advance end date.
			//$pos_sum = $advance_pos_sum + ($full_pos_sum / 2);
			$pos_sum = bcadd( $advance_pos_sum, bcdiv( $full_pos_sum, 2 ) );
		} else {
			$pos_sum = $psalf->getAmountSumByUserIdAndTypeIdAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 10, true, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getEndDate() );
		}
		//$neg_sum = $psalf->getAmountSumByUserIdAndTypeIdAndTaxExemptAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 20, FALSE, TRUE, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getEndDate() )*-1;

		Debug::text( 'Pay Stub Amendment Total Earnings: ' . $pos_sum, __FILE__, __LINE__, __METHOD__, 10 );

		return $pos_sum;
	}

	/**
	 * @return string
	 */
	function getPayStubAmendmentDeductions() {
		//Get pay stub amendments here.
		$psalf = TTnew( 'PayStubAmendmentListFactory' ); /** @var PayStubAmendmentListFactory $psalf */

		if ( $this->getAdvance() == true ) {
			//For advances, any PS amendment effective BEFORE the advance end date is considered in full.
			//Any AFTER the advance end date, is considered half.

			//$pay_period_end_date = $this->getPayPeriodObject()->getAdvanceEndDate();
			$advance_neg_sum = $psalf->getAmountSumByUserIdAndTypeIdAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 20, true, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getAdvanceEndDate() );
			Debug::text( 'Pay Stub Amendment Advance Deductions: ' . $advance_neg_sum, __FILE__, __LINE__, __METHOD__, 10 );

			$full_neg_sum = $psalf->getAmountSumByUserIdAndTypeIdAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 20, true, $this->getPayPeriodObject()->getAdvanceEndDate(), $this->getPayPeriodObject()->getEndDate() );
			Debug::text( 'Pay Stub Amendment Full Deductions: ' . $full_neg_sum, __FILE__, __LINE__, __METHOD__, 10 );
			//Take the full amount of PS amendments BEFORE the advance end date, and half of any AFTER the advance end date.
			//$neg_sum = $advance_neg_sum + ($full_neg_sum / 2);
			$neg_sum = bcadd( $advance_neg_sum, bcdiv( $full_neg_sum, 2 ) );
		} else {
			//$pay_period_end_date =
			$neg_sum = $psalf->getAmountSumByUserIdAndTypeIdAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 20, true, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getEndDate() );
		}
		//$neg_sum = $psalf->getAmountSumByUserIdAndTypeIdAndTaxExemptAndAuthorizedAndStartDateAndEndDate( $this->getUser(), 20, FALSE, TRUE, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getEndDate() )*-1;


		Debug::text( 'Pay Stub Amendment Total Deductions: ' . $neg_sum, __FILE__, __LINE__, __METHOD__, 10 );

		return bcmul( $neg_sum, -1 );
	}

	/**
	 * @return int
	 */
	function getRawGrossWage() {
		$wage = 0;

		$udt_arr = $this->getUserDateTotalArray();
		if ( isset( $udt_arr['entries'] ) && count( $udt_arr['entries'] ) > 0 ) {
			foreach ( $udt_arr['entries'] as $udt ) {
				if ( isset( $udt['amount'] ) ) {
					$wage += $udt['amount'];
				}
			}
		}

		Debug::text( 'Raw Gross Wage: ' . $wage, __FILE__, __LINE__, __METHOD__, 10 );

		return $wage;
	}

	/**
	 * @return int
	 */
	function getGrossWage() {

		$wage = $this->getRawGrossWage();

		Debug::text( 'Gross Wage (NOT incl amendments) $' . $wage, __FILE__, __LINE__, __METHOD__, 10 );

		return $wage;
	}

	/**
	 * @return array|bool|null
	 */
	function getUserDateTotalArray() {
		if ( isset( $this->user_date_total_arr ) ) {
			return $this->user_date_total_arr;
		}

		//If the user date total array isn't set, set it now, and return its value.
		return $this->setUserDateTotalArray();
		//return FALSE;
	}

	/**
	 * @return array|bool
	 */
	function setUserDateTotalArray() {
		$calculate_salary = false;

		$dock_absence_time = 0;
		$paid_absence_time = 0;
		$dock_absence_amount = 0;
		$paid_absence_amount = 0;
		$prev_wage_effective_date = 0;
		$paid_absence_amount_arr = [];
		$reduce_salary_absence_amount_arr = [];
		$salary_regular_time = [];
		$dock_absence_amount_arr = [];
		$ret_arr = [];

		//Loop through unique UserDateTotal rows... Adding entries to pay stubs.
		$udtlf = TTnew( 'UserDateTotalListFactory' ); /** @var UserDateTotalListFactory $udtlf */
		$udtlf->getByUserIdAndPayPeriodIdAndEndDate( $this->getUser(), $this->getPayPeriod(), $this->getPayPeriodObject()->getEndDate() );
		if ( $udtlf->getRecordCount() > 0 ) {
			foreach ( $udtlf as $udt_obj ) {
				Debug::text( 'User Total Row... Object Type: ' . $udt_obj->getObjectType() . ' PayCode ID: ' . $udt_obj->getPayCode() . ' Amount: ' . $udt_obj->getTotalTimeAmount() . ' Hourly Rate: ' . $udt_obj->getColumn( 'hourly_rate' ) . ' Pay Code Type: ' . $udt_obj->getColumn( 'pay_code_type_id' ) . ' User Wage ID: ' . $udt_obj->getColumn( 'user_wage_id' ), __FILE__, __LINE__, __METHOD__, 10 );

				if ( $udt_obj->getColumn( 'pay_code_type_id' ) == 10 ) { //Paid
					if ( $udt_obj->getObjectType() == 25 ) { //Absence
						Debug::text( 'User Total Row... Absence Time: ' . $udt_obj->getTotalTime(), __FILE__, __LINE__, __METHOD__, 10 );

						if ( is_object( $udt_obj->getPayCodeObject() )
								&& ( $udt_obj->getPayCodeObject()->getType() == 10 || $udt_obj->getPayCodeObject()->getType() == 12 )
								&& $udt_obj->getPayCodeObject()->getPayStubEntryAccountID() != '' ) { //Paid
							Debug::text( 'Paid Absence Time: ' . $udt_obj->getTotalTime(), __FILE__, __LINE__, __METHOD__, 10 );

							$pay_stub_entry = $udt_obj->getPayCodeObject()->getPayStubEntryAccountID();
							$total_time = $udt_obj->getTotalTime();
							$rate = $udt_obj->getColumn( 'hourly_rate' );
							$amount = $udt_obj->getTotalTimeAmount();

							//Debug::text('Paid Absence Info: '. $udt_obj->getTotalTime(), __FILE__, __LINE__, __METHOD__, 10);
							Debug::text( 'cPay Stub Entry Account ID: ' . $pay_stub_entry . ' Amount: ' . $amount . ' Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 );

							$paid_absence_time = bcadd( $paid_absence_time, $udt_obj->getTotalTime() );
							$paid_absence_amount = bcadd( $paid_absence_amount, $amount );

							//Make sure we add the amount below. Incase there are two or more
							//entries for a paid absence in the same user_wage_id on one pay stub.
							if ( !isset( $paid_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] ) ) {
								$paid_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] = 0;
							}
							$paid_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] = bcadd( $paid_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )], $amount );

							//Some paid absences are over and above employees salary, so we need to track them separately.
							//So we only reduce the salary of the amount of regular paid absences, not "Paid (Above Salary)" absences.
							if ( !isset( $reduce_salary_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] ) ) {
								$reduce_salary_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] = 0;
							}
							if ( $udt_obj->getColumn( 'pay_code_type_id' ) == 10 ) {
								$reduce_salary_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] = bcadd( $reduce_salary_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )], $amount );
							}
						}
					} else {
						//Check if they are a salary user...
						//Use WORKED time to calculate regular time. Not just regular time.
						//user_wage_id is only needed for default wages, so it doesn't take into account pay formulas at all.
						if ( $udt_obj->getColumn( 'user_wage_type_id' ) > 10 ) { //Salaried
							//Salary
							Debug::text( 'Strict Salary Wage: Reduce Regular Pay By: Dock Time: ' . $dock_absence_time . ' and Paid Absence: ' . $paid_absence_time, __FILE__, __LINE__, __METHOD__, 10 );

							$calculate_salary = true;

							if ( !isset( $salary_regular_time[$udt_obj->getColumn( 'user_wage_id' )] ) ) {
								$salary_regular_time[$udt_obj->getColumn( 'user_wage_id' )] = 0;
							}

							//Only include regular time units in salary calculation.
							if ( $udt_obj->getObjectType() == 20 ) { //Regular Time
								$salary_regular_time[$udt_obj->getColumn( 'user_wage_id' )] += $udt_obj->getTotalTime();
							}
						} else {
							//Hourly
							Debug::text( 'Hourly Wage', __FILE__, __LINE__, __METHOD__, 10 );
							$pay_stub_entry = $udt_obj->getPayCodeObject()->getPayStubEntryAccountId();
							$total_time = $udt_obj->getTotalTime();
							$rate = $udt_obj->getColumn( 'hourly_rate' );
							$amount = $udt_obj->getTotalTimeAmount();
							Debug::text( 'aPay Stub Entry Account ID: ' . $pay_stub_entry . ' Amount: ' . $amount .' Rate: '. $rate, __FILE__, __LINE__, __METHOD__, 10 );
						}
					}
				} else if ( $udt_obj->getColumn( 'pay_code_type_id' ) == 12 ) { //Paid Above
					//This is typically but not always overtime/premium time.
					if ( is_object( $udt_obj->getPayCodeObject() ) && $udt_obj->getColumn( 'hourly_rate' ) != 0 ) {
						Debug::text( 'Paid (Above Salary) Time... Rate: ' . $udt_obj->getColumn( 'hourly_rate' ), __FILE__, __LINE__, __METHOD__, 10 );
						$pay_stub_entry = $udt_obj->getPayCodeObject()->getPayStubEntryAccountId();
						$total_time = $udt_obj->getTotalTime();
						$rate = $udt_obj->getColumn( 'hourly_rate' );
						$amount = $udt_obj->getTotalTimeAmount();
						Debug::text( 'bPay Stub Entry Account ID: ' . $pay_stub_entry . ' Amount: ' . $amount . ' Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 );
					} else {
						Debug::text( '  NOT Paid Time Policy...', __FILE__, __LINE__, __METHOD__, 10 );
					}
//				//We shouldn't do anything with UNPAID pay codes. If there needs to be Tax/Deduction calculations based on unpaid timesheet information, they should be marked as "PAID" but have a $0 hourly rate instead.
//				//PayStubFactory will still accept $0 amount entries, allow calculations to be made on them, then remove them at the last second though.
//				} elseif ( $udt_obj->getColumn('pay_code_type_id') == 20 ) { //UnPaid
//					//Pass through even unpaid time to pay stubs, so Tax/Deduction records can base calculations on just units/hours if needed to create other earnings amounts.
//					if ( is_object( $udt_obj->getPayCodeObject() ) ) {
//						Debug::text('UnPaid Time... Total Time: '. $udt_obj->getTotalTime() .' Rate: '. $udt_obj->getColumn('hourly_rate'), __FILE__, __LINE__, __METHOD__, 10);
//						$pay_stub_entry = $udt_obj->getPayCodeObject()->getPayStubEntryAccountId();
//						$total_time = $udt_obj->getTotalTime();
//						$rate = $udt_obj->getColumn('hourly_rate');
//						$amount = $udt_obj->getTotalTimeAmount();
//						Debug::text('cPay Stub Entry Account ID: '. $pay_stub_entry .' Amount: '. $amount .' Rate: '. $rate, __FILE__, __LINE__, __METHOD__, 10);
//					} else {
//						Debug::text('  NOT UDT Object...', __FILE__, __LINE__, __METHOD__, 10);
//					}
				} else if ( $udt_obj->getColumn( 'pay_code_type_id' ) == 30 ) { //Dock
					$dock_absence_time = bcadd( $dock_absence_time, $udt_obj->getTotalTime() );
					$rate = $udt_obj->getColumn( 'hourly_rate' );
					$amount = $this->getWage( $udt_obj->getTotalTime(), $rate );
					$dock_absence_amount = bcadd( $dock_absence_amount, $amount );

					//Make sure we account for multiple dock absence policies, for the same wage entry in the same pay period.
					if ( isset( $dock_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] ) ) {
						$dock_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] += $amount;
					} else {
						$dock_absence_amount_arr[$udt_obj->getColumn( 'user_wage_id' )] = $amount;
					}

					Debug::text( 'DOCK Absence Time.. Adding: ' . $udt_obj->getTotalTime() . ' Total: ' . $dock_absence_time . ' Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 );
					unset( $rate );
				}

				if ( isset( $pay_stub_entry ) && $pay_stub_entry != '' ) {
					Debug::text( 'zPay Stub Entry Account ID: ' . $pay_stub_entry . ' Amount: ' . $amount, __FILE__, __LINE__, __METHOD__, 10 );
					$ret_arr['entries'][] = [
							'user_wage_id'   => $udt_obj->getColumn( 'user_wage_id' ),
							'pay_stub_entry' => $pay_stub_entry,
							'total_time'     => $total_time,
							'amount'         => $amount,
							'rate'           => $rate,
							'description'    => null,
					];
				}
				unset( $pay_stub_entry, $amount, $total_time, $rate );
			}

			if ( $calculate_salary == true ) {
				//When the employee is salary and their wage changes in the middle of the pay period and they don't have any regular time worked
				//in any one of the periods that either wage is effective, the period without any regular time was not being paid before.
				//Therefore we moved the salary calcuations to the very end and if there is any regular time in the entire pay period
				//we simply loop through all salaried wages and calculate the pro-rated amounts. Even if no regular time exists in one of the wage periods.
				Debug::text( 'Calculating Salary...', __FILE__, __LINE__, __METHOD__, 10 );

				//Get all wages that apply in this period so we can determine pro-rating for salaries.
				$uwlf = TTNew( 'UserWageListFactory' ); /** @var UserWageListFactory $uwlf */
				$uwlf->getDefaultWageGroupByUserIdAndStartDateAndEndDate( $this->getUser(), $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getEndDate() ); //ORDER BY effective_date desc
				if ( $uwlf->getRecordCount() > 0 ) {
					foreach ( $uwlf as $uw_obj ) {
						$description = null;
						if ( $uw_obj->getType() != 10 ) {
							if ( isset( $dock_absence_amount_arr[$uw_obj->getID()] ) ) {
								$dock_absence_wage = abs( $dock_absence_amount_arr[$uw_obj->getID()] ); //Make sure the dock absence wage is always a positive, since we subtract is below.
							} else {
								$dock_absence_wage = 0;
							}
							if ( isset( $reduce_salary_absence_amount_arr[$uw_obj->getID()] ) ) {
								$paid_absence_wage = abs( $reduce_salary_absence_amount_arr[$uw_obj->getID()] ); //Make sure the dock absence wage is always a positive, since we subtract is below.
							} else {
								$paid_absence_wage = 0;
							}
							Debug::text( 'Wage ID: ' . $uw_obj->getID() . ' Dock Absence Wage: ' . $dock_absence_wage . ' Paid Absence Wage: ' . $paid_absence_wage, __FILE__, __LINE__, __METHOD__, 10 );

							$maximum_wage_salary = UserWageFactory::proRateSalary( $this->getMaximumPayPeriodWage( $uw_obj ), $uw_obj->getEffectiveDate(), $prev_wage_effective_date, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getEndDate(), $this->getUserObject()->getHireDate(), $this->getUserObject()->getTerminationDate() );

							$amount = bcsub( $maximum_wage_salary, bcadd( $dock_absence_wage, $paid_absence_wage ) );
							//Include time if we have it, otherwise use 0.
							$total_time = ( isset( $salary_regular_time[$uw_obj->getID()] ) ) ? $salary_regular_time[$uw_obj->getID()] : 0; //Dont minus dock/paid absence time. Because its already not included.
							$rate = null;
							$pay_stub_entry = $this->getPayStubEntryAccountLinkObject()->getRegularTime();
							unset( $dock_absence_wage, $paid_absence_wage );

							$salary_dates = UserWageFactory::proRateSalaryDates( $uw_obj->getEffectiveDate(), $prev_wage_effective_date, $this->getPayPeriodObject()->getStartDate(), $this->getPayPeriodObject()->getEndDate(), $this->getUserObject()->getHireDate(), $this->getUserObject()->getTerminationDate() );
							if ( is_array( $salary_dates ) && isset( $salary_dates['percent'] ) && $salary_dates['percent'] < 100 ) {
								$description = TTi18n::getText( 'Prorate Salary' ) . ': ' . TTDate::getDate( 'DATE', $salary_dates['start_date'] ) . ' - ' . TTDate::getDate( 'DATE', $salary_dates['end_date'] ) . ' (' . $salary_dates['percent'] . '%)';
							}

							if ( isset( $pay_stub_entry ) && $pay_stub_entry != '' ) {
								Debug::text( '  Pay Stub Entry Account ID: ' . $pay_stub_entry . ' Amount: ' . $amount, __FILE__, __LINE__, __METHOD__, 10 );
								$ret_arr['entries'][] = [
										'user_wage_id'   => $udt_obj->getColumn( 'user_wage_id' ),
										'pay_stub_entry' => $pay_stub_entry,
										'total_time'     => $total_time,
										'amount'         => $amount,
										'rate'           => $rate,
										'description'    => $description,
								];
							}


							unset( $pay_stub_entry, $amount, $total_time, $rate );
						}

						//Must go outside the $uw_obj->getType() != 10 check, so we can properly switch from Salary to Hourly in the middle of a PP.
						$prev_wage_effective_date = $uw_obj->getEffectiveDate();
					}
				}
				unset( $uwlf, $uw_obj );
			}

			//Compact entries to minimize line items on pay stubs unless absolutely necessary.
			if ( isset( $ret_arr['entries'] ) ) {
				$ret_arr['entries'] = $this->compactUserDateTotalPayStubEntries( $ret_arr['entries'] );
			}
		} else {
			Debug::text( 'NO UserDate Total entries found.', __FILE__, __LINE__, __METHOD__, 10 );
		}

		$ret_arr['other']['paid_absence_time'] = $paid_absence_time;
		$ret_arr['other']['dock_absence_time'] = $dock_absence_time;

		$ret_arr['other']['paid_absence_amount'] = $paid_absence_amount;
		$ret_arr['other']['dock_absence_amount'] = $dock_absence_amount;

		if ( empty( $ret_arr ) == false ) {
			Debug::Arr( $ret_arr, 'UserDateTotal Array', __FILE__, __LINE__, __METHOD__, 10 );

			return $this->user_date_total_arr = $ret_arr;
		}

		return false;
	}

	/**
	 * Compact wage entries that have the same: user_wage_id, pay_stub_entry, rate and description.
	 * This is primarily to avoid having multiple Regular Time entries for each meal/break policy that they have, which can be confusing for some employees.
	 * @param $array
	 * @return array
	 */
	function compactUserDateTotalPayStubEntries( $array ) {
		$retarr = [];
		if ( is_array( $array ) ) {
			foreach( $array as $entry_arr ) {
				$compact_key = md5( $entry_arr['user_wage_id'] . $entry_arr['pay_stub_entry'] . Misc::MoneyRound( $entry_arr['rate'], 4 ) . $entry_arr['description'] );
				if ( isset( $retarr[$compact_key] ) ) {
					//Found matching Wage/PayStubEntry/Rate/Description, simply add total_time and amount to it, rather than create a new entry.
					$retarr[$compact_key]['total_time'] = bcadd( $retarr[$compact_key]['total_time'], $entry_arr['total_time'] );
					$retarr[$compact_key]['amount'] = bcadd( $retarr[$compact_key]['amount'], $entry_arr['amount'] );
				} else {
					$retarr[$compact_key] = $entry_arr;
				}
			}
		}

		return array_values( $retarr );
	}
}

?>