Merge branch 'dev' into 6366-unifyTicketChangeState
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Pablo Natek 2024-01-03 18:15:29 +00:00
commit b42aed13c4
32 changed files with 991 additions and 680 deletions

View File

@ -31,7 +31,7 @@ describe('docuware upload()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const user = await models.UserConfig.findById(userId, null, options); const user = await models.UserConfig.findById(userId, null, options);
await user.updateAttribute('tabletFk', 'Tablet1'); await user.updateAttribute('tabletFk', 'Tablet1', options);
await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options); await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options);
await tx.rollback(); await tx.rollback();

View File

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

View File

@ -30,7 +30,6 @@ describe('Renew Token', () => {
it('should renew token', async() => { it('should renew token', async() => {
const mockDate = new Date(startingTime + 26600000); const mockDate = new Date(startingTime + 26600000);
jasmine.clock().mockDate(mockDate); jasmine.clock().mockDate(mockDate);
console.log(startingTime, mockDate)
const {id} = await models.VnUser.renewToken(ctx); const {id} = await models.VnUser.renewToken(ctx);
expect(id).not.toEqual(ctx.req.accessToken.id); expect(id).not.toEqual(ctx.req.accessToken.id);

View File

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

View File

@ -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": {

View File

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

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
('WorkerTimeControl', 'login', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
('WorkerTimeControl', 'getClockIn', 'READ', 'ALLOW', 'ROLE', 'timeControl'),
('WorkerTimeControl', 'clockIn', 'WRITE', 'ALLOW', 'ROLE', 'timeControl');
CALL `account`.`role_sync`();

View File

@ -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'),

View File

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

View File

@ -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."
} }

View File

@ -331,5 +331,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"
}

View File

@ -14,6 +14,9 @@
"MailAliasAccount": { "MailAliasAccount": {
"dataSource": "vn" "dataSource": "vn"
}, },
"MailAliasAcl": {
"dataSource": "vn"
},
"MailConfig": { "MailConfig": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

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

View File

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

View File

@ -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.*

View File

@ -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',

View File

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

View File

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

View File

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

View File

@ -20,9 +20,6 @@
}, },
"stateFk": { "stateFk": {
"type": "number" "type": "number"
},
"userFk": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

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

View File

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

View File

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

View File

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

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

View File

@ -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',

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

View File

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