refs #6259 TicketSms to ClientSms #1900
|
@ -1,14 +1,5 @@
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
|
||||||
const {models} = require('vn-loopback/server/server');
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
const handlePromiseLogout = (Self, {id}, courtesyTime) => {
|
|
||||||
new Promise(res => {
|
|
||||||
setTimeout(() => {
|
|
||||||
res(Self.logout(id));
|
|
||||||
}
|
|
||||||
, courtesyTime * 1000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('renewToken', {
|
Self.remoteMethodCtx('renewToken', {
|
||||||
description: 'Checks if the token has more than renewPeriod seconds to live and if so, renews it',
|
description: 'Checks if the token has more than renewPeriod seconds to live and if so, renews it',
|
||||||
|
@ -28,14 +19,26 @@ module.exports = Self => {
|
||||||
const {accessToken: token} = ctx.req;
|
const {accessToken: token} = ctx.req;
|
||||||
|
|
||||||
// Check if current token is valid
|
// Check if current token is valid
|
||||||
const isValid = await validateToken(token);
|
|
||||||
if (isValid)
|
const {renewPeriod, courtesyTime} = await models.AccessTokenConfig.findOne({
|
||||||
|
fields: ['renewPeriod', 'courtesyTime']
|
||||||
|
});
|
||||||
|
const now = Date.now();
|
||||||
|
const differenceMilliseconds = now - token.created;
|
||||||
|
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
|
||||||
|
const isNotExceeded = differenceSeconds < renewPeriod - courtesyTime;
|
||||||
|
if (isNotExceeded)
|
||||||
return token;
|
return token;
|
||||||
|
|
||||||
const {courtesyTime} = await models.AccessTokenConfig.findOne({fields: ['courtesyTime']});
|
|
||||||
|
|
||||||
// Schedule to remove current token
|
// Schedule to remove current token
|
||||||
handlePromiseLogout(Self, token, courtesyTime);
|
setTimeout(async() => {
|
||||||
|
try {
|
||||||
|
await Self.logout(token.id);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}, courtesyTime * 1000);
|
||||||
|
|
||||||
// Create new accessToken
|
// Create new accessToken
|
||||||
const user = await Self.findById(token.userId);
|
const user = await Self.findById(token.userId);
|
||||||
|
@ -43,14 +46,4 @@ module.exports = Self => {
|
||||||
|
|
||||||
return {id: accessToken.id, ttl: accessToken.ttl};
|
return {id: accessToken.id, ttl: accessToken.ttl};
|
||||||
};
|
};
|
||||||
|
|
||||||
async function validateToken(token) {
|
|
||||||
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod', 'courtesyTime']});
|
|
||||||
const now = Date.now();
|
|
||||||
const differenceMilliseconds = now - token.created;
|
|
||||||
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
|
|
||||||
const isValid = differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime;
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
describe('loopback model MailAliasAccount', () => {
|
||||||
|
it('should fail to add a mail Alias if the worker doesnt have ACLs', async() => {
|
||||||
|
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 57}};
|
||||||
|
await models.MailAliasAccount.create({mailAlias: 2, account: 5}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toEqual('The alias cant be modified');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a mail Alias', async() => {
|
||||||
|
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||||
|
await models.MailAliasAccount.create({mailAlias: 2, account: 5}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a mail Alias of an inherit role', async() => {
|
||||||
|
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||||
|
await models.MailAliasAccount.create({mailAlias: 3, account: 5}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a mail Alias', async() => {
|
||||||
|
const tx = await models.MailAliasAccount.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 1}};
|
||||||
|
const mailAclId = 2;
|
||||||
|
await models.MailAliasAccount.destroyAll({id: mailAclId}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -95,27 +95,30 @@
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
},
|
}, {
|
||||||
{
|
"property": "recoverPassword",
|
||||||
"property": "recoverPassword",
|
|
||||||
"accessType": "EXECUTE",
|
|
||||||
"principalType": "ROLE",
|
|
||||||
"principalId": "$everyone",
|
|
||||||
"permission": "ALLOW"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"property": "validateAuth",
|
|
||||||
"accessType": "EXECUTE",
|
"accessType": "EXECUTE",
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
},
|
}, {
|
||||||
{
|
"property": "validateAuth",
|
||||||
|
"accessType": "EXECUTE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}, {
|
||||||
"property": "privileges",
|
"property": "privileges",
|
||||||
"accessType": "*",
|
"accessType": "*",
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$authenticated",
|
"principalId": "$authenticated",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
|
}, {
|
||||||
|
"property": "renewToken",
|
||||||
|
"accessType": "WRITE",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$authenticated",
|
||||||
|
"permission": "ALLOW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"scopes": {
|
"scopes": {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- Definición de la tabla mailAliasACL
|
||||||
|
|
||||||
|
CREATE OR REPLACE TABLE `account`.`mailAliasAcl` (
|
||||||
|
`mailAliasFk` int(10) unsigned NOT NULL,
|
||||||
|
`roleFk` int(10) unsigned NOT NULL,
|
||||||
|
FOREIGN KEY (`mailAliasFk`) REFERENCES `account`.`mailAlias` (`id`),
|
||||||
|
FOREIGN KEY (`roleFk`) REFERENCES `account`.`role` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
|
@ -0,0 +1,17 @@
|
||||||
|
DELETE FROM `salix`.`ACL`
|
||||||
|
WHERE model = 'VnUser'
|
||||||
|
AND property = 'renewToken';
|
||||||
|
|
||||||
|
INSERT INTO `account`.`role` (name, description)
|
||||||
|
VALUES ('timeControl','Tablet para fichar');
|
||||||
|
|
||||||
|
INSERT INTO `account`.`roleInherit` (role, inheritsFrom)
|
||||||
|
VALUES (127, 11);
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES
|
||||||
|
('WorkerTimeControl', 'login', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
|
||||||
|
('WorkerTimeControl', 'getClockIn', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
|
||||||
|
('WorkerTimeControl', 'clockIn', 'WRITE', 'ALLOW', 'ROLE', 'timeControl');
|
||||||
|
|
||||||
|
CALL `account`.`role_sync`();
|
|
@ -3010,6 +3010,12 @@ INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`)
|
||||||
(2, 'Error in sales details'),
|
(2, 'Error in sales details'),
|
||||||
(3, 'Error in customer data');
|
(3, 'Error in customer data');
|
||||||
|
|
||||||
|
INSERT INTO `account`.`mailAliasAcl` (`mailAliasFk`, `roleFk`)
|
||||||
|
VALUES
|
||||||
|
(1, 1),
|
||||||
|
(2, 9),
|
||||||
|
(3, 15);
|
||||||
|
|
||||||
INSERT INTO `vn`.`docuwareTablet` (`tablet`,`description`)
|
INSERT INTO `vn`.`docuwareTablet` (`tablet`,`description`)
|
||||||
VALUES
|
VALUES
|
||||||
('Tablet1','Jarvis tablet'),
|
('Tablet1','Jarvis tablet'),
|
||||||
|
|
|
@ -68,3 +68,4 @@ Load more results: Cargar más resultados
|
||||||
Send cau: Enviar cau
|
Send cau: Enviar cau
|
||||||
By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc
|
By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc
|
||||||
ExplainReason: Explique el motivo por el que no deberia aparecer este fallo
|
ExplainReason: Explique el motivo por el que no deberia aparecer este fallo
|
||||||
|
You already have the mailAlias: Ya tienes este alias de correo
|
||||||
|
|
|
@ -200,5 +200,6 @@
|
||||||
"Try again": "Try again",
|
"Try again": "Try again",
|
||||||
"keepPrice": "keepPrice",
|
"keepPrice": "keepPrice",
|
||||||
"Cannot past travels with entries": "Cannot past travels with entries",
|
"Cannot past travels with entries": "Cannot past travels with entries",
|
||||||
"It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}"
|
"It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}",
|
||||||
|
"Incorrect pin": "Incorrect pin."
|
||||||
}
|
}
|
||||||
|
|
|
@ -330,5 +330,8 @@
|
||||||
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima",
|
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima",
|
||||||
"Cannot past travels with entries": "No se pueden pasar envíos con entradas",
|
"Cannot past travels with entries": "No se pueden pasar envíos con entradas",
|
||||||
"It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}",
|
"It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}",
|
||||||
"This user does not have an assigned tablet": "Este usuario no tiene tablet asignada"
|
"This user does not have an assigned tablet": "Este usuario no tiene tablet asignada",
|
||||||
}
|
"Incorrect pin": "Pin incorrecto.",
|
||||||
|
"You already have the mailAlias": "Ya tienes este alias de correo",
|
||||||
|
"The alias cant be modified": "Este alias de correo no puede ser modificado"
|
||||||
|
}
|
|
@ -14,6 +14,9 @@
|
||||||
"MailAliasAccount": {
|
"MailAliasAccount": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"MailAliasAcl": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"MailConfig": {
|
"MailConfig": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,54 +2,44 @@
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
|
Self.rewriteDbError(function(err) {
|
||||||
|
if (err.code === 'ER_DUP_ENTRY')
|
||||||
|
return new UserError(`You already have the mailAlias`);
|
||||||
|
return err;
|
||||||
|
});
|
||||||
|
|
||||||
Self.observe('before save', async ctx => {
|
Self.observe('before save', async ctx => {
|
||||||
const changes = ctx.currentInstance || ctx.instance;
|
const changes = ctx.currentInstance || ctx.instance;
|
||||||
|
|
||||||
await Self.hasGrant(ctx, changes.mailAlias);
|
await checkModifyPermission(ctx, changes.mailAlias);
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.observe('before delete', async ctx => {
|
Self.observe('before delete', async ctx => {
|
||||||
const mailAliasAccount = await Self.findById(ctx.where.id);
|
const mailAliasAccount = await Self.findById(ctx.where.id);
|
||||||
|
|
||||||
await Self.hasGrant(ctx, mailAliasAccount.mailAlias);
|
await checkModifyPermission(ctx, mailAliasAccount.mailAlias);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
async function checkModifyPermission(ctx, mailAliasFk) {
|
||||||
* Checks if current user has
|
const userId = ctx.options.accessToken.userId;
|
||||||
* grant to add/remove alias
|
|
||||||
*
|
|
||||||
* @param {Object} ctx - Request context
|
|
||||||
* @param {Interger} mailAlias - mailAlias id
|
|
||||||
* @return {Boolean} True for user with grant
|
|
||||||
*/
|
|
||||||
Self.hasGrant = async function(ctx, mailAlias) {
|
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const accessToken = {req: {accessToken: ctx.options.accessToken}};
|
|
||||||
const userId = accessToken.req.accessToken.userId;
|
|
||||||
|
|
||||||
const canEditAlias = await models.ACL.checkAccessAcl(accessToken, 'MailAliasAccount', 'canEditAlias', 'WRITE');
|
const roles = await models.RoleMapping.find({
|
||||||
if (canEditAlias) return true;
|
fields: ['roleId'],
|
||||||
|
where: {principalId: userId}
|
||||||
|
});
|
||||||
|
|
||||||
const user = await models.VnUser.findById(userId, {fields: ['hasGrant']});
|
const availableMailAlias = await models.MailAliasAcl.findOne({
|
||||||
if (!user.hasGrant)
|
fields: ['mailAliasFk'],
|
||||||
throw new UserError(`You don't have grant privilege`);
|
include: {relation: 'mailAlias'},
|
||||||
|
where: {
|
||||||
const account = await models.Account.findById(userId, {
|
roleFk: {
|
||||||
fields: ['id'],
|
inq: roles.map(role => role.roleId),
|
||||||
include: {
|
},
|
||||||
relation: 'aliases',
|
mailAliasFk
|
||||||
scope: {
|
|
||||||
fields: ['mailAlias']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const aliases = account.aliases().map(alias => alias.mailAlias);
|
if (!availableMailAlias) throw new UserError('The alias cant be modified');
|
||||||
|
}
|
||||||
const hasAlias = aliases.includes(mailAlias);
|
|
||||||
if (!hasAlias)
|
|
||||||
throw new UserError(`You cannot assign/remove an alias that you are not assigned to`);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "MailAliasAcl",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.mailAliasAcl"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"mailAliasFk": {
|
||||||
|
"id": true,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"roleFk": {
|
||||||
|
"id": true,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"mailAlias": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "MailAlias",
|
||||||
|
"foreignKey": "mailAliasFk"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Role",
|
||||||
|
"foreignKey": "roleFk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,7 +90,7 @@ module.exports = Self => {
|
||||||
AND t.refFk IS NULL
|
AND t.refFk IS NULL
|
||||||
AND c.typeFk IN ('normal','trust')
|
AND c.typeFk IN ('normal','trust')
|
||||||
GROUP BY t.clientFk, negativeBase.taxableBase
|
GROUP BY t.clientFk, negativeBase.taxableBase
|
||||||
HAVING amount <> 0`, [args.from, args.to]));
|
HAVING amount < 0`, [args.from, args.to]));
|
||||||
|
|
||||||
stmt = new ParameterizedSQL(`
|
stmt = new ParameterizedSQL(`
|
||||||
SELECT f.*
|
SELECT f.*
|
||||||
|
|
|
@ -10,13 +10,17 @@ module.exports = Self => {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
description: 'From date',
|
description: 'From date',
|
||||||
required: true
|
required: true
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
arg: 'to',
|
arg: 'to',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
description: 'To date',
|
description: 'To date',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}, {
|
||||||
|
arg: 'filter',
|
||||||
|
type: 'object',
|
||||||
|
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
|
||||||
|
},
|
||||||
|
],
|
||||||
returns: [
|
returns: [
|
||||||
{
|
{
|
||||||
arg: 'body',
|
arg: 'body',
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
name: itemShelving
|
||||||
|
columns:
|
||||||
|
id: id
|
||||||
|
itemFk: item
|
||||||
|
shelvingFk: shelving
|
||||||
|
visible: visible
|
||||||
|
created: created
|
||||||
|
grouping: grouping
|
||||||
|
packing: packing
|
||||||
|
packagingFk: package
|
||||||
|
userFk: user
|
||||||
|
isChecked: isChecked
|
||||||
|
buyFk: buy
|
|
@ -0,0 +1,13 @@
|
||||||
|
name: artículo del carro
|
||||||
|
columns:
|
||||||
|
id: id
|
||||||
|
itemFk: artículo
|
||||||
|
shelvingFk: matrícula carro
|
||||||
|
visible: visible
|
||||||
|
created: creado
|
||||||
|
grouping: grouping
|
||||||
|
packing: packing
|
||||||
|
packagingFk: embalaje
|
||||||
|
userFk: usuario
|
||||||
|
isChecked: está revisado
|
||||||
|
buyFk: compra
|
|
@ -20,18 +20,10 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"isPreviousPreparedByPacking": {
|
|
||||||
"type": "boolean",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"code": {
|
"code": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"isPreviousPrepared": {
|
|
||||||
"type": "boolean",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"isPackagingArea": {
|
"isPackagingArea": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"required": true
|
"required": true
|
||||||
|
|
|
@ -20,9 +20,6 @@
|
||||||
},
|
},
|
||||||
"stateFk": {
|
"stateFk": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
|
||||||
"userFk": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
|
|
@ -23,9 +23,9 @@
|
||||||
<vn-td>{{::tracking.state.name}}</vn-td>
|
<vn-td>{{::tracking.state.name}}</vn-td>
|
||||||
<vn-td expand>
|
<vn-td expand>
|
||||||
<span
|
<span
|
||||||
ng-class="{'link': tracking.worker.id}"
|
ng-class="{'link': tracking.user.id}"
|
||||||
ng-click="workerDescriptor.show($event, tracking.worker.user.id)">
|
ng-click="workerDescriptor.show($event, tracking.user.id)">
|
||||||
{{::tracking.worker.user.name || 'System' | translate}}
|
{{::tracking.user.name || 'System' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td shrink-datetime>{{::tracking.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
|
<vn-td shrink-datetime>{{::tracking.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
|
||||||
|
|
|
@ -39,6 +39,7 @@ module.exports = Self => {
|
||||||
started.setFullYear(year);
|
started.setFullYear(year);
|
||||||
started.setMonth(0);
|
started.setMonth(0);
|
||||||
started.setDate(1);
|
started.setDate(1);
|
||||||
|
started.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
const ended = Date.vnNew();
|
const ended = Date.vnNew();
|
||||||
ended.setFullYear(year);
|
ended.setFullYear(year);
|
||||||
|
|
|
@ -43,16 +43,9 @@ module.exports = Self => {
|
||||||
const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
|
const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
|
||||||
const isHimself = userId == workerId;
|
const isHimself = userId == workerId;
|
||||||
|
|
||||||
if (!isSubordinate || (isSubordinate && isHimself && !isTeamBoss))
|
if (!isSubordinate || (isHimself && !isTeamBoss))
|
||||||
throw new UserError(`You don't have enough privileges`);
|
throw new UserError(`You don't have enough privileges`);
|
||||||
|
|
||||||
query = `CALL vn.workerTimeControl_clockIn(?,?,?)`;
|
return Self.clockIn(workerId, args.timed, args.direction, myOptions);
|
||||||
const [response] = await Self.rawSql(query, [workerId, args.timed, args.direction], myOptions);
|
|
||||||
if (response[0] && response[0].error)
|
|
||||||
throw new UserError(response[0].error);
|
|
||||||
|
|
||||||
await models.WorkerTimeControl.resendWeeklyHourEmail(ctx, workerId, args.timed, myOptions);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('clockIn', {
|
||||||
|
description: 'Check if the employee can clock in',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'workerFk',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'timed',
|
||||||
|
type: 'date'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'direction',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/clockIn`,
|
||||||
|
verb: 'POST'
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: 'Object',
|
||||||
|
root: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.clockIn = async(workerFk, timed, direction, options) => {
|
||||||
|
const myOptions = {};
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const query = 'CALL vn.workerTimeControl_clockIn(?, ?, ?)';
|
||||||
|
const [[response]] = await Self.rawSql(query, [workerFk, timed, direction], myOptions);
|
||||||
|
if (response && response.error)
|
||||||
|
throw new UserError(response.error);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('getClockIn', {
|
||||||
|
description: 'Shows the clockings for each day, in columns per day',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'workerFk',
|
||||||
|
type: 'int',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/getClockIn`,
|
||||||
|
verb: 'GET'
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: ['Object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getClockIn = async(workerFk, options) => {
|
||||||
|
const myOptions = {};
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const query = `CALL vn.workerTimeControl_getClockIn(?, ?)`;
|
||||||
|
const [result] = await Self.rawSql(query, [workerFk, Date.vnNew()], myOptions);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,35 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('login', {
|
||||||
|
description: 'Consult the user\'s information and the buttons that must be activated after logging in',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'pin',
|
||||||
|
type: 'string',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'Object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/login`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.login = async(ctx, pin, options) => {
|
||||||
|
const myOptions = {};
|
||||||
|
const $t = ctx.req.__;
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const query = `CALL vn.workerTimeControl_login(?)`;
|
||||||
|
const [[user]] = await Self.rawSql(query, [pin], myOptions);
|
||||||
|
if (!user) throw new UserError($t('Incorrect pin'));
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('resendWeeklyHourEmail', {
|
Self.remoteMethodCtx('resendWeeklyHourEmail', {
|
||||||
description: 'Adds a new hour registry',
|
description: 'Send the records for the week of the date provided',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
|
|
|
@ -0,0 +1,581 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
|
describe('workerTimeControl clockIn()', () => {
|
||||||
|
const workerId = 9;
|
||||||
|
const salesBossId = 19;
|
||||||
|
const hankPymId = 1107;
|
||||||
|
const jessicaJonesId = 1110;
|
||||||
|
const HHRRId = 37;
|
||||||
|
const teamBossId = 13;
|
||||||
|
const monday = 1;
|
||||||
|
const tuesday = 2;
|
||||||
|
const thursday = 4;
|
||||||
|
const friday = 5;
|
||||||
|
const sunday = 7;
|
||||||
|
const inTime = '2001-01-01T00:00:00.000Z';
|
||||||
|
const activeCtx = {
|
||||||
|
accessToken: {userId: 50},
|
||||||
|
};
|
||||||
|
const ctx = {req: activeCtx};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
|
active: activeCtx
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly clock in', async() => {
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
await models.WorkerTimeControl.clockIn(workerId, inTime, 'in', options);
|
||||||
|
const isClockIn = await models.WorkerTimeControl.findOne({
|
||||||
|
where: {
|
||||||
|
userFk: workerId
|
||||||
|
}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
expect(isClockIn).toBeDefined();
|
||||||
|
expect(isClockIn.direction).toBe('in');
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('as Role errors', () => {
|
||||||
|
it('should add if the current user is team boss and the target user is himself', async() => {
|
||||||
|
activeCtx.accessToken.userId = teamBossId;
|
||||||
|
const workerId = teamBossId;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
const todayAtOne = Date.vnNew();
|
||||||
|
todayAtOne.setHours(1, 0, 0, 0);
|
||||||
|
|
||||||
|
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||||
|
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
expect(createdTimeEntry.id).toBeDefined();
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete the created time entry for the team boss as himself', async() => {
|
||||||
|
activeCtx.accessToken.userId = teamBossId;
|
||||||
|
const workerId = teamBossId;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
const todayAtOne = Date.vnNew();
|
||||||
|
todayAtOne.setHours(1, 0, 0, 0);
|
||||||
|
|
||||||
|
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||||
|
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
expect(createdTimeEntry.id).toBeDefined();
|
||||||
|
|
||||||
|
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
||||||
|
|
||||||
|
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
|
||||||
|
|
||||||
|
expect(deletedTimeEntry).toBeNull();
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edit the created time entry for the team boss as HHRR', async() => {
|
||||||
|
activeCtx.accessToken.userId = HHRRId;
|
||||||
|
const workerId = teamBossId;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
const todayAtOne = Date.vnNew();
|
||||||
|
todayAtOne.setHours(1, 0, 0, 0);
|
||||||
|
|
||||||
|
ctx.args = {timed: todayAtOne, direction: 'in'};
|
||||||
|
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
expect(createdTimeEntry.id).toBeDefined();
|
||||||
|
|
||||||
|
ctx.args = {direction: 'out'};
|
||||||
|
const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(
|
||||||
|
ctx, createdTimeEntry.id, options
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updatedTimeEntry.direction).toEqual('out');
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('as saleBoss editor', () => {
|
||||||
|
let workerId;
|
||||||
|
beforeEach(() => {
|
||||||
|
activeCtx.accessToken.userId = salesBossId;
|
||||||
|
workerId = hankPymId;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail to add a time entry if the target user has an absence that day', async() => {
|
||||||
|
const date = Date.vnNew();
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
date.setDate(date.getDate() - 16);
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
try {
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`No está permitido trabajar`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail to add a time entry for a worker without an existing contract', async() => {
|
||||||
|
const date = Date.vnNew();
|
||||||
|
date.setFullYear(date.getFullYear() - 2);
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
try {
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`No hay un contrato en vigor`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail to add a time entry for a worker without an existing contract and exceeding time', async() => {
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setDate(date.getDate() - 2);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
date.setHours(0, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date.setHours(20, 0, 1);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('direction errors', () => {
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
let error;
|
||||||
|
it('should throw an error when trying "in" direction twice', async() => {
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date.setHours(10, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Dirección incorrecta`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => {
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
date.setHours(9, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'middle'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date.setHours(10, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Dirección incorrecta`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw an error when trying "out" before closing a "middle" couple', async() => {
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
date.setHours(9, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'middle'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date.setHours(10, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Dirección incorrecta`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when trying "middle" after "out"', async() => {
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
date.setHours(9, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date.setHours(10, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'middle'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Dirección incorrecta`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when trying "out" direction twice', async() => {
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
date.setHours(9, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date.setHours(10, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Dirección incorrecta`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('12h rest', () => {
|
||||||
|
activeCtx.accessToken.userId = salesBossId;
|
||||||
|
const workerId = hankPymId;
|
||||||
|
it('should throw an error when the 12h rest is not fulfilled yet', async() => {
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setDate(date.getDate() - 2);
|
||||||
|
date = weekDay(date, monday);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
date.setHours(16, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date = weekDay(date, tuesday);
|
||||||
|
date.setHours(4, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Descanso diario`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail as the 12h rest is fulfilled', async() => {
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setDate(date.getDate() - 2);
|
||||||
|
date = weekDay(date, monday);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
date.setHours(16, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date = weekDay(date, tuesday);
|
||||||
|
date.setHours(4, 1, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeDefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for 3500kg drivers with enforced 9h rest', () => {
|
||||||
|
activeCtx.accessToken.userId = salesBossId;
|
||||||
|
const workerId = jessicaJonesId;
|
||||||
|
it('should throw an error when the 9h enforced rest is not fulfilled', async() => {
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setDate(date.getDate() - 2);
|
||||||
|
date = weekDay(date, monday);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
date.setHours(16, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date = weekDay(date, tuesday);
|
||||||
|
date.setHours(1, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Descanso diario`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fail when the 9h enforced rest is fulfilled', async() => {
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setDate(date.getDate() - 2);
|
||||||
|
date = weekDay(date, monday);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
date.setHours(8, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
date.setHours(16, 0, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date = weekDay(date, tuesday);
|
||||||
|
date.setHours(1, 1, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeDefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for 72h weekly rest', () => {
|
||||||
|
|
||||||
|
it('should throw an error when work 11 consecutive days', async() => {
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setMonth(date.getMonth() - 1);
|
||||||
|
date.setDate(1);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||||
|
date = nextWeek(date);
|
||||||
|
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||||
|
try {
|
||||||
|
date = weekDay(date, friday);
|
||||||
|
date.setHours(10, 0, 1);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Descanso semanal`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when the 72h weekly rest is not fulfilled', async() => {
|
||||||
|
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setMonth(date.getMonth() - 1);
|
||||||
|
date.setDate(1);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||||
|
date = nextWeek(date);
|
||||||
|
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date = weekDay(date, sunday);
|
||||||
|
date.setHours(17, 59, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toBe(`Descanso semanal`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when the 72h weekly rest is fulfilled', async() => {
|
||||||
|
|
||||||
|
let date = Date.vnNew();
|
||||||
|
date.setMonth(date.getMonth() - 1);
|
||||||
|
date.setDate(1);
|
||||||
|
let error;
|
||||||
|
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
||||||
|
date = nextWeek(date);
|
||||||
|
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
date = weekDay(date, sunday);
|
||||||
|
date.setHours(18, 00, 0);
|
||||||
|
ctx.args = {timed: date, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).not.toBeDefined;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function weekDay(date, dayToSet) {
|
||||||
|
const currentDay = date.getDay();
|
||||||
|
const distance = dayToSet - currentDay;
|
||||||
|
|
||||||
|
date.setDate(date.getDate() + distance);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextWeek(date) {
|
||||||
|
const sunday = 7;
|
||||||
|
const currentDay = date.getDay();
|
||||||
|
let newDate = date;
|
||||||
|
if (currentDay != 0)
|
||||||
|
newDate = weekDay(date, sunday);
|
||||||
|
|
||||||
|
newDate.setDate(newDate.getDate() + 1);
|
||||||
|
return newDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) {
|
||||||
|
const dateStart = new Date(weekDay(date, dayStart));
|
||||||
|
const dateEnd = new Date(dateStart);
|
||||||
|
dateEnd.setDate(dateStart.getDate() + dayEnd);
|
||||||
|
|
||||||
|
for (let i = dayStart; i <= dayEnd; i++) {
|
||||||
|
dateStart.setHours(10, 0, 0);
|
||||||
|
ctx.args = {timed: dateStart, direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
dateStart.setHours(18, 0, 0);
|
||||||
|
ctx.args = {timed: dateStart, direction: 'out'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
dateStart.setDate(dateStart.getDate() + 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
describe('workerTimeControl getClockIn()', () => {
|
||||||
|
it('should correctly get the timetable of a worker', async() => {
|
||||||
|
const response = await models.WorkerTimeControl.getClockIn(1106, {});
|
||||||
|
|
||||||
|
expect(response.length).toEqual(4);
|
||||||
|
const [inHrs, middleOutHrs, middleInHrs, outHrs] = response;
|
||||||
|
|
||||||
|
expect(inHrs['0daysAgo']).toEqual('07:00');
|
||||||
|
expect(middleOutHrs['0daysAgo']).toEqual('10:00');
|
||||||
|
expect(middleInHrs['0daysAgo']).toEqual('10:20');
|
||||||
|
expect(outHrs['0daysAgo']).toEqual('14:50');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
describe('workerTimeControl login()', () => {
|
||||||
|
let ctx;
|
||||||
|
beforeAll(async() => {
|
||||||
|
ctx = {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
req: {
|
||||||
|
headers: {origin: 'http://localhost'},
|
||||||
|
__: key => key
|
||||||
|
}
|
||||||
|
};
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
|
active: ctx
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly login', async() => {
|
||||||
|
const response = await models.WorkerTimeControl.login(ctx, 9);
|
||||||
|
|
||||||
|
expect(response.name).toBe('developer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw UserError if pin is not provided', async() => {
|
||||||
|
try {
|
||||||
|
await models.WorkerTimeControl.login(ctx);
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(UserError);
|
||||||
|
expect(error.message).toBe('Incorrect pin');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,18 +3,10 @@ const models = require('vn-loopback/server/server').models;
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
describe('workerTimeControl add/delete timeEntry()', () => {
|
describe('workerTimeControl add/delete timeEntry()', () => {
|
||||||
const HHRRId = 37;
|
|
||||||
const teamBossId = 13;
|
|
||||||
const employeeId = 1;
|
const employeeId = 1;
|
||||||
const salesPersonId = 1106;
|
|
||||||
const salesBossId = 19;
|
const salesBossId = 19;
|
||||||
const hankPymId = 1107;
|
const hankPymId = 1107;
|
||||||
const jessicaJonesId = 1110;
|
|
||||||
const monday = 1;
|
const monday = 1;
|
||||||
const tuesday = 2;
|
|
||||||
const thursday = 4;
|
|
||||||
const friday = 5;
|
|
||||||
const sunday = 7;
|
|
||||||
const activeCtx = {
|
const activeCtx = {
|
||||||
accessToken: {userId: 50},
|
accessToken: {userId: 50},
|
||||||
};
|
};
|
||||||
|
@ -61,560 +53,11 @@ describe('workerTimeControl add/delete timeEntry()', () => {
|
||||||
expect(error.statusCode).toBe(400);
|
expect(error.statusCode).toBe(400);
|
||||||
expect(error.message).toBe(`You don't have enough privileges`);
|
expect(error.message).toBe(`You don't have enough privileges`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add if the current user is team boss and the target user is himself', async() => {
|
|
||||||
activeCtx.accessToken.userId = teamBossId;
|
|
||||||
const workerId = teamBossId;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
const todayAtOne = Date.vnNew();
|
|
||||||
todayAtOne.setHours(1, 0, 0, 0);
|
|
||||||
|
|
||||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
|
||||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
expect(createdTimeEntry.id).toBeDefined();
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should try but fail to delete his own time entry', async() => {
|
|
||||||
activeCtx.accessToken.userId = salesBossId;
|
|
||||||
const workerId = salesBossId;
|
|
||||||
|
|
||||||
let error;
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
const todayAtOne = Date.vnNew();
|
|
||||||
todayAtOne.setHours(1, 0, 0, 0);
|
|
||||||
|
|
||||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
|
||||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
activeCtx.accessToken.userId = salesPersonId;
|
|
||||||
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
await tx.rollback();
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
|
||||||
expect(error.statusCode).toBe(400);
|
|
||||||
expect(error.message).toBe(`You don't have enough privileges`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete the created time entry for the team boss as himself', async() => {
|
|
||||||
activeCtx.accessToken.userId = teamBossId;
|
|
||||||
const workerId = teamBossId;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
const todayAtOne = Date.vnNew();
|
|
||||||
todayAtOne.setHours(1, 0, 0, 0);
|
|
||||||
|
|
||||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
|
||||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
expect(createdTimeEntry.id).toBeDefined();
|
|
||||||
|
|
||||||
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
|
||||||
|
|
||||||
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
|
|
||||||
|
|
||||||
expect(deletedTimeEntry).toBeNull();
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete the created time entry for the team boss as HHRR', async() => {
|
|
||||||
activeCtx.accessToken.userId = HHRRId;
|
|
||||||
const workerId = teamBossId;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
const todayAtOne = Date.vnNew();
|
|
||||||
todayAtOne.setHours(1, 0, 0, 0);
|
|
||||||
|
|
||||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
|
||||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
expect(createdTimeEntry.id).toBeDefined();
|
|
||||||
|
|
||||||
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
|
|
||||||
|
|
||||||
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
|
|
||||||
|
|
||||||
expect(deletedTimeEntry).toBeNull();
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should edit the created time entry for the team boss as HHRR', async() => {
|
|
||||||
activeCtx.accessToken.userId = HHRRId;
|
|
||||||
const workerId = teamBossId;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
const todayAtOne = Date.vnNew();
|
|
||||||
todayAtOne.setHours(1, 0, 0, 0);
|
|
||||||
|
|
||||||
ctx.args = {timed: todayAtOne, direction: 'in'};
|
|
||||||
const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
expect(createdTimeEntry.id).toBeDefined();
|
|
||||||
|
|
||||||
ctx.args = {direction: 'out'};
|
|
||||||
const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options);
|
|
||||||
|
|
||||||
expect(updatedTimeEntry.direction).toEqual('out');
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('WorkerTimeControl_clockIn calls', () => {
|
describe('WorkerTimeControl_clockIn calls', () => {
|
||||||
let workerId;
|
beforeEach(() => activeCtx.accessToken.userId = salesBossId);
|
||||||
beforeEach(() => {
|
|
||||||
activeCtx.accessToken.userId = salesBossId;
|
|
||||||
workerId = hankPymId;
|
|
||||||
});
|
|
||||||
it('should fail to add a time entry if the target user has an absence that day', async() => {
|
|
||||||
const date = Date.vnNew();
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
date.setDate(date.getDate() - 16);
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
try {
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`No está permitido trabajar`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to add a time entry for a worker without an existing contract', async() => {
|
|
||||||
const date = Date.vnNew();
|
|
||||||
date.setFullYear(date.getFullYear() - 2);
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
try {
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`No hay un contrato en vigor`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail to add a time entry for a worker without an existing contract', async() => {
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setDate(date.getDate() - 2);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
date.setHours(0, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date.setHours(20,0, 1);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('direction errors', () => {
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setDate(date.getDate() - 1);
|
|
||||||
let error;
|
|
||||||
it('should throw an error when trying "in" direction twice', async() => {
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date.setHours(10, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Dirección incorrecta`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => {
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
date.setHours(9, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'middle'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date.setHours(10, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Dirección incorrecta`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should throw an error when trying "out" before closing a "middle" couple', async() => {
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
date.setHours(9, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'middle'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date.setHours(10, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Dirección incorrecta`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when trying "middle" after "out"', async() => {
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
date.setHours(9, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date.setHours(10, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'middle'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Dirección incorrecta`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when trying "out" direction twice', async() => {
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
date.setHours(9, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date.setHours(10, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Dirección incorrecta`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('12h rest', () => {
|
|
||||||
activeCtx.accessToken.userId = salesBossId;
|
|
||||||
const workerId = hankPymId;
|
|
||||||
it('should throw an error when the 12h rest is not fulfilled yet', async() => {
|
|
||||||
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setDate(date.getDate() - 2);
|
|
||||||
date = weekDay(date, monday);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
date.setHours(16, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date = weekDay(date, tuesday);
|
|
||||||
date.setHours(4, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Descanso diario`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not fail as the 12h rest is fulfilled', async() => {
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setDate(date.getDate() - 2);
|
|
||||||
date = weekDay(date, monday);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
date.setHours(16, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date = weekDay(date, tuesday);
|
|
||||||
date.setHours(4, 1, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).not.toBeDefined;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('for 3500kg drivers with enforced 9h rest', () => {
|
|
||||||
activeCtx.accessToken.userId = salesBossId;
|
|
||||||
const workerId = jessicaJonesId;
|
|
||||||
it('should throw an error when the 9h enforced rest is not fulfilled', async() => {
|
|
||||||
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setDate(date.getDate() - 2);
|
|
||||||
date = weekDay(date, monday);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
date.setHours(16, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date = weekDay(date, tuesday);
|
|
||||||
date.setHours(1, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Descanso diario`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not fail when the 9h enforced rest is fulfilled', async() => {
|
|
||||||
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setDate(date.getDate() - 2);
|
|
||||||
date = weekDay(date, monday);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
date.setHours(8, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
date.setHours(16, 0, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'out'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date = weekDay(date, tuesday);
|
|
||||||
date.setHours(1, 1, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).not.toBeDefined;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('for 72h weekly rest', () => {
|
|
||||||
|
|
||||||
it('should throw an error when work 11 consecutive days', async() => {
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setMonth(date.getMonth() - 1);
|
|
||||||
date.setDate(1);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
|
||||||
date = nextWeek(date);
|
|
||||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
|
||||||
try {
|
|
||||||
date = weekDay(date, friday);
|
|
||||||
date.setHours(10, 0, 1);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Descanso semanal`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when the 72h weekly rest is not fulfilled', async() => {
|
|
||||||
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setMonth(date.getMonth() - 1);
|
|
||||||
date.setDate(1);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
|
||||||
date = nextWeek(date);
|
|
||||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date = weekDay(date, sunday);
|
|
||||||
date.setHours(17, 59, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toBe(`Descanso semanal`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when the 72h weekly rest is fulfilled', async() => {
|
|
||||||
|
|
||||||
let date = Date.vnNew();
|
|
||||||
date.setMonth(date.getMonth() - 1);
|
|
||||||
date.setDate(1);
|
|
||||||
let error;
|
|
||||||
|
|
||||||
const tx = await models.WorkerTimeControl.beginTransaction({});
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
await populateWeek(date, monday, sunday, ctx, workerId, options);
|
|
||||||
date = nextWeek(date);
|
|
||||||
await populateWeek(date, monday, thursday, ctx, workerId, options);
|
|
||||||
|
|
||||||
try {
|
|
||||||
date = weekDay(date, sunday);
|
|
||||||
date.setHours(18, 00, 0);
|
|
||||||
ctx.args = {timed: date, direction: 'in'};
|
|
||||||
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).not.toBeDefined;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('WorkerTimeControl_calculate calls', () => {
|
describe('WorkerTimeControl_calculate calls', () => {
|
||||||
let dated = Date.vnNew();
|
let dated = Date.vnNew();
|
||||||
dated.setDate(dated.getDate() - 7);
|
dated.setDate(dated.getDate() - 7);
|
||||||
|
@ -836,25 +279,6 @@ function weekDay(date, dayToSet) {
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextWeek(date) {
|
|
||||||
const sunday = 7;
|
|
||||||
const currentDay = date.getDay();
|
|
||||||
let newDate = date;
|
|
||||||
if (currentDay != 0)
|
|
||||||
newDate = weekDay(date, sunday);
|
|
||||||
|
|
||||||
newDate.setDate(newDate.getDate() + 1);
|
|
||||||
return newDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lastWeek(date) {
|
|
||||||
const monday = 1;
|
|
||||||
newDate = weekDay(date, monday);
|
|
||||||
|
|
||||||
newDate.setDate(newDate.getDate() - 1);
|
|
||||||
return newDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) {
|
async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) {
|
||||||
const dateStart = new Date(weekDay(date, dayStart));
|
const dateStart = new Date(weekDay(date, dayStart));
|
||||||
const dateEnd = new Date(dateStart);
|
const dateEnd = new Date(dateStart);
|
||||||
|
|
|
@ -10,6 +10,9 @@ module.exports = Self => {
|
||||||
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
|
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
|
||||||
require('../methods/worker-time-control/getMailStates')(Self);
|
require('../methods/worker-time-control/getMailStates')(Self);
|
||||||
require('../methods/worker-time-control/resendWeeklyHourEmail')(Self);
|
require('../methods/worker-time-control/resendWeeklyHourEmail')(Self);
|
||||||
|
require('../methods/worker-time-control/login')(Self);
|
||||||
|
require('../methods/worker-time-control/getClockIn')(Self);
|
||||||
|
require('../methods/worker-time-control/clockIn')(Self);
|
||||||
|
|
||||||
Self.rewriteDbError(function(err) {
|
Self.rewriteDbError(function(err) {
|
||||||
if (err.code === 'ER_DUP_ENTRY')
|
if (err.code === 'ER_DUP_ENTRY')
|
||||||
|
|
Loading…
Reference in New Issue