6745-2404_testToMaster #1950

Merged
alexm merged 191 commits from 6745-2404_testToMaster into master 2024-01-25 07:39:13 +00:00
74 changed files with 1363 additions and 920 deletions
Showing only changes of commit 2fb60aeed3 - Show all commits

View File

@ -3,7 +3,7 @@
// Carácter predeterminado de final de línea. // Carácter predeterminado de final de línea.
"files.eol": "\n", "files.eol": "\n",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": "explicit"
}, },
"search.useIgnoreFiles": false, "search.useIgnoreFiles": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint", "editor.defaultFormatter": "dbaeumer.vscode-eslint",

View File

@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2352.01] - 2023-12-28 ## [2402.01] - 2024-01-11
### Added
### Changed
### Fixed
## [2400.01] - 2024-01-04
### Added ### Added
### Changed ### Changed
@ -13,9 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2350.01] - 2023-12-14 ## [2350.01] - 2023-12-14
### Added ### Características Añadidas 🆕
### Changed - **Tickets → Expediciones:** Añadido soporte para Viaexpress
### Fixed
## [2348.01] - 2023-11-30 ## [2348.01] - 2023-11-30

View File

@ -22,8 +22,8 @@ module.exports = Self => {
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);

View File

@ -24,15 +24,40 @@ describe('docuware upload()', () => {
}); });
it('should try upload file', async() => { it('should try upload file', async() => {
const tx = await models.Docuware.beginTransaction({});
spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({}))); spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({})));
let error; let error;
try { try {
await models.Docuware.upload(ctx, ticketIds, fileCabinetName); const options = {transaction: tx};
const user = await models.UserConfig.findById(userId, null, options);
await user.updateAttribute('tabletFk', 'Tablet1', options);
await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options);
await tx.rollback();
} catch (e) { } catch (e) {
error = e.message; error = e;
await tx.rollback();
} }
expect(error).toEqual('Action not allowed on the test environment'); expect(error.message).toEqual('Action not allowed on the test environment');
});
it('should throw error when not have tablet assigned', async() => {
const tx = await models.Docuware.beginTransaction({});
spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({})));
let error;
try {
const options = {transaction: tx};
await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.message).toEqual('This user does not have an assigned tablet');
}); });
}); });

View File

@ -29,12 +29,24 @@ module.exports = Self => {
} }
}); });
Self.upload = async function(ctx, ticketIds, fileCabinet) { Self.upload = async function(ctx, ticketIds, fileCabinet, options) {
delete ctx.args.ticketIds; delete ctx.args.ticketIds;
const models = Self.app.models; const models = Self.app.models;
const action = 'store'; const action = 'store';
const options = await Self.getOptions(); const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const userConfig = await models.UserConfig.findById(ctx.req.accessToken.userId, {
fields: ['tabletFk']
}, myOptions);
if (!userConfig?.tabletFk)
throw new UserError('This user does not have an assigned tablet');
const docuwareOptions = await Self.getOptions();
const fileCabinetId = await Self.getFileCabinet(fileCabinet); const fileCabinetId = await Self.getFileCabinet(fileCabinet);
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId); const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
@ -45,7 +57,7 @@ module.exports = Self => {
const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, {
id, id,
type: 'deliveryNote' type: 'deliveryNote'
}); }, myOptions);
// get ticket data // get ticket data
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {
include: [{ include: [{
@ -54,7 +66,7 @@ module.exports = Self => {
fields: ['id', 'name', 'fi'] fields: ['id', 'name', 'fi']
} }
}] }]
}); }, myOptions);
// upload file // upload file
const templateJson = { const templateJson = {
@ -102,7 +114,7 @@ module.exports = Self => {
{ {
'FieldName': 'FILTRO_TABLET', 'FieldName': 'FILTRO_TABLET',
'ItemElementName': 'string', 'ItemElementName': 'string',
'Item': 'Tablet1', 'Item': userConfig.tabletFk,
} }
] ]
}; };
@ -116,11 +128,11 @@ module.exports = Self => {
const deleteJson = { const deleteJson = {
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}] 'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
}; };
const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`; const deleteUri = `${docuwareOptions.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`;
await axios.put(deleteUri, deleteJson, options.headers); await axios.put(deleteUri, deleteJson, docuwareOptions.headers);
} }
const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; const uploadUri = `${docuwareOptions.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`;
const FormData = require('form-data'); const FormData = require('form-data');
const data = new FormData(); const data = new FormData();
@ -130,7 +142,7 @@ module.exports = Self => {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
'X-File-ModifiedDate': Date.vnNew(), 'X-File-ModifiedDate': Date.vnNew(),
'Cookie': options.headers.headers.Cookie, 'Cookie': docuwareOptions.headers.headers.Cookie,
...data.getHeaders() ...data.getHeaders()
}, },
}; };
@ -141,11 +153,11 @@ module.exports = Self => {
const $t = ctx.req.__; const $t = ctx.req.__;
const message = $t('Failed to upload delivery note', {id}); const message = $t('Failed to upload delivery note', {id});
if (uploaded.length) if (uploaded.length)
await models.TicketTracking.setDelivered(ctx, uploaded); await models.TicketTracking.setDelivered(ctx, uploaded, myOptions);
throw new UserError(message); throw new UserError(message);
} }
uploaded.push(id); uploaded.push(id);
} }
return models.TicketTracking.setDelivered(ctx, ticketIds); return models.TicketTracking.setDelivered(ctx, ticketIds, myOptions);
}; };
}; };

View File

@ -1,4 +1,4 @@
const UserError = require('vn-loopback/util/user-error'); const {models} = require('vn-loopback/server/server');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('renewToken', { Self.remoteMethodCtx('renewToken', {
@ -16,20 +16,31 @@ module.exports = Self => {
}); });
Self.renewToken = async function(ctx) { Self.renewToken = async function(ctx) {
const models = Self.app.models; const {accessToken: token} = ctx.req;
const token = ctx.req.accessToken;
const now = new Date(); // Check if current token is valid
const {renewPeriod, courtesyTime} = await models.AccessTokenConfig.findOne({
fields: ['renewPeriod', 'courtesyTime']
});
const now = Date.now();
const differenceMilliseconds = now - token.created; const differenceMilliseconds = now - token.created;
const differenceSeconds = Math.floor(differenceMilliseconds / 1000); const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
const isNotExceeded = differenceSeconds < renewPeriod - courtesyTime;
if (isNotExceeded)
return token;
const fields = ['renewPeriod', 'courtesyTime']; // Schedule to remove current token
const accessTokenConfig = await models.AccessTokenConfig.findOne({fields}); setTimeout(async() => {
try {
await Self.logout(token.id);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
}, courtesyTime * 1000);
if (differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime) // Create new accessToken
throw new UserError(`The renew period has not been exceeded`, 'periodNotExceeded');
await Self.logout(token.id);
const user = await Self.findById(token.userId); const user = await Self.findById(token.userId);
const accessToken = await user.createAccessToken(); const accessToken = await user.createAccessToken();

View File

@ -0,0 +1,50 @@
const {models} = require('vn-loopback/server/server');
describe('Renew Token', () => {
const startingTime = Date.now();
let ctx = null;
beforeAll(async() => {
const unAuthCtx = {
req: {
headers: {},
connection: {
remoteAddress: '127.0.0.1'
},
getLocale: () => 'en'
},
args: {}
};
let login = await models.VnUser.signIn(unAuthCtx, 'salesAssistant', 'nightmare');
let accessToken = await models.AccessToken.findById(login.token);
ctx = {req: {accessToken: accessToken}};
});
beforeEach(() => {
jasmine.clock().install();
jasmine.clock().mockDate(new Date(startingTime));
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should renew token', async() => {
const mockDate = new Date(startingTime + 26600000);
jasmine.clock().mockDate(mockDate);
const {id} = await models.VnUser.renewToken(ctx);
expect(id).not.toEqual(ctx.req.accessToken.id);
});
it('NOT should renew', async() => {
let error;
let response;
try {
response = await models.VnUser.renewToken(ctx);
} catch (e) {
error = e;
}
expect(error).toBeUndefined();
expect(response.id).toEqual(ctx.req.accessToken.id);
});
});

View File

@ -20,10 +20,7 @@ describe('VnUser Sign-in()', () => {
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
let signInLog = await SignInLog.find({where: {token: accessToken.id}}); let signInLog = await SignInLog.find({where: {token: accessToken.id}});
expect(signInLog.length).toEqual(1); expect(signInLog.length).toEqual(0);
expect(signInLog[0].userFk).toEqual(accessToken.userId);
expect(signInLog[0].owner).toEqual(true);
expect(login.token).toBeDefined();
await VnUser.logout(ctx.req.accessToken.id); await VnUser.logout(ctx.req.accessToken.id);
}); });

View File

@ -1,17 +0,0 @@
module.exports = Self => {
Self.remoteMethod('validateToken', {
description: 'Validates the current logged user token',
returns: {
type: 'Boolean',
root: true
},
http: {
path: `/validateToken`,
verb: 'GET'
}
});
Self.validateToken = async function() {
return true;
};
};

View File

@ -0,0 +1,17 @@
{
"name": "docuwareTablet",
"base": "VnModel",
"options": {
"mysql": {
"table": "docuwareTablet"
}
},
"properties": {
"tablet": {
"type": "string"
},
"description": {
"type": "string"
}
}
}

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

@ -26,6 +26,9 @@
}, },
"darkMode": { "darkMode": {
"type": "boolean" "type": "boolean"
},
"tabletFk": {
"type": "string"
} }
}, },
"relations": { "relations": {
@ -43,6 +46,11 @@
"type": "belongsTo", "type": "belongsTo",
"model": "VnUser", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} },
"Tablet": {
"type": "belongsTo",
"model": "docuwareTablet",
"foreignKey": "tabletFk"
}
} }
} }

View File

@ -10,7 +10,6 @@ module.exports = function(Self) {
require('../methods/vn-user/sign-in')(Self); require('../methods/vn-user/sign-in')(Self);
require('../methods/vn-user/acl')(Self); require('../methods/vn-user/acl')(Self);
require('../methods/vn-user/recover-password')(Self); require('../methods/vn-user/recover-password')(Self);
require('../methods/vn-user/validate-token')(Self);
require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/privileges')(Self);
require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/validate-auth')(Self);
require('../methods/vn-user/renew-token')(Self); require('../methods/vn-user/renew-token')(Self);
@ -135,15 +134,16 @@ module.exports = function(Self) {
Self.signInValidate = async(user, userToken, token, ctx) => { Self.signInValidate = async(user, userToken, token, ctx) => {
const [[key, value]] = Object.entries(Self.userUses(user)); const [[key, value]] = Object.entries(Self.userUses(user));
const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]); const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]);
await Self.app.models.SignInLog.create({ if (!isOwner) {
userName: user, await Self.app.models.SignInLog.create({
token: token.id, userName: user,
userFk: userToken.id, token: token.id,
ip: ctx.req.ip, userFk: userToken.id,
owner: isOwner ip: ctx.req.ip,
}); owner: isOwner
if (!isOwner) });
throw new UserError('Try again'); throw new UserError('Try again');
}
}; };
/** /**

View File

@ -95,34 +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": "validateToken",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"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,4 @@
-- Auto-generated SQL script #202311061003
UPDATE salix.accessTokenConfig
SET courtesyTime=60
WHERE id=1;

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

@ -1,4 +1,4 @@
RENAME TABLE `vn`.`clientCreditLimit` TO `vn`.`roleCreditLimit`; RENAME TABLE `vn`.`clientCreditLimit` TO `vn`.`roleCreditLimit`;
ALTER TABLE `vn`.`clientCreditLimit` DROP FOREIGN KEY `clientCreditLimit_FK`; ALTER TABLE `vn`.`roleCreditLimit` DROP FOREIGN KEY `clientCreditLimit_FK`;
ALTER TABLE `vn`.`roleCreditLimit` ADD CONSTRAINT `roleCreditLimit_FK` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `vn`.`roleCreditLimit` ADD CONSTRAINT `roleCreditLimit_FK` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1 @@
DELETE FROM `account`.`signInLog` where owner <> FALSE

View File

@ -1,9 +1,12 @@
CREATE SCHEMA IF NOT EXISTS `vn2008`;
USE `vn`;
CREATE OR REPLACE DEFINER=`root`@`localhost` CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER SQL SECURITY DEFINER
VIEW `vn`.`ticketState` VIEW `ticketState`
AS SELECT `tt`.`created` AS `updated`, AS SELECT `tt`.`created` AS `updated`,
`tt`.`stateFk` AS `stateFk`, `tt`.`stateFk` AS `stateFk`,
`tt`.`userFk` AS `userFk`, `tt`.`userFk` AS `workerFk`,
`tls`.`ticketFk` AS `ticketFk`, `tls`.`ticketFk` AS `ticketFk`,
`s`.`id` AS `state`, `s`.`id` AS `state`,
`s`.`order` AS `productionOrder`, `s`.`order` AS `productionOrder`,
@ -15,10 +18,10 @@ AS SELECT `tt`.`created` AS `updated`,
`s`.`isPicked` AS `isPicked` `s`.`isPicked` AS `isPicked`
FROM ( FROM (
( (
`vn`.`ticketLastState` `tls` `ticketLastState` `tls`
JOIN `vn`.`ticketTracking` `tt` ON(`tt`.`id` = `tls`.`ticketTrackingFk`) JOIN `ticketTracking` `tt` ON(`tt`.`id` = `tls`.`ticketTrackingFk`)
) )
JOIN `vn`.`state` `s` ON(`s`.`id` = `tt`.`stateFk`) JOIN `state` `s` ON(`s`.`id` = `tt`.`stateFk`)
); );
CREATE OR REPLACE DEFINER=`root`@`localhost` CREATE OR REPLACE DEFINER=`root`@`localhost`
@ -33,14 +36,15 @@ AS SELECT `tt`.`id` AS `inter_id`,
`tt`.`supervisorFk` AS `Id_supervisor` `tt`.`supervisorFk` AS `Id_supervisor`
FROM `vn`.`ticketTracking` `tt`; FROM `vn`.`ticketTracking` `tt`;
CREATE OR REPLACE CREATE OR REPLACE DEFINER=`root`@`localhost`
ALGORITHM = UNDEFINED VIEW `ticketStateToday` AS SQL SECURITY DEFINER
SELECT VIEW `ticketStateToday`
AS SELECT
`ts`.`ticket` AS `ticket`, `ts`.`ticket` AS `ticket`,
`ts`.`state` AS `state`, `ts`.`state` AS `state`,
`ts`.`productionOrder` AS `productionOrder`, `ts`.`productionOrder` AS `productionOrder`,
`ts`.`alertLevel` AS `alertLevel`, `ts`.`alertLevel` AS `alertLevel`,
`ts`.`userFk` AS `userFk`, `ts`.`worker` AS `worker`,
`ts`.`code` AS `code`, `ts`.`code` AS `code`,
`ts`.`updated` AS `updated`, `ts`.`updated` AS `updated`,
`ts`.`isPicked` AS `isPicked` `ts`.`isPicked` AS `isPicked`

View File

@ -0,0 +1,10 @@
-- vn.docuwareTablet definition
CREATE TABLE `vn`.`docuwareTablet` (
`tablet` varchar(100) NOT NULL PRIMARY KEY,
`description` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
ALTER TABLE `vn`.`userConfig`
ADD COLUMN tabletFk varchar(100) DEFAULT NULL,
ADD FOREIGN KEY (tabletFk) REFERENCES `vn`.`docuwareTablet`(tablet);

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

@ -2967,9 +2967,9 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
(2, 1, 50, 2), (2, 1, 50, 2),
(3, 1, 0, 3); (3, 1, 0, 3);
INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`) INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `courtesyTime`, `renewInterval`)
VALUES VALUES
(1, 21600, 300); (1, 21600, 60, 300);
INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`) INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`)
VALUES VALUES
@ -3009,3 +3009,14 @@ INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`)
(1, 'Error in VAT calculation'), (1, 'Error in VAT calculation'),
(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`)
VALUES
('Tablet1','Jarvis tablet'),
('Tablet2','Avengers tablet');

View File

@ -26391,6 +26391,7 @@ CREATE TABLE `cplusCorrectingType` (
) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; ) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
-- Table structure for table `cplusRectificationType` -- Table structure for table `cplusRectificationType`
-- --

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

@ -82,7 +82,7 @@ export default class Token {
if (!data) return; if (!data) return;
this.renewPeriod = data.renewPeriod; this.renewPeriod = data.renewPeriod;
this.stopRenewer(); this.stopRenewer();
this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000); this.intervalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000);
}); });
} }
@ -103,17 +103,13 @@ export default class Token {
const token = res.data; const token = res.data;
this.set(token.id, now, token.ttl, this.remember); this.set(token.id, now, token.ttl, this.remember);
}) })
.catch(res => {
if (res.data?.error?.code !== 'periodNotExceeded')
throw res;
})
.finally(() => { .finally(() => {
this.checking = false; this.checking = false;
}); });
} }
stopRenewer() { stopRenewer() {
clearInterval(this.inservalId); clearInterval(this.intervalId);
} }
} }
Token.$inject = ['vnInterceptor', '$http', '$rootScope']; Token.$inject = ['vnInterceptor', '$http', '$rootScope'];

View File

@ -18,7 +18,7 @@ Show summary: Mostrar vista previa
What is new: Novedades de la versión What is new: Novedades de la versión
Settings: Ajustes Settings: Ajustes
There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar
This ticket is locked.: Este ticket está bloqueado This ticket is locked: Este ticket está bloqueado
# Actions # Actions

View File

@ -183,7 +183,7 @@
"Social name should be uppercase": "Social name should be uppercase", "Social name should be uppercase": "Social name should be uppercase",
"Street should be uppercase": "Street should be uppercase", "Street should be uppercase": "Street should be uppercase",
"You don't have enough privileges.": "You don't have enough privileges.", "You don't have enough privileges.": "You don't have enough privileges.",
"This ticket is locked.": "This ticket is locked.", "This ticket is locked": "This ticket is locked",
"This ticket is not editable.": "This ticket is not editable.", "This ticket is not editable.": "This ticket is not editable.",
"The ticket doesn't exist.": "The ticket doesn't exist.", "The ticket doesn't exist.": "The ticket doesn't exist.",
"The sales do not exists": "The sales do not exists", "The sales do not exists": "The sales do not exists",
@ -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

@ -312,7 +312,7 @@
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado",
"You don't have enough privileges.": "No tienes suficientes permisos.", "You don't have enough privileges.": "No tienes suficientes permisos.",
"This ticket is locked.": "Este ticket está bloqueado.", "This ticket is locked": "Este ticket está bloqueado.",
"This ticket is not editable.": "Este ticket no es editable.", "This ticket is not editable.": "Este ticket no es editable.",
"The ticket doesn't exist.": "No existe el ticket.", "The ticket doesn't exist.": "No existe el ticket.",
"Social name should be uppercase": "La razón social debe ir en mayúscula", "Social name should be uppercase": "La razón social debe ir en mayúscula",
@ -329,5 +329,9 @@
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima", "The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima",
"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",
"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

@ -1,6 +1,5 @@
module.exports = () => { module.exports = () => {
Date.vnUTC = () => { Date.vnUTC = (env = process.env.NODE_ENV) => {
const env = process.env.NODE_ENV;
if (!env || env === 'development') if (!env || env === 'development')
return new Date(Date.UTC(2001, 0, 1, 11)); return new Date(Date.UTC(2001, 0, 1, 11));

View File

@ -2,7 +2,7 @@ const {models} = require('vn-loopback/server/server');
describe('account changePassword()', () => { describe('account changePassword()', () => {
const userId = 70; const userId = 70;
const unauthCtx = { const unAuthCtx = {
req: { req: {
headers: {}, headers: {},
connection: { connection: {
@ -79,7 +79,7 @@ describe('account changePassword()', () => {
passExpired: yesterday passExpired: yesterday
} }
, options); , options);
await models.VnUser.signIn(unauthCtx, 'trainee', 'nightmare', options); await models.VnUser.signIn(unAuthCtx, 'trainee', 'nightmare', options);
} catch (e) { } catch (e) {
if (e.message != 'Pass expired') if (e.message != 'Pass expired')
throw e; throw e;

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

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('removeFile', { Self.remoteMethodCtx('removeFile', {
description: 'Removes a claim document', description: 'Removes a claim document',
@ -19,8 +21,8 @@ module.exports = Self => {
}); });
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -31,19 +33,18 @@ module.exports = Self => {
} }
try { try {
const models = Self.app.models; const claimDms = await Self.findById(id, null, myOptions);
const targetClaimDms = await models.ClaimDms.findById(id, null, myOptions);
const targetDms = await models.Dms.findById(targetClaimDms.dmsFk, null, myOptions);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions);
await models.Dms.removeFile(ctx, targetClaimDms.dmsFk, myOptions); const targetDms = await Self.app.models.Dms.removeFile(ctx, claimDms.dmsFk, myOptions);
await targetClaimDms.destroy(myOptions);
await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions); if (!targetDms || ! claimDms)
throw new UserError('Try again');
const claimDmsDestroyed = await claimDms.destroy(myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return targetDms; return claimDmsDestroyed;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -1,6 +1,3 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
const path = require('path');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('uploadFile', { Self.remoteMethodCtx('uploadFile', {
@ -57,96 +54,33 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id, options) => { Self.uploadFile = async(ctx, id, options) => {
const tx = await Self.beginTransaction({}); const {Dms, ClaimDms} = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
if (!myOptions.transaction) if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx; myOptions.transaction = tx;
}
const models = Self.app.models;
const promises = [];
const TempContainer = models.TempContainer;
const ClaimContainer = models.ClaimContainer;
const fileOptions = {};
const args = ctx.args;
let srcFile;
try { try {
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions); const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
// Upload file to temporary path const promises = uploadedFiles.map(dms => ClaimDms.create({
const tempContainer = await TempContainer.container('dms'); claimFk: id,
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); dmsFk: dms.id
const files = Object.values(uploaded.files).map(file => { }, myOptions));
return file[0]; await Promise.all(promises);
});
const addedDms = [];
for (const uploadedFile of files) {
const newDms = await createDms(ctx, uploadedFile, myOptions);
const pathHash = ClaimContainer.getHash(newDms.id);
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
srcFile = path.join(file.client.root, file.container, file.name);
const claimContainer = await ClaimContainer.container(pathHash);
const dstFile = path.join(claimContainer.client.root, pathHash, newDms.file);
await fs.move(srcFile, dstFile, {
overwrite: true
});
addedDms.push(newDms);
}
addedDms.forEach(dms => {
const newClaimDms = models.ClaimDms.create({
claimFk: id,
dmsFk: dms.id
}, myOptions);
promises.push(newClaimDms);
});
const resolvedPromises = await Promise.all(promises);
if (tx) await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return uploadedFiles;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
if (fs.existsSync(srcFile))
await fs.unlink(srcFile);
throw e; throw e;
} }
}; };
async function createDms(ctx, file, myOptions) {
const models = Self.app.models;
const myUserId = ctx.req.accessToken.userId;
const args = ctx.args;
const newDms = await models.Dms.create({
workerFk: myUserId,
dmsTypeFk: args.dmsTypeId,
companyFk: args.companyId,
warehouseFk: args.warehouseId,
reference: args.reference,
description: args.description,
contentType: file.type,
hasFile: args.hasFile
}, myOptions);
let fileName = file.name;
const extension = models.DmsContainer.getFileExtension(fileName);
fileName = `${newDms.id}.${extension}`;
return newDms.updateAttribute('file', fileName, myOptions);
}
}; };

View File

@ -19,9 +19,8 @@ module.exports = Self => {
}); });
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models;
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -34,13 +33,16 @@ module.exports = Self => {
try { try {
const clientDms = await Self.findById(id, null, myOptions); const clientDms = await Self.findById(id, null, myOptions);
await models.Dms.removeFile(ctx, clientDms.dmsFk, myOptions); const targetDms = await Self.app.models.Dms.removeFile(ctx, clientDms.dmsFk, myOptions);
const destroyedClient = await clientDms.destroy(myOptions); if (!targetDms || !clientDms)
throw new UserError('Try again');
const clientDmsDestroyed = await clientDms.destroy(myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return destroyedClient; return clientDmsDestroyed;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -107,17 +107,29 @@ module.exports = Self => {
return {or: [ return {or: [
{'c.phone': {like: `%${value}%`}}, {'c.phone': {like: `%${value}%`}},
{'c.mobile': {like: `%${value}%`}}, {'c.mobile': {like: `%${value}%`}},
{'a.phone': {like: `%${value}%`}},
]}; ]};
case 'zoneFk': case 'zoneFk':
param = 'a.postalCode'; return {'a.postalCode': {inq: postalCode}};
return {[param]: {inq: postalCode}}; case 'city':
return {or: [
{'c.city': {like: `%${value}%`}},
{'a.city': {like: `%${value}%`}}
]};
case 'postcode':
return {or: [
{'c.postcode': value},
{'a.postalCode': value}
]};
case 'provinceFk':
return {or: [
{'p.id': value},
{'a.provinceFk': value}
]};
case 'name': case 'name':
case 'salesPersonFk': case 'salesPersonFk':
case 'fi': case 'fi':
case 'socialName': case 'socialName':
case 'city':
case 'postcode':
case 'provinceFk':
case 'email': case 'email':
param = `c.${param}`; param = `c.${param}`;
return {[param]: {like: `%${value}%`}}; return {[param]: {like: `%${value}%`}};
@ -134,24 +146,29 @@ module.exports = Self => {
c.fi, c.fi,
c.socialName, c.socialName,
c.phone, c.phone,
a.phone,
c.mobile, c.mobile,
c.city, c.city,
a.city,
c.postcode, c.postcode,
a.postalCode,
c.email, c.email,
c.isActive, c.isActive,
c.isFreezed, c.isFreezed,
p.id AS provinceFk, p.id AS provinceClientFk,
a.provinceFk AS provinceAddressFk,
p.name AS province, p.name AS province,
u.id AS salesPersonFk, u.id AS salesPersonFk,
u.name AS salesPerson u.name AS salesPerson
FROM client c FROM client c
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN province p ON p.id = c.provinceFk LEFT JOIN province p ON p.id = c.provinceFk
JOIN vn.address a ON a.clientFk = c.id JOIN address a ON a.clientFk = c.id
` `
); );
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));
stmt.merge('GROUP BY c.id');
stmt.merge(conn.makePagination(filter)); stmt.merge(conn.makePagination(filter));
const clientsIndex = stmts.push(stmt) - 1; const clientsIndex = stmts.push(stmt) - 1;

View File

@ -55,9 +55,9 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id, options) => { Self.uploadFile = async(ctx, id, options) => {
const models = Self.app.models; const {Dms, ClientDms} = Self.app.models;
let tx;
const myOptions = {}; const myOptions = {};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -67,23 +67,20 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
const promises = [];
try { try {
const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions); const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
uploadedFiles.forEach(dms => { const promises = uploadedFiles.map(dms =>
const newClientDms = models.ClientDms.create({ ClientDms.create({
clientFk: id, clientFk: id,
dmsFk: dms.id dmsFk: dms.id
}, myOptions); }, myOptions)
promises.push(newClientDms); );
}); await Promise.all(promises);
const resolvedPromises = await Promise.all(promises);
if (tx) await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return uploadedFiles;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

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

@ -17,3 +17,18 @@ columns:
agencyModeFk: agency agencyModeFk: agency
routeFk: route routeFk: route
zoneFk: zone zoneFk: zone
name: name
beachFk: beach
ticketPacked: tickets packed
ticketFree: tickets free
ticketProduction: tickets production
packages: packages
note: note
dated: dated
dockFk: dock
priority: priority
etd: etd
expeditionTruckFk: truck
m3boxes: m3 boxes
bufferFk: buffer
isPickingAllowed: is picking allowed

View File

@ -17,3 +17,18 @@ columns:
agencyModeFk: agencia agencyModeFk: agencia
routeFk: ruta routeFk: ruta
zoneFk: zona zoneFk: zona
name: nombre
beachFk: playa
ticketPacked: tickets encajados
ticketFree: tickets libres
ticketProduction: tickets producción
packages: paquetes
note: nota
dated: fecha
dockFk: muelle
priority: prioridad
etd: etd
expeditionTruckFk: camión
m3boxes: m3 cajas
bufferFk: buffer
isPickingAllowed: está permitido recoger

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

@ -19,7 +19,6 @@ module.exports = Self => {
}); });
Self.removeFile = async(ctx, id, options) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -32,18 +31,18 @@ module.exports = Self => {
} }
try { try {
const targetTicketDms = await models.TicketDms.findById(id, null, myOptions); const ticketDms = await Self.findById(id, null, myOptions);
const targetDms = await models.Dms.findById(targetTicketDms.dmsFk, null, myOptions);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions);
await models.Dms.removeFile(ctx, targetTicketDms.dmsFk, myOptions); const targetDms = await Self.app.models.Dms.removeFile(ctx, ticketDms.dmsFk, myOptions);
await targetTicketDms.destroy(myOptions);
await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions); if (!targetDms || !ticketDms)
throw new UserError('Try again');
const ticketDmsDestroyed = await ticketDms.destroy(myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
return targetDms; return ticketDmsDestroyed;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -41,7 +41,7 @@ module.exports = Self => {
throw new ForbiddenError(`This ticket is not editable.`); throw new ForbiddenError(`This ticket is not editable.`);
if (isLocked && !isWeekly) if (isLocked && !isWeekly)
throw new ForbiddenError(`This ticket is locked.`); throw new ForbiddenError(`This ticket is locked`);
if (isWeekly && !canEditWeeklyTicket) if (isWeekly && !canEditWeeklyTicket)
throw new ForbiddenError(`You don't have enough privileges.`); throw new ForbiddenError(`You don't have enough privileges.`);

View File

@ -89,6 +89,6 @@ describe('ticket addSale()', () => {
error = e; error = e;
} }
expect(error.message).toEqual(`This ticket is locked.`); expect(error.message).toEqual(`This ticket is locked`);
}); });
}); });

View File

@ -40,7 +40,7 @@ describe('ticket isEditableOrThrow()', () => {
expect(error.message).toEqual(`This ticket is not editable.`); expect(error.message).toEqual(`This ticket is not editable.`);
}); });
it('should throw an error as this ticket is locked.', async() => { it('should throw an error as This ticket is locked', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
let error; let error;
try { try {
@ -57,7 +57,7 @@ describe('ticket isEditableOrThrow()', () => {
await tx.rollback(); await tx.rollback();
} }
expect(error.message).toEqual(`This ticket is locked.`); expect(error.message).toEqual(`This ticket is locked`);
}); });
it('should throw an error as you do not have enough privileges.', async() => { it('should throw an error as you do not have enough privileges.', async() => {

View File

@ -39,6 +39,6 @@ describe('ticket recalculateComponents()', () => {
error = e; error = e;
} }
expect(error).toEqual(new ForbiddenError(`This ticket is locked.`)); expect(error).toEqual(new ForbiddenError(`This ticket is locked`));
}); });
}); });

View File

@ -23,7 +23,7 @@ describe('Ticket transferClient()', () => {
error = e; error = e;
} }
expect(error.message).toEqual(`This ticket is locked.`); expect(error.message).toEqual(`This ticket is locked`);
}); });
it('should be assigned a different clientFk', async() => { it('should be assigned a different clientFk', async() => {

View File

@ -47,7 +47,7 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id, options) => { Self.uploadFile = async(ctx, id, options) => {
const models = Self.app.models; const {Dms, TicketDms} = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -59,22 +59,19 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
const promises = [];
try { try {
const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions); const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
uploadedFiles.forEach(dms => {
const newTicketDms = models.TicketDms.create({
ticketFk: id,
dmsFk: dms.id
}, myOptions);
promises.push(newTicketDms); const promises = uploadedFiles.map(dms => TicketDms.create({
}); ticketFk: id,
const resolvedPromises = await Promise.all(promises); dmsFk: dms.id
}, myOptions));
await Promise.all(promises);
if (tx) await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return uploadedFiles;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -34,9 +34,9 @@
"foreignKey": "stateFk" "foreignKey": "stateFk"
}, },
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "VnUser", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "workerFk"
} }
} }
} }

View File

@ -20,9 +20,6 @@
}, },
"stateFk": { "stateFk": {
"type": "number" "type": "number"
},
"userFk": {
"type": "number"
} }
}, },
"relations": { "relations": {
@ -37,9 +34,9 @@
"foreignKey": "stateFk" "foreignKey": "stateFk"
}, },
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "VnUser", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }
} }

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

@ -18,13 +18,35 @@ module.exports = Self => {
} }
}); });
Self.removeFile = async(ctx, id) => { Self.removeFile = async(ctx, dmsFk, options) => {
const models = Self.app.models; const myOptions = {};
const workerDms = await Self.findById(id); let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
await models.Dms.removeFile(ctx, workerDms.dmsFk); if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
return workerDms.destroy(); try {
const WorkerDms = await Self.findOne({
where: {document: dmsFk}
}, myOptions);
const targetDms = await Self.app.models.Dms.removeFile(ctx, dmsFk, myOptions);
if (!targetDms || !WorkerDms)
throw new UserError('Try again');
const workerDmsDestroyed = await WorkerDms.destroy(myOptions);
if (tx) await tx.commit();
return workerDmsDestroyed;
} catch (e) {
await tx.rollback();
throw e;
}
}; };
}; };

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,559 +53,10 @@ 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();
@ -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

@ -47,30 +47,33 @@ module.exports = Self => {
}); });
Self.uploadFile = async(ctx, id) => { Self.uploadFile = async(ctx, id) => {
const models = Self.app.models; const {Dms, WorkerDms} = Self.app.models;
const promises = []; const myOptions = {};
const tx = await Self.beginTransaction({}); let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try { try {
const options = {transaction: tx}; const uploadedFiles = await Dms.uploadFile(ctx, myOptions);
const promises = uploadedFiles.map(dms =>
const uploadedFiles = await models.Dms.uploadFile(ctx, options); WorkerDms.create({
uploadedFiles.forEach(dms => {
const newWorkerDms = models.WorkerDms.create({
workerFk: id, workerFk: id,
dmsFk: dms.id dmsFk: dms.id
}, options); }, myOptions));
await Promise.all(promises);
promises.push(newWorkerDms); if (tx) await tx.commit();
});
const resolvedPromises = await Promise.all(promises);
await tx.commit(); return uploadedFiles;
} catch (e) {
return resolvedPromises; if (tx) await tx.rollback();
} catch (err) { throw e;
await tx.rollback();
throw err;
} }
}; };
}; };

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

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.50.01", "version": "24.02.01",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "salix-back", "name": "salix-back",
"version": "23.50.01", "version": "24.02.01",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"axios": "^1.2.2", "axios": "^1.2.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.52.01", "version": "24.02.01",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",