diff --git a/api-payroll/public/html/landing.php b/api-payroll/public/html/landing.php index 7d12330..eddf31b 100644 --- a/api-payroll/public/html/landing.php +++ b/api-payroll/public/html/landing.php @@ -38,7 +38,7 @@ if(!isset($_SESSION['userName'])){
  • - Management + Management
  • diff --git a/api-payroll/public/html/registerWorkDays.php b/api-payroll/public/html/registerWorkDays.php new file mode 100644 index 0000000..5bdd05d --- /dev/null +++ b/api-payroll/public/html/registerWorkDays.php @@ -0,0 +1,102 @@ + + + +
    +
    +
    +
    +

    Managing work days

    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    + + +
    +
    + +
    + +
    +
    +
    +
    +
    + Save +
    +
    +
    +
    +
    + diff --git a/api-payroll/public/js/editEmployee.js b/api-payroll/public/js/editEmployee.js index c51e545..2416684 100644 --- a/api-payroll/public/js/editEmployee.js +++ b/api-payroll/public/js/editEmployee.js @@ -52,7 +52,7 @@ $(document).ready(function(){ }); /** - * Loads the the enmployee types into their select option + * Loads the the employee types into their select option */ function loadEmployeeTypes(){ let baseUrl = getbaseUrl(); diff --git a/api-payroll/public/js/registerWorkDays.js b/api-payroll/public/js/registerWorkDays.js new file mode 100644 index 0000000..5579599 --- /dev/null +++ b/api-payroll/public/js/registerWorkDays.js @@ -0,0 +1,219 @@ +/** + * Bootstrapping the starting actions for the module + */ +$(document).ready(function(){ + let baseUrl = getbaseUrl(); + + loadEmployeeTypesForWorkDays(); + + $('.datepicker').datepicker({ + format: "yyyy/mm/dd", + autoclose: true + }); + + // Not to be edited + $("#hidenEmployeeCodeForWorkDays").hide(); + + // Setting up bloodhound typeahead + let employeesList = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace("name"), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + 'cache': false, + url: baseUrl + '/api/employee/find', + + replace: function(url, uriEncodedQuery) { + + return url + '/' + uriEncodedQuery + + }, + wildcard: '%QUERY', + filter: function (data) { + return data; + } + } + }); + + employeesList.initialize(); + + $("#workDaysSearchEmployee").typeahead({ + hint: true, + highlight: true, + minLength: 3 + }, + { + name: "result", + displayKey: "fullName", + source: employeesList.ttAdapter() + }).bind("typeahead:selected", function(obj, datum, name) { + $(this).data("id", datum.code); + + loadEmployeeDataForWorkDays(datum.code); + validateEmployeeCanDoOtherRoles(datum.code); + $('#hidenEmployeeCodeForWorkDaysCode').val(datum.code); // For future reference + }); +}); + + +/** + * Loads the the employee types into their select option + */ +function loadEmployeeTypesForWorkDays(){ + let baseUrl = getbaseUrl(); + + $.ajax({ + url: baseUrl + '/api/employee/types', + type: 'GET', + dataType: 'json', + success:function(data){ + $(data).each(function(i,v){ + $('#workDaysEmployeeRol').append( + '' + ); + + $('#workDaysEmployeePerformedRol').append( + '' + ); + }); + }, + error:function(x,e) { + let responseText = $.parseJSON(x["responseText"]); + + if (x.status==0) { + $('#modalErrorInternetConnection').modal('show'); + } else if(x.status==404) { + $('#modalError404').modal('show'); + } else if(x.status==500) { + $('#modalServerResponseError').modal('show'); + document.getElementById('modalResponseError').innerHTML = responseText['message']; + } else if(e=='parsererror') { + $('#modalErrorParsererror').modal('show'); + } else if(e=='timeout'){ + $('#modalErrorTimeout').modal('show'); + } else { + $('#modalErrorOther').modal('show'); + } + }, + }); +} + +/** + * Searches the employee data by its employee code and loads it + * into the form to be edited and saved + * + * @param code string + */ +function loadEmployeeDataForWorkDays(code){ + let baseUrl = getbaseUrl(); + + $.ajax({ + url: baseUrl + '/api/employee/code/' + code, + type: 'GET', + dataType: 'json', + success:function(data){ + let fullName = data['firstName'] + ' ' + data['middleName'] + ' ' + data['lastName']; + + $('#workDaysEmployeeName').val(fullName); + $('#workDaysEmployeeRol').val(data['idEmployeeType']); + $('#workDaysEmployeePerformedRol').val(data['idEmployeeType']); + $('#workDaysEmployeeContractType').val(data['contractType']); + }, + error:function(x,e) { + let responseText = $.parseJSON(x["responseText"]); + + if (x.status==0) { + $('#modalErrorInternetConnection').modal('show'); + } else if(x.status==404) { + $('#modalError404').modal('show'); + } else if(x.status==500) { + $('#modalServerResponseError').modal('show'); + document.getElementById('modalResponseError').innerHTML = responseText['message']; + } else if(e=='parsererror') { + $('#modalErrorParsererror').modal('show'); + } else if(e=='timeout'){ + $('#modalErrorTimeout').modal('show'); + } else { + $('#modalErrorOther').modal('show'); + } + }, + }); +} + +/** + * Based on the employee code determines their type to decide if + * they should be able to cover for other roles or not + * + * @param code string + */ +function validateEmployeeCanDoOtherRoles(code){ + let baseUrl = getbaseUrl(); + + $.ajax({ + url: baseUrl + '/api/employee/type/' + code, + type: 'GET', + dataType: 'json', + success:function(data){ + if(data == 3){ + $("#workDaysEmployeePerformedRol").prop('disabled', false); + } + }, + error:function(x,e) { + let responseText = $.parseJSON(x["responseText"]); + + if (x.status==0) { + $('#modalErrorInternetConnection').modal('show'); + } else if(x.status==404) { + $('#modalError404').modal('show'); + } else if(x.status==500) { + $('#modalServerResponseError').modal('show'); + document.getElementById('modalResponseError').innerHTML = responseText['message']; + } else if(e=='parsererror') { + $('#modalErrorParsererror').modal('show'); + } else if(e=='timeout'){ + $('#modalErrorTimeout').modal('show'); + } else { + $('#modalErrorOther').modal('show'); + } + }, + }); +} + +function saveNewWorkDay(){ + let baseUrl = getbaseUrl(); + + let parameters = { + "code":$('#hidenEmployeeCodeForWorkDaysCode').val(), + "idEmployeeTypePerformed":$('#workDaysEmployeePerformedRol').val(), + "deliveries":$('#workDaysEmployeeDeliveries').val(), + "date":$('#workDaysEmployeeWorkedDay').val(), + }; + + $.ajax({ + url: baseUrl + '/api/employee/workday', + type: 'POST', + dataType: 'json', + data: parameters, + success:function(data){ + $('#modalServerResponseSuccess').modal('show'); + document.getElementById('serverResponseSuccess').innerHTML = data['message']; + }, + error:function(x,e) { + let responseText = $.parseJSON(x["responseText"]); + + if (x.status==0) { + $('#modalErrorInternetConnection').modal('show'); + } else if(x.status==404) { + $('#modalError404').modal('show'); + } else if(x.status==500) { + $('#modalServerResponseError').modal('show'); + document.getElementById('modalResponseError').innerHTML = responseText['message']; + } else if(e=='parsererror') { + $('#modalErrorParsererror').modal('show'); + } else if(e=='timeout'){ + $('#modalErrorTimeout').modal('show'); + } else { + $('#modalErrorOther').modal('show'); + } + }, + }); +} \ No newline at end of file diff --git a/api-payroll/src/application/EmployeeApplication.php b/api-payroll/src/application/EmployeeApplication.php index cc52e32..97665c2 100644 --- a/api-payroll/src/application/EmployeeApplication.php +++ b/api-payroll/src/application/EmployeeApplication.php @@ -1,6 +1,7 @@ asserts->isNotEmpty($firstName, "The first name can't be empty."); @@ -87,6 +89,7 @@ class EmployeeApplication{ * @param $code string * @param $contractType string * @return mixed + * @throws Exception */ function savePersonAsEmployee($idEmployeeType, $idPerson, $code, $contractType){ $this->asserts->higherThanZero($idEmployeeType, "idEmployeeType must be higher than 0"); @@ -115,6 +118,7 @@ class EmployeeApplication{ /** * @param $requestData object * @return array + * @throws Exception */ function saveNewEmployee($requestData){ // Getting and validating the data @@ -577,5 +581,140 @@ class EmployeeApplication{ return $matches; } + + /** + * Helper to determine if the date has already been saved as a worked day for + * an employee, so long as it's currently active in the database + * + * @param $idEmployee integer + * @param $date date + * @return integer + * @throws Exception + */ + function checkDateNotUsedWorkDayPerEmployee($idEmployee, $date){ + $this->asserts->isNotEmpty($idEmployee, "The code can't be empty."); + $this->asserts->higherThanZero($idEmployee, "idEmployee must be higher than 0"); + + $this->asserts->isNotEmpty($date, "The code can't be empty."); + + $stmt = $this->pdo->prepare("SELECT + COALESCE((SELECT + COUNT(*) + FROM + paymentsPerEmployeePerDay + WHERE + date = :date AND idEmployee = :idEmployee + AND status = 'ACTIVE'), + 0) AS timesDateFound"); + + $stmt->execute(array(':date' => $date, ':idEmployee' => $idEmployee)); + $results = $stmt->fetchAll(); + if(!$results){ + throw new Exception('Unable to determine the usage of date for the worked days.'); + } + $stmt = null; + + return $results[0]['timesDateFound']; + } + + /** + * Saves the new worked day for the employee + * + * @param $idEmployee integer + * @param $date date + * @param $baseAmount double + * @param $bonusTime double + * @param $deliveries double + * @return integer + * @throws Exception + */ + function saveWorkedDay($idEmployee, $date, $baseAmount, $bonusTime, $deliveries){ + $this->asserts->isNotEmpty($idEmployee, "The idEmployee can't be empty."); + $this->asserts->isNotEmpty($date, "The date can't be empty."); + $this->asserts->isNotEmpty($baseAmount, "The base payment per day can't be empty."); + $this->asserts->isNotEmpty($bonusTime, "The bonus per worked hours can't be empty."); + $this->asserts->isNotEmpty($deliveries, "The payment for deliveries can't be empty."); + + try { + $stmt = $this->pdo->prepare("INSERT INTO paymentsPerEmployeePerDay + (idEmployee, date, baseAmount, bonusTime, deliveries) + VALUES (:idEmployee, :date, :baseAmount, :bonusTime, :deliveries)"); + $this->pdo->beginTransaction(); + $stmt->execute(array(':idEmployee' => $idEmployee, ':date' => $date, ':baseAmount' => $baseAmount, + ':bonusTime' => $bonusTime, ':deliveries' => $deliveries)); + $id = $this->pdo->lastInsertId(); + $this->pdo->commit(); + + return $id; + + $stmt = null; + } catch( PDOExecption $e ) { + $this->pdo->rollback(); + throw new Exception('There was an error while trying to save the worked day.'); + } + } + + /** + * Takes the data from the front end for the new worked day for a + * employee and saves it + * + * @param $requestData object + * @return array + * @throws Exception + */ + function SaveNewWorkDay($requestData){ + $code = $requestData['code']; + $this->asserts->isNotEmpty($code, "The code can't be empty."); + + $idEmployee = $this->getIdEmployeeByCode($code); + $this->asserts->higherThanZero($idEmployee, "idEmployee must be higher than 0"); + + $idEmployeeType = $this->getIdEmployeeTypeByCode($code); + $this->asserts->higherThanZero($idEmployeeType, "idEmployeeType must be higher than 0"); + + $idEmployeeTypePerformed = $requestData['idEmployeeTypePerformed']; + $this->asserts->isNotEmpty($idEmployeeTypePerformed, "The performed rol must be provided."); + $this->asserts->higherThanZero($idEmployeeTypePerformed, "idEmployeeTypePerformed must be higher than 0"); + + $deliveries = $requestData['deliveries']; + $this->asserts->isNotEmpty($deliveries, "The number of deliveries cannot be empty or 0."); + + $date = $requestData['date']; + $this->asserts->isNotEmpty($date, "The worked date cannot be empty."); + + if($this->checkDateNotUsedWorkDayPerEmployee($idEmployee, $date) > 0){ + throw new Exception("This date has already been saved as a worked day."); + } + + // The emplpoyee can't take that rol + if($idEmployeeType != 3 and $idEmployeeType != $idEmployeeTypePerformed){ + throw new Exception("The performed rol can't be done by this type of employee."); + } + + // If we're working on a different month + $this->asserts->datesHaveSameMonth($date, date('Y-m-d'), "Work days can only be registered within the same month."); + + $baseAmountPaid = $this->settings['hoursPerWorkDay'] * $this->settings['paymentPerHour']; + + // Getting setting data based on employee type that was performed + switch ($idEmployeeTypePerformed) { + case 1: + $perHourBonus = $this->settings['perHourBonusDriver']; + break; + case 2: + $perHourBonus = $this->settings['perHourBonusLoader']; + break; + case 3: + $perHourBonus = $this->settings['perHourBonusAux']; + break; + } + + $bonusTime = $perHourBonus * $this->settings['hoursPerWorkDay']; + $bonusDeliveries = $deliveries * $this->settings['bonusPerDelivery']; + + $this->saveWorkedDay($idEmployee, $date, $baseAmountPaid, $bonusTime, $bonusDeliveries); + + return array('status' => 'success', 'message' => 'The worked day has been saved.', 'data' => $requestData); + } } ?> \ No newline at end of file diff --git a/api-payroll/src/routes.php b/api-payroll/src/routes.php index 6a2fc5b..3a34c82 100644 --- a/api-payroll/src/routes.php +++ b/api-payroll/src/routes.php @@ -95,4 +95,12 @@ $app->get('/api/employee/code/{code}', function (Request $request, Response $res return $response->withStatus(200) ->withHeader('Content-Type', 'application/json') ->write(json_encode($this->employeeApplication->getEmployeeDataByCode($code))); +}); + +$app->post('/api/employee/workday', function ($request, $response) { + $requestData = $request->getParsedBody(); + + return $response->withStatus(200) + ->withHeader('Content-Type', 'application/json') + ->write(json_encode($this->employeeApplication->SaveNewWorkDay($requestData))); }); \ No newline at end of file diff --git a/api-payroll/src/service/Asserts.php b/api-payroll/src/service/Asserts.php index d627334..ce3db51 100644 --- a/api-payroll/src/service/Asserts.php +++ b/api-payroll/src/service/Asserts.php @@ -81,5 +81,19 @@ class Asserts{ throw new Exception($errorMessage); } } + + /** + * Compares two dates to dertermine if they have the same month + * + * @param $firstDate date + * @param $secondDate date + * @param $errorMessage string + * @throws Exception + */ + function datesHaveSameMonth($firstDate, $secondDate, $errorMessage){ + if (date("m",strtotime($firstDate)) != date("m",strtotime($secondDate))){ + throw new Exception($errorMessage); + } + } } ?> diff --git a/api-payroll/src/settings.php b/api-payroll/src/settings.php index 4a54cdd..0e84984 100644 --- a/api-payroll/src/settings.php +++ b/api-payroll/src/settings.php @@ -46,6 +46,18 @@ return [ 'employee' => [ 'codeLength' => '3', 'contractTypes' => array('INTERNO', 'EXTERNO'), + 'hoursPerWorkDay' => 8, + 'paymentPerHour' => 30, + 'bonusPerDelivery' => 5, + 'perHourBonusDriver' => 10, + 'perHourBonusLoader' => 5, + 'perHourBonusAux' => 0, + 'BaseIsr' => 9, + 'extraIsr' => 3, + 'taxesAddUp' => true, // If true this will be total/(9 + 3) else they're subtracted separately + 'amountForExtraTaxes' => 16000, + 'vouchersForAllContractTypes' => false, // Outsourced personal won't get vouchers + 'percentOfPaymentForVouchers' => 4, ], ], ]; diff --git a/database/database.sql b/database/database.sql index 7163d96..232ae18 100644 --- a/database/database.sql +++ b/database/database.sql @@ -73,3 +73,19 @@ CREATE TABLE IF NOT EXISTS `employees` ( PRIMARY KEY (`id`), UNIQUE (`code`) ); + +DROP TABLE IF EXISTS paymentsPerEmployeePerDay; +CREATE TABLE IF NOT EXISTS `paymentsPerEmployeePerDay` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `idEmployee` INT UNSIGNED NOT NULL comment 'The employee to who this payment will be made', + `date` DATE NOT NULL DEFAULT '1900-01-01' comment 'Date of the worked day', + `baseAmount` DOUBLE(10,2) NOT NULL DEFAULT 0.0 comment 'Amount paid for the hours worked', + `bonusTime` DOUBLE(10,2) NOT NULL DEFAULT 0.0 comment 'Bonus paid for the hours worked', + `deliveries` DOUBLE(10,2) NOT NULL DEFAULT 0.0 comment 'Bonus for the number of deliveries', + `status` ENUM('ACTIVE', 'INACTIVE') NOT NULL DEFAULT 'ACTIVE', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP comment 'The date on which the registry was created', + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment 'The date of the last time the row was modified', + PRIMARY KEY (`id`), + FOREIGN KEY (idEmployee) REFERENCES employees(id), + UNIQUE (`idEmployee`, `date`, `status`) +);