Files
icehrm/core/src/Payroll/Admin/Api/PayrollActionManager.php
2020-05-20 18:47:29 +02:00

577 lines
20 KiB
PHP

<?php
/**
* Created by PhpStorm.
* User: Thilina
* Date: 8/19/17
* Time: 4:18 PM
*/
namespace Payroll\Admin\Api;
use Classes\BaseService;
use Classes\IceResponse;
use Classes\SubActionManager;
use Company\Common\Model\CompanyStructure;
use Employees\Common\Model\Employee;
use Payroll\Common\Model\Deduction;
use Payroll\Common\Model\DeductionGroup;
use Payroll\Common\Model\Payroll;
use Payroll\Common\Model\PayrollCalculations;
use Payroll\Common\Model\PayrollColumn;
use Payroll\Common\Model\PayrollData;
use Salary\Common\Model\EmployeeSalary;
use Salary\Common\Model\PayrollEmployee;
use Salary\Common\Model\SalaryComponent;
use Utils\LogManager;
use Utils\Math\EvalMath;
use Utils\ScriptRunner;
use Classes\Migration\AbstractMigration;
class PayrollActionManager extends SubActionManager
{
const REG_HOURS_PER_WEEK = 40;
protected $calCache = array();
public function addToCalculationCache($key, $val)
{
$this->calCache[$key] = $val;
}
public function getFromCalculationCache($key)
{
if (isset($this->calCache[$key])) {
return $this->calCache[$key];
}
return null;
}
public function calculatePayrollColumn(
$col,
$payroll,
$employeeId,
$payrollEmployeeId,
$noColumnCalculations = false
) {
$val = $this->getFromCalculationCache($col->id."-".$payroll->id."-".$employeeId);
if (!empty($val)) {
return $val;
}
if (!empty($col->calculation_hook)) {
$valueData = BaseService::getInstance()->executeCalculationHook(
array($employeeId, $payroll->date_start, $payroll->date_end),
$col->calculation_hook,
$col->calculation_function
);
if (is_array($valueData) && $valueData[0] == 'string') {
$val = $valueData[1];
} else {
$val = number_format(round($valueData, 2), 2, '.', '');
}
$this->addToCalculationCache($col->id."-".$payroll->id."-".$employeeId, $val);
return $val;
}
$sum = 0;
$payRollEmp = new PayrollEmployee();
$payRollEmp->Load("id = ?", array($payrollEmployeeId));
//Salary
LogManager::getInstance()->info("salary components row:".$col->salary_components);
if (!empty($col->salary_components) &&
!empty(json_decode($col->salary_components, true))) {
$salaryComponent = new SalaryComponent();
$salaryComponents = $salaryComponent->Find(
"id in (".implode(",", json_decode($col->salary_components, true)).")",
array()
);
LogManager::getInstance()->info("salary components:".$salaryComponents);
foreach ($salaryComponents as $salaryComponent) {
$sum += $this->getTotalForEmployeeSalaryByComponent($employeeId, $salaryComponent->id);
}
}
//Deductions
if (!empty($col->deductions) &&
!empty(json_decode($col->deductions, true))) {
$deduction = new Deduction();
if (empty($payRollEmp->deduction_group)) {
$deductions = $deduction->Find(
"id in (".implode(",", json_decode($col->deductions, true)).")",
array()
);
} else {
$deductions = $deduction->Find(
"deduction_group = ? and id in (".implode(",", json_decode($col->deductions, true)).")",
array($payRollEmp->deduction_group)
);
}
$allowedDeductions = $this->getAllowedDeductionsForEmployee($employeeId, $payRollEmp->deduction_group);
foreach ($deductions as $deduction) {
if (!in_array($deduction->id, $allowedDeductions)) {
continue;
}
}
foreach ($deductions as $deduction) {
$sum += $this->calculateDeductionValue($employeeId, $deduction, $payroll);
}
}
if (!$noColumnCalculations) {
$evalMath = new EvalMath();
if ($col->function_type === 'Simple') {
$evalMath->evaluate('max(x,y) = (y - x) * ceil(tanh(exp(tanh(y - x)) - exp(0))) + x');
$evalMath->evaluate('min(x,y) = y - (y - x) * ceil(tanh(exp(tanh(y - x)) - exp(0)))');
}
if (!empty($col->add_columns) &&
!empty(json_decode($col->add_columns, true))) {
$colIds = json_decode($col->add_columns, true);
$payrollColumn = new PayrollColumn();
$payrollColumns = $payrollColumn->Find("id in (".implode(",", $colIds).")", array());
foreach ($payrollColumns as $payrollColumn) {
$sum += $this->calculatePayrollColumn(
$payrollColumn,
$payroll,
$employeeId,
$payrollEmployeeId,
true
);
}
}
if (!empty($col->sub_columns) &&
!empty(json_decode($col->sub_columns, true))) {
$colIds = json_decode($col->sub_columns, true);
$payrollColumn = new PayrollColumn();
$payrollColumns = $payrollColumn->Find("id in (".implode(",", $colIds).")", array());
foreach ($payrollColumns as $payrollColumn) {
$sum -= $this->calculatePayrollColumn(
$payrollColumn,
$payroll,
$employeeId,
$payrollEmployeeId,
true
);
}
}
if (!empty($col->calculation_columns) &&
!empty(json_decode($col->calculation_columns, true)) && !empty($col->calculation_function)) {
$cc = json_decode($col->calculation_columns);
$func = $col->calculation_function;
$variableList = [];
foreach ($cc as $c) {
$value = $this->getFromCalculationCache($c->column."-".$payroll->id."-".$employeeId);
if ($col->function_type === 'Simple') {
if (empty($value)) {
$value = 0.00;
}
$func = str_replace($c->name, $value, $func);
} else {
$variableList[$c->name] = $value;
}
}
try {
if ($col->function_type === 'Simple') {
$sum += $evalMath->evaluate($func);
} else {
$sum = ScriptRunner::executeJs($variableList, $func);
}
} catch (\Exception $e) {
LogManager::getInstance()->error("Error:".$e->getMessage());
LogManager::getInstance()->notifyException($e);
}
}
}
//return $sum;
$val = number_format(round($sum, 2), 2, '.', '');
$this->addToCalculationCache($col->id."-".$payroll->id."-".$employeeId, $val);
return $val;
}
private function calculateDeductionValue($employeeId, $deduction, $payroll)
{
$salaryComponents = array();
if (!empty($deduction->componentType) && !empty(json_decode($deduction->componentType, true))) {
$salaryComponent = new SalaryComponent();
$salaryComponents = $salaryComponent->Find(
"componentType in (".implode(",", json_decode($deduction->componentType, true)).")",
array()
);
}
$salaryComponents2 = array();
if (!empty($deduction->component) && !empty(json_decode($deduction->component, true))) {
$salaryComponent = new SalaryComponent();
$salaryComponents2 = $salaryComponent->Find(
"id in (".implode(",", json_decode($deduction->component, true)).")",
array()
);
}
$sum = 0;
$salaryComponentIDs = array();
foreach ($salaryComponents as $sc) {
$salaryComponentIDs[] = $sc->id;
$sum += $this->getTotalForEmployeeSalaryByComponent($employeeId, $sc->id);
}
foreach ($salaryComponents2 as $sc) {
if (!in_array($sc->id, $salaryComponentIDs)) {
$salaryComponents[] = $sc;
$sum += $this->getTotalForEmployeeSalaryByComponent($employeeId, $sc->id);
}
}
if (!empty($deduction->payrollColumn)) {
$columnVal = $this->getFromCalculationCache($deduction->payrollColumn."-".$payroll->id."-".$employeeId);
if (!empty($columnVal)) {
$sum += $columnVal;
}
}
$deductionFunction = $this->getDeductionFunction($deduction, $sum);
if (empty($deductionFunction)) {
LogManager::getInstance()->error("Deduction function not found");
return 0;
}
$deductionFunction = str_replace("X", $sum, $deductionFunction);
$evalMath = new EvalMath();
$val = $evalMath->evaluate($deductionFunction);
return floatval($val);
}
private function getDeductionFunction($deduction, $amount)
{
$amount = floatval($amount);
$ranges = json_decode($deduction->rangeAmounts);
foreach ($ranges as $range) {
$lowerLimitPassed = false;
if ($range->lowerCondition == "No Lower Limit") {
$lowerLimitPassed = true;
} elseif ($range->lowerCondition == "gt") {
if (floatval($range->lowerLimit) < $amount) {
$lowerLimitPassed = true;
}
} elseif ($range->lowerCondition == "gte") {
if (floatval($range->lowerLimit) <= $amount) {
$lowerLimitPassed = true;
}
}
$upperLimitPassed = false;
if ($range->upperCondition == "No Upper Limit") {
$upperLimitPassed = true;
} elseif ($range->upperCondition == "lt") {
if (floatval($range->upperLimit) > $amount) {
$upperLimitPassed = true;
}
} elseif ($range->upperCondition == "lte") {
if (floatval($range->upperLimit) >= $amount) {
$upperLimitPassed = true;
}
}
if ($lowerLimitPassed && $upperLimitPassed) {
return $range->amount;
}
}
return null;
}
private function getTotalForEmployeeSalaryByComponent($employeeId, $salaryComponentId)
{
$empSalary = new EmployeeSalary();
$list = $empSalary->Find("employee = ? and component =?", array($employeeId, $salaryComponentId));
$sum = 0;
foreach ($list as $empSalary) {
$sum += floatval($empSalary->amount);
}
return $sum;
}
private function getAllowedDeductionsForEmployee($employeeId, $deductionGroup)
{
$payrollEmp = new PayrollEmployee();
$payrollEmp->Load("employee = ?", array($employeeId));
$allowed = array();
$deduction = new Deduction();
if (empty($payrollEmp->deduction_allowed) || empty(json_decode($payrollEmp->deduction_allowed, true))) {
$allowed = $deduction->Find("deduction_group = ?", array($deductionGroup));
} else {
$allowedIds = json_decode($payrollEmp->deduction_allowed, true);
$allowed = $deduction->Find("id in (".implode(",", $allowedIds).")");
}
$allowedFiltered = array();
$disallowedIds = json_decode($payrollEmp->deduction_exemptions, true);
if (!empty($disallowedIds)) {
foreach ($allowed as $item) {
if (!in_array($item->id, $disallowedIds)) {
$allowedFiltered[] = $item;
}
}
} else {
$allowedFiltered = $allowed;
}
$allowedIds = array();
foreach ($allowedFiltered as $item) {
$allowedIds[] = $item->id;
}
return $allowedIds;
}
public function getAllData($req)
{
$cal = new PayrollCalculations();
$rowTable = BaseService::getInstance()->getFullQualifiedModelClassName($req->rowTable);
$columnTable = BaseService::getInstance()->getFullQualifiedModelClassName($req->columnTable);
$valueTable = BaseService::getInstance()->getFullQualifiedModelClassName($req->valueTable);
$save = $req->save;
//Only select employees matching pay frequency
$payroll = new Payroll();
$payroll->Load("id = ?", array($req->payrollId));
$columnList = json_decode($payroll->columns, true);
//Get Child company structures
$cssResp = CompanyStructure::getAllChildCompanyStructures($payroll->department);
error_log(json_encode($cssResp));
$css = $cssResp->getData();
$cssIds = array();
foreach ($css as $c) {
$cssIds[] = $c->id;
}
$employeeNamesById = array();
$baseEmp = new Employee();
$baseEmpList = $baseEmp->Find(
"department in (".implode(",", $cssIds).") and status = ?",
array('Active')
);
$empIds = array();
foreach ($baseEmpList as $baseEmp) {
$employeeNamesById[$baseEmp->id] = $baseEmp->first_name." ".$baseEmp->last_name;
$empIds[] = $baseEmp->id;
}
$emp = new $rowTable();
$emps = $emp->Find(
"pay_frequency = ? and deduction_group = ? and employee in (".implode(",", $empIds).")",
array($payroll->pay_period, $payroll->deduction_group)
);
if (!$emps) {
error_log("Error:".$emp->ErrorMsg());
} else {
error_log("Employees:".json_encode($emps));
}
$employees = array();
foreach ($emps as $emp) {
$empNew = new \stdClass();
$empNew->id = $emp->employee;
$empNew->payrollEmployeeId = $emp->id;
$empNew->name = $employeeNamesById[$emp->employee];
$employees[] = $empNew;
}
$column = new $columnTable();
$columns = $column->Find(
"enabled = ? and id in (".implode(",", $columnList).") order by colorder, id",
array('Yes')
);
$cost = new $valueTable();
$costs = $cost->Find("payroll = ?", array($req->payrollId));
//Build value map
$valueMap = array();
foreach ($costs as $val) {
if (!isset($valueMap[$val->employee])) {
$valueMap[$val->employee] = array();
}
$valueMap[$val->employee][$val->payroll_item] = $val;
}
//Fill hours worked
foreach ($employees as $e) {
if ($payroll->status != "Completed") {
foreach ($columns as $column) {
if (isset($valueMap[$e->id][$column->id]) && $column->editable == "Yes") {
$this->addToCalculationCache(
$column->id."-".$payroll->id."-".$e->id,
$valueMap[$e->id][$column->id]->amount
);
continue;
}
$item = new PayrollData();
$item->payroll = $req->payrollId;
$item->employee = $e->id;
$item->payroll_item = $column->id;
$item->amount = $this->calculatePayrollColumn($column, $payroll, $e->id, $e->payrollEmployeeId);
if ($item->amount == "") {
$item->amount = $column->default_value;
}
$valueMap[$e->id][$column->id] = $item;
}
}
}
$values = array();
foreach ($valueMap as $key => $val) {
$values = array_merge($values, array_values($val));
}
if ($save == "1") {
foreach ($values as $value) {
if (empty($value->id)) {
$value->Save();
}
}
}
if ($payroll->status == 'Processing') {
$payroll->status = 'Completed';
$payroll->Save();
}
if ($payroll->status == 'Completed') {
$newCols = array();
foreach ($columns as $col) {
$col->editable = 'No';
$newCols[] = $col;
}
$columns = $newCols;
}
return new IceResponse(IceResponse::SUCCESS, array($employees,$columns,$values));
}
public function updateAllData($req)
{
$resp = $this->updateData($req);
if ($resp->getStatus() == IceResponse::SUCCESS) {
$payroll = new Payroll();
$payroll->Load("id = ?", array($req->payrollId));
$payroll->status = 'Processing';
$ok = $payroll->Save();
if (!$ok) {
return new IceResponse(IceResponse::ERROR, $payroll->ErrorMsg());
}
}
return $resp;
}
public function updateData($req)
{
$payroll = new Payroll();
$payroll->Load("id = ?", array($req->payrollId));
if ($payroll->status == 'Completed') {
return new IceResponse(IceResponse::ERROR, true);
}
$valueTable = BaseService::getInstance()->getFullQualifiedModelClassName($req->valueTable);
$payrollId = $req->payrollId;
foreach ($req as $key => $val) {
if (!is_array($val)) {
continue;
}
$data = new $valueTable();
$data->Load("payroll = ? and employee = ? and payroll_item = ?", array($payrollId,$val[1],$val[0]));
if (empty($data->id)) {
$data->payroll = $payrollId;
$data->employee = $val[1];
$data->payroll_item = $val[0];
}
$data->amount = $val[2];
LogManager::getInstance()->info("Saving payroll data :".json_encode($data));
$ok = $data->Save();
if (!$ok) {
LogManager::getInstance()->error("Error saving payroll data:".$data->ErrorMsg());
}
}
return new IceResponse(IceResponse::SUCCESS, true);
}
public function deletePayrollGroup($req)
{
$employee = new PayrollEmployee();
$report = new Payroll();
$payrollGroup = new DeductionGroup();
$payrollColumn = new PayrollColumn();
$calcMethod =new Deduction();
$payrollGroup->Load("id = ?", array($req->id));
if (empty($payrollGroup->id)) {
return new IceResponse(IceResponse::ERROR);
}
$this->baseService->checkSecureAccess('delete', $payrollGroup, 'DeductionGroup', $_POST);
$this->baseService->checkSecureAccess('delete', $payrollColumn, 'payrollColumn', $_POST);
$this->baseService->checkSecureAccess('delete', $calcMethod, 'Deduction', $_POST);
$employee->Load("deduction_group = ?", $payrollGroup->id);
$report->Load("deduction_group = ?", $payrollGroup->id);
$payrollColumn->Load("deduction_group = ?", $payrollGroup->id);
$calcMethod->Load("deduction_group = ?", $payrollGroup->id);
if (!empty($employee->id)) {
return new IceResponse(
IceResponse::ERROR,
"There are employees attached to this payroll group,
please re-assign employees to a different payroll group"
);
}
if (!empty($report->id)) {
return new IceResponse(
IceResponse::ERROR,
"There are payroll reports attached to this group,
please move the payroll reports to a different payroll group before deleting this group"
);
}
BaseService::getInstance()->getDB()->Execute(
'DELETE FROM PayrollColumns WHERE deduction_group= ? ',
array($req->id)
);
BaseService::getInstance()->getDB()->Execute(
'DELETE FROM Deductions WHERE deduction_group= ? ',
array($req->id)
);
$ok = $payrollGroup->Delete();
if (!$ok) {
return new IceResponse(
IceResponse::ERROR,
"Error occurred while deleting payroll group"
);
}
return new IceResponse(IceResponse::SUCCESS);
}
}