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); } }