TimeTrex/classes/modules/policy/PayFormulaPolicyFactory.class.php

978 lines
34 KiB
PHP
Raw Normal View History

2022-12-13 07:10:06 +01:00
<?php
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
/**
* @package Modules\Policy
*/
class PayFormulaPolicyFactory extends Factory {
protected $table = 'pay_formula_policy';
protected $pk_sequence_name = 'pay_formula_policy_id_seq'; //PK Sequence name
protected $company_obj = null;
protected $accrual_policy_account_obj = null;
protected $accrual_balance_threshold_fallback_accrual_policy_account_obj = null;
/**
* @param $name
* @param null $parent
* @return array|null
*/
function _getFactoryOptions( $name, $parent = null ) {
//Attempt to get the edition of the currently logged in users company, so we can better tailor the columns to them.
$product_edition_id = Misc::getCurrentCompanyProductEdition();
$retval = null;
switch ( $name ) {
case 'pay_type':
//THIS NEEDS TO GO BACK TO EACH INDIVIDUAL POLICY
//Otherwise customers will just need to duplicate pay codes for each policy in many cases.
// ** Actually since they can use Regular Time policies, they shouldn't need as many policies even if the rate is only defined in the pay code.
// ** Pay Code *must* define the rate though, as we need to support manual entry of codes.
$retval = [
10 => TTi18n::gettext( 'Pay Multiplied By Factor' ),
//20 => TTi18n::gettext('Premium Only'), //Just the specified premium amount. This is now #32 though as that makes more sense.
30 => TTi18n::gettext( 'Flat Hourly Rate (Relative to Wage)' ), //This is a relative rate based on their hourly rate.
32 => TTi18n::gettext( 'Flat Hourly Rate' ), //NOT relative to their default rate.
34 => TTi18n::gettext( 'Flat Hourly Rate (w/Default)' ), //Uses the hourly rate in the pay formula as the default, unless a wage record exists, then it uses that instead, even if its lower or higher.
40 => TTi18n::gettext( 'Minimum Hourly Rate (Relative to Wage)' ), //Pays whichever is greater, this rate or the employees original rate.
42 => TTi18n::gettext( 'Minimum Hourly Rate' ), //Pays whichever is greater, this rate or the employees original rate.
50 => TTi18n::gettext( 'Pay + Premium' ),
];
if ( $product_edition_id >= TT_PRODUCT_PROFESSIONAL ) { //These require wage_source_type=30, which is professional edition or higher.
$retval[60] = TTi18n::gettext( 'Daily Flat Rate (w/Default)' ); //Uses the hourly (daily) rate in the pay formula as the default, unless a wage record exists, then it uses that instead, even if its lower or higher.
$retval[70] = TTi18n::gettext( 'Daily Average Rate' );
}
if ( $product_edition_id >= TT_PRODUCT_CORPORATE ) { //These require wage_source_type=30, which is professional edition or higher.
$retval[200] = TTi18n::gettext( 'Piece Rate (per Good Quantity)' );
//$retval[210] = TTi18n::gettext( 'Piece Rate w/Minimum Hourly Rate (Relative to Wage)' ); //Use the employees wage as their minimum hourly rate.
//900 => TTi18n::gettext('Custom Formula'), //Can be used to calculate piece work rates.
}
break;
case 'wage_source_type':
//Used to calculate wages based on inputs other than just their wage record.
//For example if an employee works in two different departments at two different rates, average them then calculate OT on the average.
// This should help cut down on requiring a ton of OT policies for each different rate of pay the employee can get.
//
//****PAY CODES HAVE TO CALCULATE PAY, SO THEY CAN BE MANUALLY ENTERED DIRECTLY FROM A MANUAL TIMESHEET.
// Have two levels of rate calculations, so the premium policy can calculate its own rate, then pass it off to the pay code, which can do additional calculations.
// For Chesapeake, they would only need different pay codes for Regular Rate, then OT would all be based on that, so it actually wouldn't be that bad.
//Label: Obtain Hourly Rate From:
$retval = [
10 => TTi18n::gettext( 'Wage Group' ),
//"Code" is singular, as it can just be one. Input pay code calculation
// This is basically the source policy(?)
20 => TTi18n::gettext( 'Contributing Pay Code' ),
];
if ( $product_edition_id >= TT_PRODUCT_PROFESSIONAL ) {
//Required to calculate US federal OT rates that include premium time.
//But what date range is the average over? Daily, Weekly, Per Pay Period?
$retval[30] = TTi18n::gettext( 'Average of Contributing Pay Codes' ); //Input pay code average calculation
//For cases where the contract rate may be lower than the FLSA rate, in which case FLSA needs to be used.
//But in some cases the contract rate may be higher then it needs to be used.
//http://docs.oracle.com/cd/E13053_01/hr9pbr1_website_master/eng/psbooks/hpay/chapter.htm?File=hpay/htm/hpay38.htm
//40 => TTi18n::gettext('Higher of the Contributing Pay Code or Average'),
}
break;
case 'columns':
$retval = [
'-1010-name' => TTi18n::gettext( 'Name' ),
'-1020-description' => TTi18n::gettext( 'Description' ),
'-1100-pay_type' => TTi18n::gettext( 'Pay Type' ),
'-1110-rate' => TTi18n::gettext( 'Rate' ),
'-1120-accrual_rate' => TTi18n::gettext( 'Accrual Rate' ),
'-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 = [
'name',
'description',
'updated_date',
'updated_by',
];
break;
case 'unique_columns': //Columns that are unique, and disabled for mass editing.
$retval = [
'name',
];
break;
}
return $retval;
}
/**
* @param $data
* @return array
*/
function _getVariableToFunctionMap( $data ) {
$variable_function_map = [
'id' => 'ID',
'company_id' => 'Company',
'name' => 'Name',
'description' => 'Description',
'wage_source_type_id' => 'WageSourceType',
'wage_source_type' => false,
'wage_source_contributing_shift_policy_id' => 'WageSourceContributingShiftPolicy',
'wage_source_contributing_shift_policy' => false,
'time_source_contributing_shift_policy_id' => 'TimeSourceContributingShiftPolicy',
'time_source_contributing_shift_policy' => false,
'average_days' => 'AverageDays',
'pay_type_id' => 'PayType',
'pay_type' => false,
'rate' => 'Rate',
'wage_group_id' => 'WageGroup',
'accrual_rate' => 'AccrualRate',
'accrual_policy_account_id' => 'AccrualPolicyAccount',
'accrual_balance_threshold' => 'AccrualBalanceThreshold',
'accrual_balance_threshold_fallback_accrual_policy_account_id' => 'AccrualBalanceThresholdFallbackAccrualPolicyAccount',
'in_use' => false,
'deleted' => 'Deleted',
];
return $variable_function_map;
}
/**
* @return bool
*/
function getCompanyObject() {
return $this->getGenericObject( 'CompanyListFactory', $this->getCompany(), 'company_obj' );
}
/**
* @return bool
*/
function getAccrualPolicyAccountObject() {
return $this->getGenericObject( 'AccrualPolicyAccountListFactory', $this->getAccrualPolicyAccount(), 'accrual_policy_account_obj' );
}
/**
* @return bool
*/
function getAccrualBalanceThresholdFallbackAccrualPolicyAccountObject() {
return $this->getGenericObject( 'AccrualPolicyAccountListFactory', $this->getAccrualBalanceThresholdFallbackAccrualPolicyAccount(), 'accrual_balance_threshold_fallback_accrual_policy_account_obj' );
}
/**
* @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 );
}
/**
* @param $name
* @return bool
*/
function isUniqueName( $name ) {
$name = trim( $name );
if ( $name == '' ) {
return false;
}
$ph = [
'company_id' => TTUUID::castUUID( $this->getCompany() ),
'name' => TTi18n::strtolower( $name ),
];
$query = 'select id from ' . $this->getTable() . ' where company_id = ? AND lower(name) = ? AND deleted=0';
$id = $this->db->GetOne( $query, $ph );
Debug::Arr( $id, 'Unique: ' . $name, __FILE__, __LINE__, __METHOD__, 10 );
if ( $id === false ) {
return true;
} else {
if ( $id == $this->getId() ) {
return true;
}
}
return false;
}
/**
* @return bool|mixed
*/
function getName() {
return $this->getGenericDataValue( 'name' );
}
/**
* @param $value
* @return bool
*/
function setName( $value ) {
$value = trim( $value );
return $this->setGenericDataValue( 'name', $value );
}
/**
* @return bool|mixed
*/
function getDescription() {
return $this->getGenericDataValue( 'description' );
}
/**
* @param $value
* @return bool
*/
function setDescription( $value ) {
$value = trim( $value );
return $this->setGenericDataValue( 'description', $value );
}
/**
* @return bool|mixed
*/
function getCode() {
return $this->getGenericDataValue( 'code' );
}
/**
* @param $value
* @return bool
*/
function setCode( $value ) {
$value = trim( $value );
return $this->setGenericDataValue( 'code', $value );
}
/**
* @return bool|int
*/
function getPayType() {
return $this->getGenericDataValue( 'pay_type_id' );
}
/**
* @param $value
* @return bool
*/
function setPayType( $value ) {
$value = (int)trim( $value );
return $this->setGenericDataValue( 'pay_type_id', $value );
}
/**
* @return bool|int
*/
function getWageSourceType() {
return $this->getGenericDataValue( 'wage_source_type_id' );
}
/**
* @param $value
* @return bool
*/
function setWageSourceType( $value ) {
$value = (int)trim( $value );
return $this->setGenericDataValue( 'wage_source_type_id', $value );
}
/**
* @return bool|mixed
*/
function getWageSourceContributingShiftPolicy() {
return $this->getGenericDataValue( 'wage_source_contributing_shift_policy_id' );
}
/**
* @param string $value UUID
* @return bool
*/
function setWageSourceContributingShiftPolicy( $value ) {
$value = TTUUID::castUUID( $value );
return $this->setGenericDataValue( 'wage_source_contributing_shift_policy_id', $value );
}
/**
* @return bool|mixed
*/
function getTimeSourceContributingShiftPolicy() {
return $this->getGenericDataValue( 'time_source_contributing_shift_policy_id' );
}
/**
* @param string $value UUID
* @return bool
*/
function setTimeSourceContributingShiftPolicy( $value ) {
$value = TTUUID::castUUID( $value );
return $this->setGenericDataValue( 'time_source_contributing_shift_policy_id', $value );
}
/**
* @return bool|mixed
*/
function getAverageDays() {
return $this->getGenericDataValue( 'average_days' );
}
/**
* @param $value
* @return bool
*/
function setAverageDays( $value ) {
$value = (int)trim( $value );
return $this->setGenericDataValue( 'average_days', $value );
}
/**
* @param $original_hourly_rate
* @return bool|int|mixed
*/
function getHourlyRate( $original_hourly_rate, $udtf = null ) {
//Debug::text(' Getting Rate based off Hourly Rate: '. $original_hourly_rate .' Pay Type: '. $this->getPayType(), __FILE__, __LINE__, __METHOD__, 10);
$rate = 0;
switch ( $this->getPayType() ) {
case 10: //Pay Factor
//Since they are already paid for this time with regular or OT, minus 1 from the rate
$rate = bcmul( $original_hourly_rate, $this->getRate() );
break;
case 30: //Flat Hourly Rate (Relative)
//Get the difference between the employees current wage and the original wage.
$rate = bcsub( $this->getRate(), $original_hourly_rate );
break;
//case 20: //Was Premium Only, but its really the same as Flat Hourly Rate (NON relative)
case 32: //Flat Hourly Rate (NON relative)
//This should be original_hourly_rate, which is typically related to the users wage/wage group, so they can pay whatever is defined there.
//If they want to pay a flat hourly rate specified in the pay code use Pay Plus Premium instead.
//$rate = $original_hourly_rate;
//In v7 this was the above, which isn't correct and unexpected for the user.
$rate = $this->getRate();
break;
case 34: //Flat Hourly Rate (w/Default)
//If a wage record ($original_hourly_rate) is specified, use it, otherwise use the default flat hourly rate specified in the pay formula policy.
if ( $original_hourly_rate != 0 ) {
$rate = $original_hourly_rate;
} else {
$rate = $this->getRate();
}
break;
case 40: //Minimum/Prevailing wage (relative)
if ( $this->getRate() > $original_hourly_rate ) {
$rate = bcsub( $this->getRate(), $original_hourly_rate );
} else {
$rate = 0;
}
break;
case 42: //Minimum/Prevailing wage (NON relative)
if ( $this->getRate() > $original_hourly_rate ) {
$rate = $this->getRate();
} else {
//Use the original rate rather than 0, since this is non-relative its likely
//that the employee is just getting paid from pay codes, so if they are getting
//paid more than the pay code states, without this they would get paid nothing.
//This allows pay codes like "Painting (Regular)" to actually have wages associated with them.
$rate = $original_hourly_rate;
}
break;
case 50: //Pay Plus Premium
$rate = bcadd( $original_hourly_rate, $this->getRate() );
break;
case 60: //Daily Flat Rate (w/Default)
case 70: //Daily Average Rate
//If a wage record ($original_hourly_rate) is specified, use it, otherwise use the default flat hourly rate specified in the pay formula policy.
if ( $original_hourly_rate != 0 ) {
$rate = $original_hourly_rate;
} else {
$rate = 0;
}
break;
case 200: //Piece Rate (Good Quantity)
if ( is_object( $udtf ) && $udtf->getTotalTime() != 0 && $udtf->getQuantity() != 0 ) {
$rate = bcdiv( bcmul( $this->getRate(), $udtf->getQuantity() ), TTDate::getHours( $udtf->getTotalTime() ) );
} else {
$rate = 0;
}
break;
default:
Debug::text( ' ERROR: Invalid Pay Type: ' . $this->getPayType(), __FILE__, __LINE__, __METHOD__, 10 );
break;
}
//Don't round rate, as some currencies accept more than 2 decimal places now.
//and all wages support up to 4 decimal places too.
//return Misc::MoneyRound($rate);
//Debug::text(' Final Rate: '. $rate, __FILE__, __LINE__, __METHOD__, 10);
return $rate;
}
/**
* @return bool|mixed
*/
function getRate() {
return $this->getGenericDataValue( 'rate' );
}
/**
* @param $value
* @return bool
*/
function setRate( $value ) {
$value = trim( $value );
return $this->setGenericDataValue( 'rate', $value );
}
/**
* @return bool|mixed
*/
function getWageGroup() {
return $this->getGenericDataValue( 'wage_group_id' );
}
/**
* @param string $value UUID
* @return bool
*/
function setWageGroup( $value ) {
$value = TTUUID::castUUID( $value );
return $this->setGenericDataValue( 'wage_group_id', $value );
}
/**
* @return bool|mixed
*/
function getAccrualRate() {
return $this->getGenericDataValue( 'accrual_rate' );
}
/**
* @param $value
* @return bool
*/
function setAccrualRate( $value ) {
$value = trim( $value );
return $this->setGenericDataValue( 'accrual_rate', $value );
}
/**
* @return bool|mixed
*/
function getAccrualPolicyAccount() {
return $this->getGenericDataValue( 'accrual_policy_account_id' );
}
/**
* @param string $value UUID
* @return bool
*/
function setAccrualPolicyAccount( $value ) {
$value = TTUUID::castUUID( $value );
return $this->setGenericDataValue( 'accrual_policy_account_id', $value );
}
/**
* @return bool|mixed
*/
function getAccrualBalanceThreshold() {
return $this->getGenericDataValue( 'accrual_balance_threshold' );
}
/**
* @param $value
* @return bool
*/
function setAccrualBalanceThreshold( $value ) {
$value = trim( $value );
return $this->setGenericDataValue( 'accrual_balance_threshold', (int)$this->Validator->stripNon32bitInteger( $value ) );
}
/**
* @return bool|mixed
*/
function getAccrualBalanceThresholdFallbackAccrualPolicyAccount() {
return $this->getGenericDataValue( 'accrual_balance_threshold_fallback_accrual_policy_account_id' );
}
/**
* @param string $value UUID
* @return bool
*/
function setAccrualBalanceThresholdFallbackAccrualPolicyAccount( $value ) {
$value = TTUUID::castUUID( $value );
return $this->setGenericDataValue( 'accrual_balance_threshold_fallback_accrual_policy_account_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' )
);
// Name
if ( $this->Validator->getValidateOnly() == false ) { //Don't check the below when mass editing.
if ( $this->getName() == '' ) {
$this->Validator->isTRUE( 'name',
false,
TTi18n::gettext( 'Please specify a name' ) );
}
}
if ( $this->getName() != '' && $this->Validator->isError( 'name' ) == false ) {
$this->Validator->isLength( 'name',
$this->getName(),
TTi18n::gettext( 'Name is too short or too long' ),
2, 100 ); //Needs to be long enough for upgrade procedure when converting from other policies.
}
if ( $this->getName() != '' && $this->Validator->isError( 'name' ) == false ) {
$this->Validator->isTrue( 'name',
$this->isUniqueName( $this->getName() ),
TTi18n::gettext( 'Name is already in use' )
);
}
// Description
if ( $this->getDescription() != '' ) {
$this->Validator->isLength( 'description',
$this->getDescription(),
TTi18n::gettext( 'Description is invalid' ),
1, 250
);
}
// Code
if ( $this->getCode() !== false ) {
$this->Validator->isLength( 'code',
$this->getCode(),
TTi18n::gettext( 'Code is too short or too long' ),
2, 50
);
}
// Pay Type
if ( $this->getPayType() !== false ) {
$this->Validator->inArrayKey( 'pay_type_id',
$this->getPayType(),
TTi18n::gettext( 'Incorrect Pay Type' ),
$this->getOptions( 'pay_type' )
);
}
// Wage Source Type
if ( $this->getWageSourceType() !== false ) {
$this->Validator->inArrayKey( 'wage_source_type_id',
$this->getWageSourceType(),
TTi18n::gettext( 'Incorrect Wage Source Type' ),
$this->getOptions( 'wage_source_type' )
);
}
// Wage Source Contributing Shift Policy
if ( $this->getWageSourceContributingShiftPolicy() !== false && $this->getWageSourceContributingShiftPolicy() != TTUUID::getZeroID() ) {
$csplf = TTnew( 'ContributingShiftPolicyListFactory' ); /** @var ContributingShiftPolicyListFactory $csplf */
$this->Validator->isResultSetWithRows( 'wage_source_contributing_shift_policy_id',
$csplf->getByID( $this->getWageSourceContributingShiftPolicy() ),
TTi18n::gettext( 'Wage Source Contributing Shift Policy is invalid' )
);
}
// Time Source Contributing Shift Policy
if ( $this->getTimeSourceContributingShiftPolicy() !== false && $this->getTimeSourceContributingShiftPolicy() != TTUUID::getZeroID() ) {
$csplf = TTnew( 'ContributingShiftPolicyListFactory' ); /** @var ContributingShiftPolicyListFactory $csplf */
$this->Validator->isResultSetWithRows( 'time_source_contributing_shift_policy_id',
$csplf->getByID( $this->getTimeSourceContributingShiftPolicy() ),
TTi18n::gettext( 'Time Source Contributing Shift Policy is invalid' )
);
}
// Rate
if ( $this->getRate() !== false ) {
$this->Validator->isFloat( 'rate',
$this->getRate(),
TTi18n::gettext( 'Incorrect Rate' )
);
$this->Validator->isGreaterThan( 'rate',
$this->getRate(),
TTi18n::gettext( 'Rate is too low' ),
-99999
);
if ( $this->Validator->isError( 'rate' ) == false ) {
$this->Validator->isLessThan( 'rate',
$this->getRate(),
TTi18n::gettext( 'Rate is too high' ),
99999
);
}
}
// Wage Group
if ( $this->getWageGroup() !== false && $this->getWageGroup() != TTUUID::getZeroID() ) {
$wglf = TTnew( 'WageGroupListFactory' ); /** @var WageGroupListFactory $wglf */
$this->Validator->isResultSetWithRows( 'wage_group_id',
$wglf->getByID( $this->getWageGroup() ),
TTi18n::gettext( 'Wage Group is invalid' )
);
}
// Accrual Rate
if ( $this->getAccrualRate() !== false ) {
$this->Validator->isFloat( 'accrual_rate',
$this->getAccrualRate(),
TTi18n::gettext( 'Incorrect Accrual Rate' )
);
$this->Validator->isGreaterThan( 'accrual_rate',
$this->getAccrualRate(),
TTi18n::gettext( 'Accrual Rate is too low' ),
-99999
);
if ( $this->Validator->isError( 'accrual_rate' ) == false ) {
$this->Validator->isLessThan( 'accrual_rate',
$this->getAccrualRate(),
TTi18n::gettext( 'Accrual Rate is too high' ),
99999
);
}
}
// Accrual Account
if ( $this->getAccrualPolicyAccount() !== false && $this->getAccrualPolicyAccount() != TTUUID::getZeroID() ) {
$apalf = TTnew( 'AccrualPolicyAccountListFactory' ); /** @var AccrualPolicyAccountListFactory $apalf */
$this->Validator->isResultSetWithRows( 'accrual_policy_account_id',
$apalf->getByID( $this->getAccrualPolicyAccount() ),
TTi18n::gettext( 'Accrual Account is invalid' )
);
}
if ( $this->getAccrualBalanceThreshold() !== false ) {
$this->Validator->isFloat( 'accrual_balance_threshold',
$this->getAccrualBalanceThreshold(),
TTi18n::gettext( 'Incorrect Accrual Balance Threshold' )
);
}
// Accrual Balance Threshold Fallback Accrual Account
if ( $this->getAccrualBalanceThresholdFallbackAccrualPolicyAccount() !== false && $this->getAccrualBalanceThresholdFallbackAccrualPolicyAccount() != TTUUID::getZeroID() ) {
$apalf = TTnew( 'AccrualPolicyAccountListFactory' ); /** @var AccrualPolicyAccountListFactory $apalf */
$this->Validator->isResultSetWithRows( 'accrual_balance_threshold_fallback_accrual_policy_account_id',
$apalf->getByID( $this->getAccrualBalanceThresholdFallbackAccrualPolicyAccount() ),
TTi18n::gettext( 'Accrual Balance Threshold Fallback Account is invalid' )
);
if ( $this->getAccrualPolicyAccount() == $this->getAccrualBalanceThresholdFallbackAccrualPolicyAccount() ) {
$this->Validator->isTRUE( 'accrual_balance_threshold_fallback_accrual_policy_account_id',
false,
TTi18n::gettext( 'Accrual Balance Threshold Fallback Account must not match Accrual Account' ) );
}
}
//
// ABOVE: Validation code moved from set*() functions.
//
if ( $this->getDeleted() == true ) {
$pclf = TTNew( 'PayCodeListFactory' ); /** @var PayCodeListFactory $pclf */
$pclf->getByCompanyIdAndPayFormulaPolicyId( $this->getCompany(), $this->getId() );
if ( $pclf->getRecordCount() > 0 ) {
$this->Validator->isTRUE( 'in_use',
false,
TTi18n::gettext( 'This pay formula policy is currently in use' ) . ' ' . TTi18n::gettext( 'by pay codes' ) );
}
$rtplf = TTNew( 'RegularTimePolicyListFactory' ); /** @var RegularTimePolicyListFactory $rtplf */
$rtplf->getByCompanyIdAndPayFormulaPolicyId( $this->getCompany(), $this->getId() );
if ( $rtplf->getRecordCount() > 0 ) {
$this->Validator->isTRUE( 'in_use',
false,
TTi18n::gettext( 'This pay formula policy is currently in use' ) . ' ' . TTi18n::gettext( 'by regular time policies' ) );
}
$otplf = TTNew( 'OverTimePolicyListFactory' ); /** @var OverTimePolicyListFactory $otplf */
$otplf->getByCompanyIdAndPayFormulaPolicyId( $this->getCompany(), $this->getId() );
if ( $otplf->getRecordCount() > 0 ) {
$this->Validator->isTRUE( 'in_use',
false,
TTi18n::gettext( 'This pay formula policy is currently in use' ) . ' ' . TTi18n::gettext( 'by overtime policies' ) );
}
$pplf = TTNew( 'PremiumPolicyListFactory' ); /** @var PremiumPolicyListFactory $pplf */
$pplf->getByCompanyIdAndPayFormulaPolicyId( $this->getCompany(), $this->getId() );
if ( $pplf->getRecordCount() > 0 ) {
$this->Validator->isTRUE( 'in_use',
false,
TTi18n::gettext( 'This pay formula policy is currently in use' ) . ' ' . TTi18n::gettext( 'by premium policies' ) );
}
$aplf = TTNew( 'AbsencePolicyListFactory' ); /** @var AbsencePolicyListFactory $aplf */
$aplf->getByCompanyIdAndPayFormulaPolicyId( $this->getCompany(), $this->getId() );
if ( $aplf->getRecordCount() > 0 ) {
$this->Validator->isTRUE( 'in_use',
false,
TTi18n::gettext( 'This pay formula policy is currently in use' ) . ' ' . TTi18n::gettext( 'by absence policies' ) );
}
$mplf = TTNew( 'MealPolicyListFactory' ); /** @var MealPolicyListFactory $mplf */
$mplf->getByCompanyIdAndPayFormulaPolicyId( $this->getCompany(), $this->getId() );
if ( $mplf->getRecordCount() > 0 ) {
$this->Validator->isTRUE( 'in_use',
false,
TTi18n::gettext( 'This pay formula policy is currently in use' ) . ' ' . TTi18n::gettext( 'by meal policies' ) );
}
$bplf = TTNew( 'BreakPolicyListFactory' ); /** @var BreakPolicyListFactory $bplf */
$bplf->getByCompanyIdAndPayFormulaPolicyId( $this->getCompany(), $this->getId() );
if ( $bplf->getRecordCount() > 0 ) {
$this->Validator->isTRUE( 'in_use',
false,
TTi18n::gettext( 'This pay formula policy is currently in use' ) . ' ' . TTi18n::gettext( 'by break policies' ) );
}
}
return true;
}
/**
* @return bool
*/
function preValidate() {
if ( $this->getWageGroup() === false ) {
$this->setWageGroup( TTUUID::getZeroID() );
}
if ( $this->getPayType() == 60 || $this->getPayType() == 70 ) { //60=Daily Flat Wage, 70=Daily Average -- Always set WageSourceType=30 (Average Contributing)
$this->setWageSourceType( 30 );
}
return true;
}
/**
* @return bool
*/
function postSave() {
$this->removeCache( $this->getId() );
return true;
}
/**
* @param string $user_id UUID
* @param string $accrual_policy_account_id UUID
* @return bool|int
*/
function getCurrentAccrualBalance( $user_id, $accrual_policy_account_id = null ) {
if ( $user_id == '' ) {
return false;
}
if ( $accrual_policy_account_id == '' ) {
$accrual_policy_account_id = $this->getID();
}
//Check min/max times of accrual policy.
$ablf = TTnew( 'AccrualBalanceListFactory' ); /** @var AccrualBalanceListFactory $ablf */
$ablf->getByUserIdAndAccrualPolicyAccount( $user_id, $accrual_policy_account_id );
if ( $ablf->getRecordCount() > 0 ) {
$accrual_balance = $ablf->getCurrent()->getBalance();
} else {
$accrual_balance = 0;
}
Debug::Text( ' Current Accrual Balance: ' . $accrual_balance, __FILE__, __LINE__, __METHOD__, 10 );
return $accrual_balance;
}
function getAmountAfterBalanceThreshold( $user_id, $amount, $previous_amount = 0 ) {
$retval = [ 'adjusted_amount' => $amount, 'amount_remaining' => 0 ];
//Check Accrual Balance Threshold
$current_accrual_balance = $this->getCurrentAccrualBalance( $user_id, $this->getAccrualPolicyAccount() );
if ( $this->getAccrualRate() > 0 ) { //Threshold is a maximum
$current_accrual_balance -= $previous_amount; //When editing an existing record, we need to back out the previous amount before calculating the new balance threshold.
$adjusted_accrual_balance = ( $current_accrual_balance + $amount );
if ( $adjusted_accrual_balance > $this->getAccrualBalanceThreshold() ) {
$retval = [ 'adjusted_amount' => ( $this->getAccrualBalanceThreshold() - $current_accrual_balance ), 'amount_remaining' => ( $adjusted_accrual_balance - $this->getAccrualBalanceThreshold() ) ];
}
} else if ( $this->getAccrualRate() < 0 ) { //Threshold is a minimum
$current_accrual_balance += $previous_amount; //When editing an existing record, we need to back out the previous amount before calculating the new balance threshold.
$adjusted_accrual_balance = ( $current_accrual_balance + $amount );
if ( $adjusted_accrual_balance < $this->getAccrualBalanceThreshold() ) {
$retval = [ 'adjusted_amount' => ( $amount - $adjusted_accrual_balance ), 'amount_remaining' => ( $current_accrual_balance + $amount ) ];
}
}
if ( $retval['amount_remaining'] != 0 && is_object( $this->getAccrualPolicyAccountObject() ) ) {
$retval['note'] = TTi18n::getText( 'Amount: %1 Current Balance: %2 Falling Back: %3 From: %4 to %5', [ TTDate::getTimeUnit( $amount ),
TTDate::getTimeUnit( $current_accrual_balance ),
TTDate::getTimeUnit( $retval['amount_remaining'] ),
$this->getAccrualPolicyAccountObject()->getName(),
( is_object( $this->getAccrualBalanceThresholdFallbackAccrualPolicyAccountObject() ) ? $this->getAccrualBalanceThresholdFallbackAccrualPolicyAccountObject()->getName() : TTi18n::getText('N/A') ) ]
);
}
Debug::Arr( $retval, ' Current Accrual Balance: ' . $current_accrual_balance .' Previous Amount: '. $previous_amount, __FILE__, __LINE__, __METHOD__, 10 );
return $retval;
}
/**
* @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 ) {
$data = [];
$variable_function_map = $this->getVariableToFunctionMap();
if ( is_array( $variable_function_map ) ) {
foreach ( $variable_function_map as $variable => $function_stub ) {
if ( $include_columns == null || ( isset( $include_columns[$variable] ) && $include_columns[$variable] == true ) ) {
$function = 'get' . $function_stub;
switch ( $variable ) {
case 'in_use':
$data[$variable] = $this->getColumn( $variable );
break;
case 'pay_type':
$function = 'get' . str_replace( '_', '', $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 Formula Policy' ) .': '. $this->getName(), null, $this->getTable(), $this );
}
}
?>