952 lines
29 KiB
PHP
952 lines
29 KiB
PHP
|
<?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 PayrollDeduction\US
|
||
|
*/
|
||
|
class PayrollDeduction_US extends PayrollDeduction_US_Data {
|
||
|
//
|
||
|
// Federal
|
||
|
//
|
||
|
function setFederalFilingStatus( $value ) {
|
||
|
//Check for invalid value, default to single if found.
|
||
|
if ( in_array( $value, [ 10, 20, 40 ] ) == false ) {
|
||
|
$value = 10; //Single
|
||
|
}
|
||
|
|
||
|
$this->data['federal_filing_status'] = (int)$value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalFilingStatus() {
|
||
|
if ( isset( $this->data['federal_filing_status'] ) && $this->data['federal_filing_status'] != '' ) {
|
||
|
return $this->data['federal_filing_status'];
|
||
|
}
|
||
|
|
||
|
return 10; //Single
|
||
|
}
|
||
|
|
||
|
function setFederalAllowance( $value ) {
|
||
|
$this->data['federal_allowance'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalAllowance() {
|
||
|
if ( isset( $this->data['federal_allowance'] ) ) {
|
||
|
return $this->data['federal_allowance'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setFederalFormW4Version( $value ) {
|
||
|
//Check for invalid value, default to single if found.
|
||
|
if ( in_array( $value, [ 2019, 2020 ] ) == false ) {
|
||
|
$value = 2019; //Default to 2019 version.
|
||
|
}
|
||
|
|
||
|
$this->data['federal_form_w4_version'] = (string)$value; //2019 or 2010
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalFormW4Version() {
|
||
|
if ( isset( $this->data['federal_form_w4_version'] ) ) {
|
||
|
return $this->data['federal_form_w4_version'];
|
||
|
}
|
||
|
|
||
|
return 2019; //Default to 2019 version if not set.
|
||
|
}
|
||
|
|
||
|
|
||
|
function setFederalMultipleJobs( $value ) {
|
||
|
$this->data['federal_multiple_jobs'] = (bool)$value; //Boolean Yes/No
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalMultipleJobs() {
|
||
|
if ( isset( $this->data['federal_multiple_jobs'] ) ) {
|
||
|
return $this->data['federal_multiple_jobs'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setFederalClaimDependents( $value ) {
|
||
|
$this->data['federal_claim_dependents'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalClaimDependents() {
|
||
|
if ( isset( $this->data['federal_claim_dependents'] ) ) {
|
||
|
return $this->data['federal_claim_dependents'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setFederalOtherIncome( $value ) {
|
||
|
$this->data['federal_other_income'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalOtherIncome() {
|
||
|
if ( isset( $this->data['federal_other_income'] ) ) {
|
||
|
return $this->data['federal_other_income'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setFederalDeductions( $value ) {
|
||
|
$this->data['federal_deductions'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalDeductions() {
|
||
|
if ( isset( $this->data['federal_deductions'] ) ) {
|
||
|
return $this->data['federal_deductions'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setFederalAdditionalDeduction( $value ) {
|
||
|
$this->data['federal_additional_deduction'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalAdditionalDeduction() {
|
||
|
if ( isset( $this->data['federal_additional_deduction'] ) ) {
|
||
|
return $this->data['federal_additional_deduction'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setYearToDateSocialSecurityContribution( $value ) {
|
||
|
if ( $value > 0 ) {
|
||
|
$this->data['social_security_ytd_contribution'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function getYearToDateSocialSecurityContribution() {
|
||
|
if ( isset( $this->data['social_security_ytd_contribution'] ) ) {
|
||
|
return $this->data['social_security_ytd_contribution'];
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function setFederalUIRate( $value ) {
|
||
|
if ( $value > 0 ) {
|
||
|
$retarr = $this->getDataFromRateArray( $this->getDate(), $this->federal_ui_options );
|
||
|
if ( $retarr != false ) {
|
||
|
if ( $value != 0 && $value < $this->getFederalUIMinimumRate() ) { //Allow a 0 rate, but nothing between 0 and the minimum.
|
||
|
Debug::Text( ' Federal UI Rate is below minimum, using minimum instead: '. $value, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$value = $this->getFederalUIMinimumRate();
|
||
|
}
|
||
|
|
||
|
if ( $value > $this->getFederalUIMaximumRate() ) {
|
||
|
Debug::Text( ' Federal UI Rate is above maximum, using minimum instead: '. $value, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$value = $this->getFederalUIMaximumRate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->data['federal_ui_rate'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function getFederalUIRate() {
|
||
|
if ( isset( $this->data['federal_ui_rate'] ) ) {
|
||
|
return $this->data['federal_ui_rate'];
|
||
|
} else {
|
||
|
return $this->getFederalUIMinimumRate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setYearToDateFederalUIContribution( $value ) {
|
||
|
if ( $value > 0 ) {
|
||
|
$this->data['federal_ui_ytd_contribution'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function getYearToDateFederalUIContribution() {
|
||
|
if ( isset( $this->data['federal_ui_ytd_contribution'] ) ) {
|
||
|
return $this->data['federal_ui_ytd_contribution'];
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function setFederalTaxExempt( $value ) {
|
||
|
$this->data['federal_tax_exempt'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getFederalTaxExempt() {
|
||
|
if ( isset( $this->data['federal_tax_exempt'] ) ) {
|
||
|
return $this->data['federal_tax_exempt'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// State
|
||
|
//
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Used to determine if this state tax calculation requires federal tax as an input value to be properly calculated.
|
||
|
* Mostly used outside this class to determine if we need to go through the extra work to add federal tax input values.
|
||
|
* This gets overloaded in each state class file where its TRUE.
|
||
|
* @return false
|
||
|
*/
|
||
|
function isFederalTaxRequired() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setStateFilingStatus( $value ) {
|
||
|
$value = (int)$value;
|
||
|
if ( in_array( $value, [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ] ) == false ) {
|
||
|
$value = 10; //Single
|
||
|
}
|
||
|
|
||
|
$this->data['state_filing_status'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getStateFilingStatus() {
|
||
|
if ( isset( $this->data['state_filing_status'] ) && $this->data['state_filing_status'] != '' ) {
|
||
|
return $this->data['state_filing_status'];
|
||
|
}
|
||
|
|
||
|
return 10; //Single
|
||
|
}
|
||
|
|
||
|
function setStateAllowance( $value ) {
|
||
|
$this->data['state_allowance'] = (int)$value; //Don't allow fractions, like 1.5 allowances, as this can cause problems with rate lookups failing when its expecting 1 or 2, and it gets 1.5
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getStateAllowance() {
|
||
|
if ( isset( $this->data['state_allowance'] ) ) {
|
||
|
return $this->data['state_allowance'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setStateAdditionalDeduction( $value ) {
|
||
|
$this->data['state_additional_deduction'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getStateAdditionalDeduction() {
|
||
|
if ( isset( $this->data['state_additional_deduction'] ) ) {
|
||
|
return $this->data['state_additional_deduction'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Default to 0 unless otherwise defined in a State specific class.
|
||
|
function getStateTaxPayable() {
|
||
|
if ( $this->getProvincialTaxExempt() == true ) {
|
||
|
Debug::text( 'State Tax Exempt!', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return 0;
|
||
|
} else {
|
||
|
return $this->_getStateTaxPayable();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _getStateTaxPayable() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function getStatePayPeriodDeductionRoundedValue( $amount ) {
|
||
|
return $amount;
|
||
|
}
|
||
|
|
||
|
function getStatePayPeriodDeductions() {
|
||
|
if ( $this->getFormulaType() == 20 ) {
|
||
|
Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Payable: ' . $this->getStateTaxPayable() . ' YTD Paid: ' . $this->getYearToDateDeduction() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$retval = $this->calcNonPeriodicDeduction( $this->getStateTaxPayable(), $this->getYearToDateDeduction() );
|
||
|
|
||
|
//Ensure that the tax amount doesn't exceed the highest possible tax rate plus 25% for "catch-up" purposes.
|
||
|
$highest_taxable_amount = bcmul( $this->getGrossPayPeriodIncome(), bcmul( $this->getStateHighestRate(), 1.25 ) );
|
||
|
if ( $highest_taxable_amount > 0 && $retval > $highest_taxable_amount ) {
|
||
|
$retval = $highest_taxable_amount;
|
||
|
Debug::text( 'State tax amount exceeds highest tax bracket rate, capping amount at: ' . $highest_taxable_amount, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
} else {
|
||
|
$retval = bcdiv( $this->getStateTaxPayable(), $this->getAnnualPayPeriods() );
|
||
|
}
|
||
|
|
||
|
Debug::text( 'State Pay Period Deductions: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return $this->getStatePayPeriodDeductionRoundedValue( $retval );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// District
|
||
|
//
|
||
|
|
||
|
//Generic district functions that handle straight percentages for any district unless otherwise overloaded.
|
||
|
//for custom formulas.
|
||
|
function getDistrictPayPeriodDeductions() {
|
||
|
if ( $this->getFormulaType() == 20 ) {
|
||
|
Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Payable: ' . $this->getDistrictTaxPayable() . ' YTD Paid: ' . $this->getYearToDateDeduction() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$retval = $this->calcNonPeriodicDeduction( $this->getDistrictTaxPayable(), $this->getYearToDateDeduction() );
|
||
|
|
||
|
//Ensure that the tax amount doesn't exceed the highest possible tax rate plus 25% for "catch-up" purposes.
|
||
|
$highest_taxable_amount = bcmul( $this->getGrossPayPeriodIncome(), bcmul( $this->getDistrictHighestRate(), 1.25 ) );
|
||
|
if ( $highest_taxable_amount > 0 && $retval > $highest_taxable_amount ) {
|
||
|
$retval = $highest_taxable_amount;
|
||
|
Debug::text( 'District tax amount exceeds highest tax bracket rate, capping amount at: ' . $highest_taxable_amount, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
} else {
|
||
|
$retval = bcdiv( $this->getDistrictTaxPayable(), $this->getAnnualPayPeriods() );
|
||
|
}
|
||
|
|
||
|
Debug::text( 'District Pay Period Deductions: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
function getDistrictAnnualTaxableIncome() {
|
||
|
$annual_income = $this->getAnnualTaxableIncome();
|
||
|
|
||
|
return $annual_income;
|
||
|
}
|
||
|
|
||
|
function getDistrictTaxPayable() {
|
||
|
$annual_income = $this->getDistrictAnnualTaxableIncome();
|
||
|
|
||
|
if ( $annual_income > 0 ) {
|
||
|
$rate = bcdiv( $this->getUserValue2(), 100 );
|
||
|
$retval = bcmul( $annual_income, $rate );
|
||
|
}
|
||
|
|
||
|
if ( !isset( $retval ) || $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
Debug::text( 'zzDistrict Annual Tax Payable: ' . $retval . ' User Value 2: ' . $this->getUserValue2() . ' Annual Income: ' . $annual_income, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
function setDistrictFilingStatus( $value ) {
|
||
|
$this->data['district_filing_status'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getDistrictFilingStatus() {
|
||
|
if ( isset( $this->data['district_filing_status'] ) ) {
|
||
|
return $this->data['district_filing_status'];
|
||
|
}
|
||
|
|
||
|
return 10; //Single
|
||
|
}
|
||
|
|
||
|
function setDistrictAllowance( $value ) {
|
||
|
$this->data['district_allowance'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getDistrictAllowance() {
|
||
|
if ( isset( $this->data['district_allowance'] ) ) {
|
||
|
return $this->data['district_allowance'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setYearToDateStateUIContribution( $value ) {
|
||
|
if ( $value > 0 ) {
|
||
|
$this->data['state_ui_ytd_contribution'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function getYearToDateStateUIContribution() {
|
||
|
if ( isset( $this->data['state_ui_ytd_contribution'] ) ) {
|
||
|
return $this->data['state_ui_ytd_contribution'];
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function setStateUIRate( $value ) {
|
||
|
if ( $value > 0 ) {
|
||
|
$this->data['state_ui_rate'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function getStateUIRate() {
|
||
|
if ( isset( $this->data['state_ui_rate'] ) ) {
|
||
|
return $this->data['state_ui_rate'];
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function setStateUIWageBase( $value ) {
|
||
|
if ( $value > 0 ) {
|
||
|
$this->data['state_ui_wage_base'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function getStateUIWageBase() {
|
||
|
if ( isset( $this->data['state_ui_wage_base'] ) ) {
|
||
|
return $this->data['state_ui_wage_base'];
|
||
|
} else {
|
||
|
return $this->getStateUIDefaultWageBase();
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function getStateUIDefaultWageBase() {
|
||
|
$retarr = $this->getDataFromRateArray( $this->getDate(), $this->state_ui_options );
|
||
|
if ( $retarr == false ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( isset( $retarr['wage_base'] ) && !empty( $retarr['wage_base'] ) ) {
|
||
|
return $retarr['wage_base'];
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function setProvincialTaxExempt( $value ) {
|
||
|
$this->data['provincial_tax_exempt'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getProvincialTaxExempt() {
|
||
|
if ( isset( $this->data['provincial_tax_exempt'] ) ) {
|
||
|
return $this->data['provincial_tax_exempt'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setSocialSecurityExempt( $value ) {
|
||
|
$this->data['social_security_exempt'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getSocialSecurityExempt() {
|
||
|
if ( isset( $this->data['social_security_exempt'] ) ) {
|
||
|
return $this->data['social_security_exempt'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setMedicareExempt( $value ) {
|
||
|
$this->data['medicare_exempt'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getMedicareExempt() {
|
||
|
if ( isset( $this->data['medicare_exempt'] ) ) {
|
||
|
return $this->data['medicare_exempt'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function setUIExempt( $value ) {
|
||
|
$this->data['ui_exempt'] = $value;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function getUIExempt() {
|
||
|
if ( isset( $this->data['ui_exempt'] ) ) {
|
||
|
return $this->data['ui_exempt'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Calculation Functions
|
||
|
//
|
||
|
function getAnnualTaxableIncome() {
|
||
|
if ( $this->getFormulaType() == 20 ) {
|
||
|
Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Gross: ' . $this->getYearToDateGrossIncome() . ' This Gross: ' . $this->getGrossPayPeriodIncome() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$retval = $this->calcNonPeriodicIncome( $this->getYearToDateGrossIncome(), $this->getGrossPayPeriodIncome() );
|
||
|
} else {
|
||
|
$retval = bcmul( $this->getGrossPayPeriodIncome(), $this->getAnnualPayPeriods() );
|
||
|
}
|
||
|
Debug::text( 'Annual Taxable Income: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
if ( $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Federal Tax
|
||
|
//
|
||
|
function getFederalPayPeriodDeductions() {
|
||
|
if ( $this->getFormulaType() == 20 ) {
|
||
|
Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Payable: ' . $this->getFederalTaxPayable() . ' YTD Paid: ' . $this->getYearToDateDeduction() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$retval = $this->calcNonPeriodicDeduction( $this->getFederalTaxPayable(), $this->getYearToDateDeduction() );
|
||
|
|
||
|
//Ensure that the tax amount doesn't exceed the highest possible tax rate plus 25% for "catch-up" purposes.
|
||
|
$highest_taxable_amount = bcmul( $this->getGrossPayPeriodIncome(), bcmul( $this->getFederalHighestRate(), 1.25 ) );
|
||
|
if ( $highest_taxable_amount > 0 && $retval > $highest_taxable_amount ) {
|
||
|
$retval = $highest_taxable_amount;
|
||
|
Debug::text( 'Federal tax amount exceeds highest tax bracket rate, capping amount at: ' . $highest_taxable_amount, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
}
|
||
|
} else {
|
||
|
$retval = bcdiv( $this->getFederalTaxPayable(), $this->getAnnualPayPeriods() );
|
||
|
}
|
||
|
|
||
|
Debug::text( 'Federal Pay Period Deductions: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
function getFederalTaxPayable() {
|
||
|
if ( $this->getFederalTaxExempt() == true ) {
|
||
|
Debug::text( 'Federal Tax Exempt!', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$annual_taxable_income = $this->getAnnualTaxableIncome();
|
||
|
if ( $this->getDate() >= 20200101 && $this->getFederalFormW4Version() == 2020 ) { //See Form W4 Version check below as well.
|
||
|
$annual_taxable_income = bcadd( $annual_taxable_income, $this->getFederalOtherIncome() );
|
||
|
|
||
|
$filing_status_adjustment = 0;
|
||
|
if ( $this->getFederalMultipleJobs() == false ) {
|
||
|
if ( $this->getFederalFilingStatus() == 20 ) { //Married Filing Jointly
|
||
|
$filing_status_adjustment = bcmul( $this->getFederalAllowanceAmount( $this->getDate() ), 3 ); //$12,600 (4,200 * 3 )
|
||
|
} else {
|
||
|
$filing_status_adjustment = bcmul( $this->getFederalAllowanceAmount( $this->getDate() ), 2 ); //$8,400 ( 4,200 * 2 )
|
||
|
}
|
||
|
}
|
||
|
Debug::text( 'Filing Status Adjustment: ' . $filing_status_adjustment . ' W4 Deductions: ' . $this->getFederalDeductions(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$annual_taxable_income = bcsub( bcsub( $annual_taxable_income, $filing_status_adjustment ), $this->getFederalDeductions() );
|
||
|
Debug::text( '2020 W4 - Annual Taxable Income: ' . $annual_taxable_income . ' Other Income: ' . $this->getFederalOtherIncome() . ' ', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
} else {
|
||
|
$annual_allowance = bcmul( $this->getFederalAllowanceAmount( $this->getDate() ), $this->getFederalAllowance() );
|
||
|
Debug::text( 'Legacy W4 - Annual Taxable Income: ' . $annual_taxable_income . 'Allowance: ' . $annual_allowance, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
if ( $annual_taxable_income > $annual_allowance ) {
|
||
|
$annual_taxable_income = bcsub( $annual_taxable_income, $annual_allowance );
|
||
|
} else {
|
||
|
Debug::text( 'Income is less then allowance: ', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$annual_taxable_income = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( $annual_taxable_income > 0 ) {
|
||
|
Debug::text( 'Annual Taxable Income: ' . $annual_taxable_income, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$rate = $this->getData()->getFederalRate( $annual_taxable_income );
|
||
|
$federal_constant = $this->getData()->getFederalConstant( $annual_taxable_income );
|
||
|
$federal_rate_income = $this->getData()->getFederalRatePreviousIncome( $annual_taxable_income );
|
||
|
|
||
|
$retval = bcadd( bcmul( bcsub( $annual_taxable_income, $federal_rate_income ), $rate ), $federal_constant );
|
||
|
|
||
|
if ( $this->getDate() >= 20200101 && $this->getFederalFormW4Version() == 2020 ) { //See Form W4 Version check above as well.
|
||
|
Debug::text( ' Claimed Dependent Amount: ' . $this->getFederalClaimDependents(), __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$retval = bcsub( $retval, $this->getFederalClaimDependents() );
|
||
|
}
|
||
|
|
||
|
if ( $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
} else {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
//Additional deduction must be added at the very end, even if annual income is less than 0.
|
||
|
if ( $this->getDate() >= 20200101 && $this->getFederalFormW4Version() == 2020 ) { //See Form W4 Version check above as well.
|
||
|
if ( $annual_taxable_income <= 0 ) { //If annual taxable income is 0, we don't bother getting tax rates above, so we need to get them now so getFederalHighestRate() works below.
|
||
|
$this->getData();
|
||
|
}
|
||
|
|
||
|
$additional_deduction = 0;
|
||
|
|
||
|
//Don't deduct the additional withholding during out-of-cycle payroll runs, or when using the non-periodic calculations as the non-periodic formula contradicts the purpose of additional withholding.
|
||
|
// Especially if they change the additional withholding it will try to calculate what was (or was not) owed retroactively to the beginning of the year too.
|
||
|
// Both additional withholding and regular withholding is combined into a single pay stub account so we can't separate if we did wanted to handle them differently somehow anyways.
|
||
|
// The only way to do always non-periodic tax calculation and additional withhold would be with a separate pay stub account.
|
||
|
// Also cap additional withholding at the highest tax rate plus a buffer, to ensure it never exceeds the employees gross earnings.
|
||
|
if ( $this->getFederalAdditionalDeduction() > 0 && $this->getFormulaType() == 10 ) {
|
||
|
$maximum_tax_rate_threshold = bcmul( $this->getFederalHighestRate(), 1.50 );
|
||
|
if ( $maximum_tax_rate_threshold > 0.80 ) { //Hopefully unlikely, but some states have odd tax brackets with high rates for small dollar amount brackets. So make sure no tax rate happens to exceed 80%.
|
||
|
$maximum_tax_rate_threshold = 0.80;
|
||
|
}
|
||
|
|
||
|
$maximum_additional_deduction = bcmul( $this->getGrossPayPeriodIncome(), $maximum_tax_rate_threshold );
|
||
|
if ( $this->getFederalAdditionalDeduction() > $maximum_additional_deduction ) {
|
||
|
$additional_deduction = $maximum_additional_deduction;
|
||
|
Debug::text( ' Additional Deduction exceeds maximum threshold of highest rate plus buffer, capping: ' . $maximum_additional_deduction, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
} else {
|
||
|
$additional_deduction = $this->getFederalAdditionalDeduction();
|
||
|
}
|
||
|
|
||
|
$additional_deduction = bcmul( $additional_deduction, $this->getAnnualPayPeriods() ); //Federal Deduction amount from 2020 W4 is *PER PAY PERIOD*
|
||
|
}
|
||
|
|
||
|
Debug::text( ' Additional Deduction: ' . $additional_deduction, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$retval = bcadd( $retval, $additional_deduction );
|
||
|
}
|
||
|
|
||
|
Debug::text( 'RetVal: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Social Security
|
||
|
//
|
||
|
function getAnnualEmployeeSocialSecurity() {
|
||
|
if ( $this->getSocialSecurityExempt() == true ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$annual_income = $this->getAnnualTaxableIncome();
|
||
|
$rate = bcdiv( $this->getSocialSecurityRate(), 100 );
|
||
|
$maximum_contribution = $this->getSocialSecurityMaximumContribution();
|
||
|
|
||
|
Debug::text( 'Rate: ' . $rate . ' Maximum Contribution: ' . $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$amount = bcmul( $annual_income, $rate );
|
||
|
$max_amount = $maximum_contribution;
|
||
|
|
||
|
if ( $amount > $max_amount ) {
|
||
|
$retval = $max_amount;
|
||
|
} else {
|
||
|
$retval = $amount;
|
||
|
}
|
||
|
|
||
|
if ( $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
function getEmployeeSocialSecurity() {
|
||
|
if ( $this->getSocialSecurityExempt() == true ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$type = 'employee';
|
||
|
|
||
|
$pay_period_income = $this->getGrossPayPeriodIncome();
|
||
|
$rate = bcdiv( $this->getSocialSecurityRate( $type ), 100 );
|
||
|
$maximum_contribution = $this->getSocialSecurityMaximumContribution( $type );
|
||
|
$ytd_contribution = $this->getYearToDateSocialSecurityContribution();
|
||
|
|
||
|
Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution .' Max Contribution: '. $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$amount = bcmul( $pay_period_income, $rate );
|
||
|
$max_amount = bcsub( $maximum_contribution, $ytd_contribution );
|
||
|
|
||
|
if ( $amount > $max_amount ) {
|
||
|
$retval = $max_amount;
|
||
|
} else {
|
||
|
$retval = $amount;
|
||
|
}
|
||
|
|
||
|
if ( $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
function getEmployerSocialSecurity() {
|
||
|
if ( $this->getSocialSecurityExempt() == true ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$type = 'employer';
|
||
|
|
||
|
$pay_period_income = $this->getGrossPayPeriodIncome();
|
||
|
$rate = bcdiv( $this->getSocialSecurityRate( $type ), 100 );
|
||
|
$maximum_contribution = $this->getSocialSecurityMaximumContribution( $type );
|
||
|
$ytd_contribution = $this->getYearToDateSocialSecurityContribution();
|
||
|
|
||
|
Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution .' Max Contribution: '. $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$amount = bcmul( $pay_period_income, $rate );
|
||
|
$max_amount = bcsub( $maximum_contribution, $ytd_contribution );
|
||
|
|
||
|
if ( $amount > $max_amount ) {
|
||
|
$retval = $max_amount;
|
||
|
} else {
|
||
|
$retval = $amount;
|
||
|
}
|
||
|
|
||
|
if ( $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Medicare
|
||
|
//
|
||
|
function getAnnualEmployeeMedicare() {
|
||
|
return bcmul( $this->getEmployeeMedicare(), $this->getAnnualPayPeriods() );
|
||
|
}
|
||
|
|
||
|
function getEmployeeMedicare() {
|
||
|
if ( $this->getMedicareExempt() == true ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$pay_period_income = $this->getGrossPayPeriodIncome();
|
||
|
|
||
|
$rate_data = $this->getMedicareRate();
|
||
|
$rate = bcdiv( $rate_data['employee_rate'], 100 );
|
||
|
Debug::text( 'Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$amount = round( bcmul( $pay_period_income, $rate ), 2 ); //Must round separately from additional medicare, as they are broken out in tax reports.
|
||
|
Debug::text( 'Amount: ' . $amount, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$threshold_income = $this->getMedicareAdditionalEmployerThreshold();
|
||
|
Debug::text( 'Threshold Income: ' . $threshold_income, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
if ( $threshold_income > 0 && bcadd( $this->getYearToDateGrossIncome(), $this->getGrossPayPeriodIncome() ) > $threshold_income ) {
|
||
|
if ( $this->getYearToDateGrossIncome() < $threshold_income ) {
|
||
|
$threshold_income = bcsub( bcadd( $this->getYearToDateGrossIncome(), $this->getGrossPayPeriodIncome() ), $threshold_income );
|
||
|
} else {
|
||
|
$threshold_income = $pay_period_income;
|
||
|
}
|
||
|
Debug::text( 'bThreshold Income: ' . $threshold_income, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$threshold_amount = round( bcmul( $threshold_income, bcdiv( $rate_data['employee_threshold_rate'], 100 ) ), 2 ); //Must round separately from regular medicare, as they are broken out in tax reports.
|
||
|
Debug::text( 'Threshold Amount: ' . $threshold_amount, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
$amount = bcadd( $amount, $threshold_amount );
|
||
|
}
|
||
|
|
||
|
if ( $amount < 0 ) {
|
||
|
$amount = 0;
|
||
|
}
|
||
|
|
||
|
return $amount;
|
||
|
}
|
||
|
|
||
|
function getEmployerMedicare() {
|
||
|
//return $this->getEmployeeMedicare();
|
||
|
if ( $this->getMedicareExempt() == true ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$pay_period_income = $this->getGrossPayPeriodIncome();
|
||
|
|
||
|
$rate_data = $this->getMedicareRate();
|
||
|
$rate = bcdiv( $rate_data['employer_rate'], 100 );
|
||
|
Debug::text( 'Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$amount = bcmul( $pay_period_income, $rate );
|
||
|
|
||
|
if ( $amount < 0 ) {
|
||
|
$amount = 0;
|
||
|
}
|
||
|
|
||
|
return $amount;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Federal UI
|
||
|
//
|
||
|
function getFederalEmployerUI() {
|
||
|
if ( $this->getUIExempt() == true ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$pay_period_income = $this->getGrossPayPeriodIncome();
|
||
|
$rate = bcdiv( $this->getFederalUIRate(), 100 );
|
||
|
$maximum_contribution = $this->getFederalUIMaximumContribution();
|
||
|
$ytd_contribution = $this->getYearToDateFederalUIContribution();
|
||
|
|
||
|
Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution . ' Maximum: ' . $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$amount = bcmul( $pay_period_income, $rate );
|
||
|
$max_amount = bcsub( $maximum_contribution, $ytd_contribution );
|
||
|
|
||
|
if ( $amount > $max_amount ) {
|
||
|
$retval = $max_amount;
|
||
|
} else {
|
||
|
$retval = $amount;
|
||
|
}
|
||
|
|
||
|
if ( $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// State UI
|
||
|
//
|
||
|
function getStateEmployerUI() {
|
||
|
if ( $this->getUIExempt() == true ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
$pay_period_income = $this->getGrossPayPeriodIncome();
|
||
|
$rate = bcdiv( $this->getStateUIRate(), 100 );
|
||
|
$maximum_contribution = bcmul( $this->getStateUIWageBase(), $rate );
|
||
|
$ytd_contribution = $this->getYearToDateStateUIContribution();
|
||
|
|
||
|
Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution . ' Maximum: ' . $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
$amount = bcmul( $pay_period_income, $rate );
|
||
|
$max_amount = bcsub( $maximum_contribution, $ytd_contribution );
|
||
|
|
||
|
if ( $amount > $max_amount ) {
|
||
|
$retval = $max_amount;
|
||
|
} else {
|
||
|
$retval = $amount;
|
||
|
}
|
||
|
|
||
|
if ( $retval < 0 ) {
|
||
|
$retval = 0;
|
||
|
}
|
||
|
|
||
|
return $retval;
|
||
|
}
|
||
|
|
||
|
function getPayPeriodTaxDeductions() {
|
||
|
return bcadd( $this->getFederalPayPeriodDeductions(), $this->getStatePayPeriodDeductions() );
|
||
|
}
|
||
|
|
||
|
function getPayPeriodEmployeeTotalDeductions() {
|
||
|
return bcadd( bcadd( $this->getPayPeriodTaxDeductions(), $this->getEmployeeSocialSecurity() ), $this->getEmployeeMedicare() );
|
||
|
}
|
||
|
|
||
|
function getPayPeriodEmployeeNetPay() {
|
||
|
return bcsub( $this->getGrossPayPeriodIncome(), $this->getPayPeriodEmployeeTotalDeductions() );
|
||
|
}
|
||
|
|
||
|
function RoundNearestDollar( $amount ) {
|
||
|
return round( $amount, 0 );
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Use this to get all useful values.
|
||
|
*/
|
||
|
function getArray() {
|
||
|
|
||
|
$array = [
|
||
|
'gross_pay' => $this->getGrossPayPeriodIncome(),
|
||
|
'federal_tax' => $this->getFederalPayPeriodDeductions(),
|
||
|
'state_tax' => $this->getStatePayPeriodDeductions(),
|
||
|
/*
|
||
|
'employee_social_security' => $this->getEmployeeSocialSecurity(),
|
||
|
'employer_social_security' => $this->getEmployeeSocialSecurity(),
|
||
|
'employee_medicare' => $this->getEmployeeMedicare(),
|
||
|
'employer_medicare' => $this->getEmployerMedicare(),
|
||
|
*/
|
||
|
'employee_social_security' => $this->getEmployeeSocialSecurity(),
|
||
|
'federal_employer_ui' => $this->getFederalEmployerUI(),
|
||
|
// 'state_employer_ui' => $this->getStateEmployerUI(),
|
||
|
|
||
|
];
|
||
|
|
||
|
Debug::Arr( $array, 'Deductions Array:', __FILE__, __LINE__, __METHOD__, 10 );
|
||
|
|
||
|
return $array;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
?>
|