#6274 workerTimeControl #1858

Merged
jorgep merged 31 commits from 6274-loginWorkerTimeControl into dev 2024-01-03 11:31:52 +00:00
15 changed files with 804 additions and 627 deletions

View File

@ -1,14 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
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 => {
Self.remoteMethodCtx('renewToken', {
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;
// 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;
const {courtesyTime} = await models.AccessTokenConfig.findOne({fields: ['courtesyTime']});
// 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
const user = await Self.findById(token.userId);
@ -43,14 +46,4 @@ module.exports = Self => {
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;
}
};

View File

@ -95,27 +95,30 @@
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
}, {
"property": "recoverPassword",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
}, {
"property": "validateAuth",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
}, {
"property": "privileges",
"accessType": "*",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}, {
"property": "renewToken",
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
],
"scopes": {

View File

@ -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
jorgep marked this conversation as resolved Outdated
Outdated
Review

el acl sobre renewToken ya debería estar definido para $owner, si no es asi definirlo

el acl sobre renewToken ya debería estar definido para $owner, si no es asi definirlo

@juan podemos mirarlo?

@juan podemos mirarlo?
('WorkerTimeControl', 'login', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
('WorkerTimeControl', 'getClockIn', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
('WorkerTimeControl', 'clockIn', 'WRITE', 'ALLOW', 'ROLE', 'timeControl');
CALL `account`.`role_sync`();

View File

@ -200,5 +200,6 @@
"Try again": "Try again",
"keepPrice": "keepPrice",
"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."
}

View File

@ -331,7 +331,7 @@
"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}}",
"This user does not have an assigned tablet": "Este usuario no tiene tablet asignada",
"Incorrect pin": "Pin incorrecto.",
jorgep marked this conversation as resolved Outdated

ayer no hablamos de no poner puntos? son estos casos?

ayer no hablamos de no poner puntos? son estos casos?

cierto

cierto
"You already have the mailAlias": "Ya tienes este alias de correo",
"The alias cant be modified": "Este alias de correo no puede ser modificado"
}

View File

@ -43,16 +43,9 @@ module.exports = Self => {
const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
const isHimself = userId == workerId;
if (!isSubordinate || (isSubordinate && isHimself && !isTeamBoss))
if (!isSubordinate || (isHimself && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
query = `CALL vn.workerTimeControl_clockIn(?,?,?)`;
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;
return Self.clockIn(workerId, args.timed, args.direction, myOptions);
};
};

View File

@ -0,0 +1,45 @@
const UserError = require('vn-loopback/util/user-error');
jorgep marked this conversation as resolved Outdated

veo que existe un archivo muy similar que llama al mismo procedimiento, lo has tenido en cuenta? conviene juntarlo en uno?
modules/worker/back/methods/worker-time-control/addTimeEntry.js

veo que existe un archivo muy similar que llama al mismo procedimiento, lo has tenido en cuenta? conviene juntarlo en uno? modules/worker/back/methods/worker-time-control/addTimeEntry.js

Ya se ha implementado, tenemos que mirarlo juntos conforme hemos hablado por rocket.

Ya se ha implementado, tenemos que mirarlo juntos conforme hemos hablado por rocket.

Refactor aplicado tras revisión en persona.

Refactor aplicado tras revisión en persona.
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);
jorgep marked this conversation as resolved Outdated
Outdated
Review

Si es el caso puedes hacer:

const myArray = [[{}]]
const [[myNewArray]] = myArray
console.log(myNewArray) // Devuelve {}

Es decir, hacer const [[response]] = y te evitas luego hacer response[0]

Si es el caso puedes hacer: ``` const myArray = [[{}]] const [[myNewArray]] = myArray console.log(myNewArray) // Devuelve {} ``` Es decir, hacer `const [[response]] =` y te evitas luego hacer response[0]
if (response && response.error)
throw new UserError(response.error);
return response;
};
};

View File

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

View File

@ -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'));
jorgep marked this conversation as resolved Outdated
Outdated
Review

Falta traduccion

Falta traduccion
return user;
};
};

View File

@ -1,6 +1,6 @@
module.exports = Self => {
Self.remoteMethodCtx('resendWeeklyHourEmail', {
description: 'Adds a new hour registry',
description: 'Send the records for the week of the date provided',
accessType: 'WRITE',
accepts: [{
arg: 'id',

View File

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

View File

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

View File

@ -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;
jorgep marked this conversation as resolved Outdated
Outdated
Review

No hace falta pasar el objeto

No hace falta pasar el objeto
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');
}
});
});

View File

@ -3,18 +3,10 @@ const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('workerTimeControl add/delete timeEntry()', () => {
const HHRRId = 37;
const teamBossId = 13;
const employeeId = 1;
const salesPersonId = 1106;
const salesBossId = 19;
const hankPymId = 1107;
const jessicaJonesId = 1110;
const monday = 1;
const tuesday = 2;
const thursday = 4;
const friday = 5;
const sunday = 7;
const activeCtx = {
accessToken: {userId: 50},
};
@ -61,559 +53,10 @@ describe('workerTimeControl add/delete timeEntry()', () => {
expect(error.statusCode).toBe(400);
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', () => {
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', 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;
});
});
beforeEach(() => activeCtx.accessToken.userId = salesBossId);
describe('WorkerTimeControl_calculate calls', () => {
let dated = Date.vnNew();
@ -836,25 +279,6 @@ function weekDay(date, dayToSet) {
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) {
const dateStart = new Date(weekDay(date, dayStart));
const dateEnd = new Date(dateStart);

View File

@ -10,6 +10,9 @@ module.exports = Self => {
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
require('../methods/worker-time-control/getMailStates')(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) {
if (err.code === 'ER_DUP_ENTRY')