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 @@
+
+
+
+
+
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`)
+);