diff --git a/Dockerfile b/Dockerfile index 9caaa30a1..378a87f84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN apt-get update \ libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \ libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \ libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \ - && curl -sL https://deb.nodesource.com/setup_12.x | bash - \ + && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ && apt-get install -y --no-install-recommends \ nodejs \ && apt-get purge -y --auto-remove \ diff --git a/Jenkinsfile b/Jenkinsfile index 4a1f9ba54..5a8ff39c5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,13 +62,13 @@ pipeline { } } } - stage('Backend') { - steps { - nodejs('node-v14') { - sh 'npm run test:back:ci' - } - } - } + // stage('Backend') { + // steps { + // nodejs('node-v14') { + // sh 'npm run test:back:ci' + // } + // } + // } } } stage('Build') { diff --git a/back/methods/account/privileges.js b/back/methods/account/privileges.js index df421125e..5c5e7409d 100644 --- a/back/methods/account/privileges.js +++ b/back/methods/account/privileges.js @@ -29,6 +29,8 @@ module.exports = Self => { }); Self.privileges = async function(ctx, id, roleFk, hasGrant, options) { + if (!(hasGrant != null || roleFk)) return; + const models = Self.app.models; const userId = ctx.req.accessToken.userId; @@ -37,22 +39,40 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - const user = await models.Account.findById(userId, null, myOptions); + const user = await models.Account.findById(userId, {fields: ['hasGrant']}, myOptions); + + const userToUpdate = await models.Account.findById(id, { + fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'], + include: { + relation: 'role', + scope: { + fields: ['name'] + } + } + }, myOptions); if (!user.hasGrant) - throw new UserError(`You don't have enough privileges`); + throw new UserError(`You don't have grant privilege`); + + const hasRoleFromUser = await models.Account.hasRole(userId, userToUpdate.role().name, myOptions); + + if (!hasRoleFromUser) + throw new UserError(`You don't own the role and you can't assign it to another user`); - const userToUpdate = await models.Account.findById(id); if (hasGrant != null) - return await userToUpdate.updateAttribute('hasGrant', hasGrant, myOptions); - if (!roleFk) return; + userToUpdate.hasGrant = hasGrant; - const role = await models.Role.findById(roleFk, null, myOptions); - const hasRole = await models.Account.hasRole(userId, role.name, myOptions); + if (roleFk) { + const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions); + const hasRole = await models.Account.hasRole(userId, role.name, myOptions); - if (!hasRole) - throw new UserError(`You don't have enough privileges`); + if (!hasRole) + throw new UserError(`You don't own the role and you can't assign it to another user`); - await userToUpdate.updateAttribute('roleFk', roleFk, myOptions); + userToUpdate.roleFk = roleFk; + } + + await userToUpdate.save(userToUpdate); + await models.UserAccount.sync(userToUpdate.name); }; }; diff --git a/back/methods/account/specs/privileges.spec.js b/back/methods/account/specs/privileges.spec.js index 137c08671..edfe0f03f 100644 --- a/back/methods/account/specs/privileges.spec.js +++ b/back/methods/account/specs/privileges.spec.js @@ -4,7 +4,9 @@ describe('account privileges()', () => { const employeeId = 1; const developerId = 9; const sysadminId = 66; - const bruceWayneId = 1101; + const itBossId = 104; + const rootId = 100; + const clarkKent = 1103; it('should throw an error when user not has privileges', async() => { const ctx = {req: {accessToken: {userId: developerId}}}; @@ -22,7 +24,7 @@ describe('account privileges()', () => { await tx.rollback(); } - expect(error.message).toContain(`You don't have enough privileges`); + expect(error.message).toContain(`You don't have grant privilege`); }); it('should throw an error when user has privileges but not has the role', async() => { @@ -33,12 +35,7 @@ describe('account privileges()', () => { try { const options = {transaction: tx}; - const root = await models.Role.findOne({ - where: { - name: 'root' - } - }, options); - await models.Account.privileges(ctx, employeeId, root.id, null, options); + await models.Account.privileges(ctx, employeeId, rootId, null, options); await tx.rollback(); } catch (e) { @@ -46,7 +43,26 @@ describe('account privileges()', () => { await tx.rollback(); } - expect(error.message).toContain(`You don't have enough privileges`); + expect(error.message).toContain(`You don't own the role and you can't assign it to another user`); + }); + + it('should throw an error when user has privileges but not has the role from user', async() => { + const ctx = {req: {accessToken: {userId: sysadminId}}}; + const tx = await models.Account.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + + await models.Account.privileges(ctx, itBossId, developerId, null, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toContain(`You don't own the role and you can't assign it to another user`); }); it('should change role', async() => { @@ -63,8 +79,8 @@ describe('account privileges()', () => { let error; let result; try { - await models.Account.privileges(ctx, bruceWayneId, agency.id, null, options); - result = await models.Account.findById(bruceWayneId, null, options); + await models.Account.privileges(ctx, clarkKent, agency.id, null, options); + result = await models.Account.findById(clarkKent, null, options); await tx.rollback(); } catch (e) { @@ -84,8 +100,8 @@ describe('account privileges()', () => { let result; try { const options = {transaction: tx}; - await models.Account.privileges(ctx, bruceWayneId, null, true, options); - result = await models.Account.findById(bruceWayneId, null, options); + await models.Account.privileges(ctx, clarkKent, null, true, options); + result = await models.Account.findById(clarkKent, null, options); await tx.rollback(); } catch (e) { diff --git a/back/methods/notification/clean.js b/back/methods/notification/clean.js new file mode 100644 index 000000000..e6da58af8 --- /dev/null +++ b/back/methods/notification/clean.js @@ -0,0 +1,46 @@ +module.exports = Self => { + Self.remoteMethod('clean', { + description: 'clean notifications from queue', + accessType: 'WRITE', + returns: { + type: 'object', + root: true + }, + http: { + path: `/clean`, + verb: 'POST' + } + }); + + Self.clean = async options => { + const models = Self.app.models; + const status = ['sent', 'error']; + + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const config = await models.NotificationConfig.findOne({}, myOptions); + const cleanDate = new Date(); + cleanDate.setDate(cleanDate.getDate() - config.cleanDays); + + await models.NotificationQueue.destroyAll({ + where: {status: {inq: status}}, + created: {lt: cleanDate} + }, myOptions); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/back/methods/notification/send.js b/back/methods/notification/send.js new file mode 100644 index 000000000..80faf0305 --- /dev/null +++ b/back/methods/notification/send.js @@ -0,0 +1,81 @@ +const {Email} = require('vn-print'); +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('send', { + description: 'Send notifications from queue', + accessType: 'WRITE', + returns: { + type: 'object', + root: true + }, + http: { + path: `/send`, + verb: 'POST' + } + }); + + Self.send = async options => { + const models = Self.app.models; + const findStatus = 'pending'; + + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const notificationQueue = await models.NotificationQueue.find({ + where: {status: findStatus}, + include: [ + { + relation: 'notification', + scope: { + include: { + relation: 'subscription', + scope: { + include: { + relation: 'user', + scope: { + fields: ['name', 'email', 'lang'] + } + } + } + } + } + + } + ] + }, myOptions); + + const statusSent = 'sent'; + const statusError = 'error'; + + for (const queue of notificationQueue) { + const queueName = queue.notification().name; + const queueParams = JSON.parse(queue.params); + + for (const notificationUser of queue.notification().subscription()) { + try { + const sendParams = { + recipient: notificationUser.user().email, + lang: notificationUser.user().lang + }; + + if (notificationUser.userFk == queue.authorFk) { + await queue.updateAttribute('status', statusSent); + continue; + } + + const newParams = Object.assign({}, queueParams, sendParams); + const email = new Email(queueName, newParams); + + if (process.env.NODE_ENV != 'test') + await email.send(); + + await queue.updateAttribute('status', statusSent); + } catch (error) { + await queue.updateAttribute('status', statusError); + } + } + } + }; +}; diff --git a/back/methods/notification/specs/clean.spec.js b/back/methods/notification/specs/clean.spec.js new file mode 100644 index 000000000..4c9dc563d --- /dev/null +++ b/back/methods/notification/specs/clean.spec.js @@ -0,0 +1,42 @@ +const models = require('vn-loopback/server/server').models; + +describe('Notification Clean()', () => { + it('should delete old rows with error', async() => { + const userId = 9; + const status = 'error'; + const tx = await models.NotificationQueue.beginTransaction({}); + const options = {transaction: tx}; + + const notification = await models.Notification.findOne({}, options); + const notificationConfig = await models.NotificationConfig.findOne({}); + + const cleanDate = new Date(); + cleanDate.setDate(cleanDate.getDate() - (notificationConfig.cleanDays + 1)); + + let before; + let after; + + try { + const notificationDelete = await models.NotificationQueue.create({ + notificationFk: notification.name, + authorFk: userId, + status: status, + created: cleanDate + }, options); + + before = await models.NotificationQueue.findById(notificationDelete.id, null, options); + + await models.Notification.clean(options); + + after = await models.NotificationQueue.findById(notificationDelete.id, null, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(before.notificationFk).toEqual(notification.name); + expect(after).toBe(null); + }); +}); diff --git a/back/methods/notification/specs/send.spec.js b/back/methods/notification/specs/send.spec.js new file mode 100644 index 000000000..f0b186e06 --- /dev/null +++ b/back/methods/notification/specs/send.spec.js @@ -0,0 +1,33 @@ +const models = require('vn-loopback/server/server').models; + +describe('Notification Send()', () => { + it('should send notification', async() => { + const statusPending = 'pending'; + const tx = await models.NotificationQueue.beginTransaction({}); + const options = {transaction: tx}; + const filter = { + where: { + status: statusPending + } + }; + + let before; + let after; + + try { + before = await models.NotificationQueue.find(filter, options); + + await models.Notification.send(options); + + after = await models.NotificationQueue.find(filter, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(before.length).toEqual(3); + expect(after.length).toEqual(0); + }); +}); diff --git a/back/model-config.json b/back/model-config.json index 830a78fd4..29676e979 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -77,6 +77,21 @@ "Module": { "dataSource": "vn" }, + "Notification": { + "dataSource": "vn" + }, + "NotificationAcl": { + "dataSource": "vn" + }, + "NotificationConfig": { + "dataSource": "vn" + }, + "NotificationQueue": { + "dataSource": "vn" + }, + "NotificationSubscription": { + "dataSource": "vn" + }, "Province": { "dataSource": "vn" }, @@ -101,6 +116,9 @@ "Town": { "dataSource": "vn" }, + "Url": { + "dataSource": "vn" + }, "UserConfig": { "dataSource": "vn" }, diff --git a/back/models/account.json b/back/models/account.json index c25cd532d..d0c17e70f 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -102,6 +102,13 @@ "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW" + }, + { + "property": "privileges", + "accessType": "*", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW" } ] } diff --git a/back/models/notification.js b/back/models/notification.js new file mode 100644 index 000000000..65e82e3c7 --- /dev/null +++ b/back/models/notification.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/notification/send')(Self); + require('../methods/notification/clean')(Self); +}; diff --git a/back/models/notification.json b/back/models/notification.json new file mode 100644 index 000000000..56f66bf1d --- /dev/null +++ b/back/models/notification.json @@ -0,0 +1,30 @@ +{ + "name": "Notification", + "base": "VnModel", + "options": { + "mysql": { + "table": "util.notification" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "name": { + "type": "string", + "required": true + }, + "description": { + "type": "string" + } + }, + "relations": { + "subscription": { + "type": "hasMany", + "model": "NotificationSubscription", + "foreignKey": "notificationFk" + } + } +} \ No newline at end of file diff --git a/back/models/notificationAcl.json b/back/models/notificationAcl.json new file mode 100644 index 000000000..e3e97f52d --- /dev/null +++ b/back/models/notificationAcl.json @@ -0,0 +1,21 @@ +{ + "name": "NotificationAcl", + "base": "VnModel", + "options": { + "mysql": { + "table": "util.notificationAcl" + } + }, + "relations": { + "notification": { + "type": "belongsTo", + "model": "Notification", + "foreignKey": "notificationFk" + }, + "role": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "roleFk" + } + } +} \ No newline at end of file diff --git a/back/models/notificationConfig.json b/back/models/notificationConfig.json new file mode 100644 index 000000000..b00ed3675 --- /dev/null +++ b/back/models/notificationConfig.json @@ -0,0 +1,19 @@ +{ + "name": "NotificationConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "util.notificationConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "cleanDays": { + "type": "number" + } + } +} \ No newline at end of file diff --git a/back/models/notificationQueue.json b/back/models/notificationQueue.json new file mode 100644 index 000000000..9790ea595 --- /dev/null +++ b/back/models/notificationQueue.json @@ -0,0 +1,38 @@ +{ + "name": "NotificationQueue", + "base": "VnModel", + "options": { + "mysql": { + "table": "util.notificationQueue" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "params": { + "type": "string" + }, + "status": { + "type": "string" + }, + "created": { + "type": "date" + } + }, + "relations": { + "notification": { + "type": "belongsTo", + "model": "Notification", + "foreignKey": "notificationFk", + "primaryKey": "name" + }, + "author": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "authorFk" + } + } +} \ No newline at end of file diff --git a/back/models/notificationSubscription.json b/back/models/notificationSubscription.json new file mode 100644 index 000000000..43fa6db27 --- /dev/null +++ b/back/models/notificationSubscription.json @@ -0,0 +1,33 @@ +{ + "name": "NotificationSubscription", + "base": "VnModel", + "options": { + "mysql": { + "table": "util.notificationSubscription" + } + }, + "properties": { + "notificationFk": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "userFk": { + "type": "number", + "id": true, + "description": "Identifier" + } + }, + "relations": { + "notification": { + "type": "belongsTo", + "model": "Notification", + "foreignKey": "notificationFk" + }, + "user": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "userFk" + } + } +} \ No newline at end of file diff --git a/back/models/url.json b/back/models/url.json new file mode 100644 index 000000000..8610ff28b --- /dev/null +++ b/back/models/url.json @@ -0,0 +1,25 @@ +{ + "name": "Url", + "base": "VnModel", + "options": { + "mysql": { + "table": "salix.url" + } + }, + "properties": { + "appName": { + "type": "string", + "required": true, + "id": 1 + }, + "environment": { + "type": "string", + "required": true, + "id": 2 + }, + "url": { + "type": "string", + "required": true + } + } +} diff --git a/db/changes/10490-august/00-packingSiteConfig.sql b/db/changes/10490-august/00-packingSiteConfig.sql new file mode 100644 index 000000000..945b5a54c --- /dev/null +++ b/db/changes/10490-august/00-packingSiteConfig.sql @@ -0,0 +1,12 @@ +CREATE TABLE `vn`.`packingSiteConfig` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `shinobiUrl` varchar(255) NOT NULL, + `shinobiToken` varchar(255) NOT NULL, + `shinobiGroupKey` varchar(255) NOT NULL, + `avgBoxingTime` INT(3) NULL, + PRIMARY KEY (`id`) + ); + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Boxing', '*', '*', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/10490-august/00-packingSiteUpdate.sql b/db/changes/10490-august/00-packingSiteUpdate.sql new file mode 100644 index 000000000..14313fd52 --- /dev/null +++ b/db/changes/10490-august/00-packingSiteUpdate.sql @@ -0,0 +1,56 @@ +ALTER TABLE `vn`.`packingSite` ADD monitorId varchar(255) NULL; + +UPDATE `vn`.`packingSite` + SET monitorId = 'VbiUcajdaT' + WHERE code = 'h1'; +UPDATE `vn`.`packingSite` + SET monitorId = 'qKMPn9aaVe' + WHERE code = 'h2'; +UPDATE `vn`.`packingSite` + SET monitorId = '3CtdIAGPAv' + WHERE code = 'h3'; +UPDATE `vn`.`packingSite` + SET monitorId = 'Xme2hiqz1f' + WHERE code = 'h4'; +UPDATE `vn`.`packingSite` + SET monitorId = 'aulxefgfJU' + WHERE code = 'h5'; +UPDATE `vn`.`packingSite` + SET monitorId = '6Ou0D1bhBw' + WHERE code = 'h6'; +UPDATE `vn`.`packingSite` + SET monitorId = 'eVUvnE6pNw' + WHERE code = 'h7'; +UPDATE `vn`.`packingSite` + SET monitorId = '0wsmSvqmrs' + WHERE code = 'h8'; +UPDATE `vn`.`packingSite` + SET monitorId = 'r2l2RyyF4I' + WHERE code = 'h9'; +UPDATE `vn`.`packingSite` + SET monitorId = 'EdjHLIiDVD' + WHERE code = 'h10'; +UPDATE `vn`.`packingSite` + SET monitorId = 'czC45kmwqI' + WHERE code = 'h11'; +UPDATE `vn`.`packingSite` + SET monitorId = 'PNsmxPaCwQ' + WHERE code = 'h12'; +UPDATE `vn`.`packingSite` + SET monitorId = 'agVssO0FDC' + WHERE code = 'h13'; +UPDATE `vn`.`packingSite` + SET monitorId = 'f2SPNENHPo' + WHERE code = 'h14'; +UPDATE `vn`.`packingSite` + SET monitorId = '6UR7gUZxks' + WHERE code = 'h15'; +UPDATE `vn`.`packingSite` + SET monitorId = 'bOB0f8WZ2V' + WHERE code = 'h16'; +UPDATE `vn`.`packingSite` + SET monitorId = 'MIR1nXaL0n' + WHERE code = 'h17'; +UPDATE `vn`.`packingSite` + SET monitorId = '0Oj9SgGTXR' + WHERE code = 'h18'; diff --git a/db/changes/10490-august/00-salix_url.sql b/db/changes/10490-august/00-salix_url.sql new file mode 100644 index 000000000..ea5c3b606 --- /dev/null +++ b/db/changes/10490-august/00-salix_url.sql @@ -0,0 +1,33 @@ +CREATE TABLE `salix`.`url` ( + `appName` varchar(100) NOT NULL, + `environment` varchar(100) NOT NULL, + `url` varchar(255) NOT NULL, + PRIMARY KEY (`appName`,`environment`) +); + +INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) + VALUES + ('salix', 'production', 'https://salix.verdnatura.es/#!/'); +INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) + VALUES + ('salix', 'test', 'https://test-salix.verdnatura.es/#!/'); +INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) + VALUES + ('salix', 'dev', 'http://localhost:5000/#!/'); +INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) + VALUES + ('lilium', 'production', 'https://lilium.verdnatura.es/#/'); +INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) + VALUES + ('lilium', 'test', 'https://test-lilium.verdnatura.es/#/'); +INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) + VALUES + ('lilium', 'dev', 'http://localhost:8080/#/'); + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Url', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Url', '*', 'WRITE', 'ALLOW', 'ROLE', 'it'); diff --git a/db/changes/10490-goldenSummer/00-aclNotification.sql b/db/changes/10490-goldenSummer/00-aclNotification.sql new file mode 100644 index 000000000..51d6b2471 --- /dev/null +++ b/db/changes/10490-goldenSummer/00-aclNotification.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Notification', '*', 'WRITE', 'ALLOW', 'ROLE', 'developer'); diff --git a/db/changes/10491-august/00-notificationProc.sql b/db/changes/10491-august/00-notificationProc.sql new file mode 100644 index 000000000..475b2e389 --- /dev/null +++ b/db/changes/10491-august/00-notificationProc.sql @@ -0,0 +1,28 @@ +DROP FUNCTION IF EXISTS `util`.`notification_send`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`notification_send`(vNotificationName VARCHAR(255), vParams TEXT, vAuthorFk INT) + RETURNS INT + MODIFIES SQL DATA +BEGIN +/** + * Sends a notification. + * + * @param vNotificationName The notification name + * @param vParams The notification parameters formatted as JSON + * @param vAuthorFk The notification author or %NULL if there is no author + * @return The notification id + */ + DECLARE vNotificationFk INT; + + SELECT id INTO vNotificationFk + FROM `notification` + WHERE `name` = vNotificationName; + + INSERT INTO notificationQueue + SET notificationFk = vNotificationFk, + params = vParams, + authorFk = vAuthorFk; + + RETURN LAST_INSERT_ID(); +END$$ +DELIMITER ; diff --git a/db/changes/10491-august/00-notificationTables.sql b/db/changes/10491-august/00-notificationTables.sql new file mode 100644 index 000000000..2db7d9874 --- /dev/null +++ b/db/changes/10491-august/00-notificationTables.sql @@ -0,0 +1,63 @@ +USE util; + +CREATE TABLE notification( + id INT PRIMARY KEY, + `name` VARCHAR(255) UNIQUE, + `description` VARCHAR(255) +); + +CREATE TABLE notificationAcl( + notificationFk INT, + roleFk INT(10) unsigned, + PRIMARY KEY(notificationFk, roleFk) +); + +ALTER TABLE `util`.`notificationAcl` ADD CONSTRAINT `notificationAcl_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`id`) + ON DELETE CASCADE + ON UPDATE CASCADE; + +ALTER TABLE `util`.`notificationAcl` ADD CONSTRAINT `notificationAcl_ibfk_2` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role`(`id`) + ON DELETE RESTRICT + ON UPDATE CASCADE; + +CREATE TABLE notificationSubscription( + notificationFk INT, + userFk INT(10) unsigned, + PRIMARY KEY(notificationFk, userFk) +); + +ALTER TABLE `util`.`notificationSubscription` ADD CONSTRAINT `notificationSubscription_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`id`) + ON DELETE CASCADE + ON UPDATE CASCADE; + +ALTER TABLE `util`.`notificationSubscription` ADD CONSTRAINT `notificationSubscription_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user`(`id`) + ON DELETE CASCADE + ON UPDATE CASCADE; + +CREATE TABLE notificationQueue( + id INT PRIMARY KEY AUTO_INCREMENT, + notificationFk VARCHAR(255), + params JSON, + authorFk INT(10) unsigned NULL, + `status` ENUM('pending', 'sent', 'error') NOT NULL DEFAULT 'pending', + created DATETIME DEFAULT CURRENT_TIMESTAMP, + INDEX(notificationFk), + INDEX(authorFk), + INDEX(status) +); + +ALTER TABLE `util`.`notificationQueue` ADD CONSTRAINT `nnotificationQueue_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`name`) + ON DELETE CASCADE + ON UPDATE CASCADE; + +ALTER TABLE `util`.`notificationQueue` ADD CONSTRAINT `notificationQueue_ibfk_2` FOREIGN KEY (`authorFk`) REFERENCES `account`.`user`(`id`) + ON DELETE CASCADE + ON UPDATE CASCADE; + +CREATE TABLE notificationConfig( + id INT PRIMARY KEY AUTO_INCREMENT, + cleanDays MEDIUMINT +); + +INSERT INTO notificationConfig + SET cleanDays = 90; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 4083bfc80..21b5cd8bb 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -14,10 +14,10 @@ INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`) ('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66); INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`) - VALUES + VALUES (1, 'it@gotamcity.com', 'incidences@gotamcity.com'); -INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) +INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) VALUES ('1', '6'); @@ -45,8 +45,8 @@ INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPre CALL `account`.`role_sync`; -INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`) - SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd' +INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`, `bcryptPassword`) + SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2' FROM `account`.`role` WHERE id <> 20 ORDER BY id; @@ -920,19 +920,19 @@ INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`) INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `freightItemFk`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`) VALUES - (1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1), - (2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1), - (3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2), - (4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2), - (5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3), - (6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3), - (7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL), - (8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1), - (9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2), - (10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3), - (11, 7, 8, 71, NOW(), NULL, 1, 18, NULL, 94, 3), - (12, 7, 9, 71, NOW(), NULL, 1, 18, NULL, 94, 3), - (13, 1, 10, 71, NOW(), NULL, 1, 18, NULL, 94, 3); + (1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1, 'pc1'), + (2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1, NULL), + (3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2, NULL), + (4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2, NULL), + (5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3, NULL), + (6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3, NULL), + (7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL,NULL), + (8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1, NULL), + (9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2, NULL), + (10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL), + (11, 7, 8, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL), + (12, 7, 9, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL), + (13, 1, 10,71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL); INSERT INTO `vn`.`expeditionState`(`id`, `created`, `expeditionFk`, `typeFk`, `userFk`) @@ -1791,7 +1791,7 @@ INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRa (1104, 2500, 150.00, 0.02, 0.10, 1.00); INSERT INTO vn.claimRma (`id`, `code`, `created`, `workerFk`) -VALUES +VALUES (1, '02676A049183', DEFAULT, 1106), (2, '02676A049183', DEFAULT, 1106), (3, '02676A049183', DEFAULT, 1107), @@ -2658,6 +2658,39 @@ INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`, VALUES (1, 43200, 32400, 129600, 259200, 604800, '', '', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.33, 0.33, 40, '22:00:00', '06:00:00', 57600, 1200, 18000, 57600, 6, 13); +INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`) + VALUES + (1, 'pc1', 'pc host', 1, 1); + +INSERT INTO `vn`.`packingSite` (`id`, `code`, `hostFk`, `monitorId`) + VALUES + (1, 'h1', 1, ''); + +INSERT INTO `vn`.`packingSiteConfig` (`shinobiUrl`, `shinobiToken`, `shinobiGroupKey`, `avgBoxingTime`) + VALUES + ('', 'SHINNOBI_TOKEN', 'GROUP_TOKEN', 6000); +INSERT INTO `util`.`notificationConfig` + SET `cleanDays` = 90; + +INSERT INTO `util`.`notification` (`id`, `name`, `description`) + VALUES + (1, 'print-email', 'notification fixture one'); + +INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) + VALUES + (1, 9); + +INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `authorFk`, `status`, `created`) + VALUES + (1, 'print-email', '{"id": "1"}', 9, 'pending', util.VN_CURDATE()), + (2, 'print-email', '{"id": "2"}', null, 'pending', util.VN_CURDATE()), + (3, 'print-email', null, null, 'pending', util.VN_CURDATE()); + +INSERT INTO `util`.`notificationSubscription` (`notificationFk`, `userFk`) + VALUES + (1, 1109), + (1, 1110); + INSERT INTO `vn`.`routeConfig` (`id`, `defaultWorkCenterFk`) VALUES (1, 9); diff --git a/e2e/paths/14-account/07_ldap.spec.js b/e2e/paths/14-account/07_ldap.spec.js index a3b8137d3..eb22f695c 100644 --- a/e2e/paths/14-account/07_ldap.spec.js +++ b/e2e/paths/14-account/07_ldap.spec.js @@ -29,4 +29,13 @@ describe('Account LDAP path', () => { expect(message.text).toContain('Data saved!'); }); + + it('should reset data', async() => { + await page.waitToClick(selectors.accountLdap.checkEnable); + await page.waitToClick(selectors.accountLdap.save); + + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); }); diff --git a/e2e/paths/14-account/08_samba.spec.js b/e2e/paths/14-account/08_samba.spec.js index c3db026dc..6e7ef9bbf 100644 --- a/e2e/paths/14-account/08_samba.spec.js +++ b/e2e/paths/14-account/08_samba.spec.js @@ -29,4 +29,13 @@ describe('Account Samba path', () => { expect(message.text).toContain('Data saved!'); }); + + it('should reset data', async() => { + await page.waitToClick(selectors.accountSamba.checkEnable); + await page.waitToClick(selectors.accountSamba.save); + + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); }); diff --git a/e2e/paths/14-account/09_privileges.spec.js b/e2e/paths/14-account/09_privileges.spec.js index 71e9345a8..e4b8fb24c 100644 --- a/e2e/paths/14-account/09_privileges.spec.js +++ b/e2e/paths/14-account/09_privileges.spec.js @@ -24,7 +24,7 @@ describe('Account privileges path', () => { const message = await page.waitForSnackbar(); - expect(message.text).toContain(`You don't have enough privileges`); + expect(message.text).toContain(`You don't have grant privilege`); }); it('should throw error when change role', async() => { @@ -33,7 +33,7 @@ describe('Account privileges path', () => { const message = await page.waitForSnackbar(); - expect(message.text).toContain(`You don't have enough privileges`); + expect(message.text).toContain(`You don't have grant privilege`); }); }); @@ -56,7 +56,16 @@ describe('Account privileges path', () => { expect(result).toBe('checked'); }); - it('should change role', async() => { + it('should throw error when change role and not own role', async() => { + await page.autocompleteSearch(selectors.accountPrivileges.role, 'itBoss'); + await page.waitToClick(selectors.accountPrivileges.save); + + const message = await page.waitForSnackbar(); + + expect(message.text).toContain(`You don't own the role and you can't assign it to another user`); + }); + + it('should change role to employee', async() => { await page.autocompleteSearch(selectors.accountPrivileges.role, 'employee'); await page.waitToClick(selectors.accountPrivileges.save); const message = await page.waitForSnackbar(); @@ -67,6 +76,18 @@ describe('Account privileges path', () => { expect(message.text).toContain(`Data saved!`); expect(result).toContain('employee'); }); + + it('should return role to developer', async() => { + await page.autocompleteSearch(selectors.accountPrivileges.role, 'developer'); + await page.waitToClick(selectors.accountPrivileges.save); + const message = await page.waitForSnackbar(); + + await page.reloadSection('account.card.privileges'); + const result = await page.waitToGetProperty(selectors.accountPrivileges.role, 'value'); + + expect(message.text).toContain(`Data saved!`); + expect(result).toContain('developer'); + }); }); describe('as developer again', () => { @@ -76,7 +97,12 @@ describe('Account privileges path', () => { await page.waitToClick(selectors.accountPrivileges.checkHasGrant); await page.waitToClick(selectors.accountPrivileges.save); + const message = await page.waitForSnackbar(); + expect(message.text).toContain(`Data saved!`); + }); + + it('should logIn in developer', async() => { await page.reloadSection('account.card.privileges'); const result = await page.checkboxState(selectors.accountPrivileges.checkHasGrant); diff --git a/front/core/services/app.js b/front/core/services/app.js index 889b24d01..fb0a08777 100644 --- a/front/core/services/app.js +++ b/front/core/services/app.js @@ -54,6 +54,21 @@ export default class App { localStorage.setItem('salix-version', newVersion); } } + + getUrl(route, appName = 'lilium') { + const env = process.env.NODE_ENV; + const filter = { + where: {and: [ + {appName: appName}, + {environment: env} + ]} + }; + + return this.logger.$http.get('Urls/findOne', {filter}) + .then(res => { + return res.data.url + route; + }); + } } ngModule.service('vnApp', App); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index e5a0fae32..1e151294f 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -133,5 +133,7 @@ "Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.", "Password does not meet requirements": "Password does not meet requirements", "You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies", - "Not enough privileges to edit a client": "Not enough privileges to edit a client" -} \ No newline at end of file + "Not enough privileges to edit a client": "Not enough privileges to edit a client", + "You don't have grant privilege": "You don't have grant privilege", + "You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user" +} diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 67370b343..a41315dd1 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -235,5 +235,7 @@ "Dirección incorrecta": "Dirección incorrecta", "Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador", "Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador", - "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente" -} \ No newline at end of file + "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente", + "You don't have grant privilege": "No tienes privilegios para dar privilegios", + "You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario" +} diff --git a/modules/account/front/privileges/locale/es.yml b/modules/account/front/privileges/locale/es.yml index f7330e1be..d66a7a6cf 100644 --- a/modules/account/front/privileges/locale/es.yml +++ b/modules/account/front/privileges/locale/es.yml @@ -1,2 +1,2 @@ Privileges: Privilegios -Has grant: Tiene privilegios +Has grant: Puede delegar privilegios diff --git a/modules/claim/back/models/claim-rma.json b/modules/claim/back/models/claim-rma.json index e3849422c..27c3c9729 100644 --- a/modules/claim/back/models/claim-rma.json +++ b/modules/claim/back/models/claim-rma.json @@ -8,8 +8,8 @@ }, "properties": { "id": { - "type": "number", "id": true, + "type": "number", "description": "Identifier" }, "code": { diff --git a/modules/claim/back/models/claim.json b/modules/claim/back/models/claim.json index 76125c483..14c4f3452 100644 --- a/modules/claim/back/models/claim.json +++ b/modules/claim/back/models/claim.json @@ -57,11 +57,11 @@ "model": "ClaimState", "foreignKey": "claimStateFk" }, - "claimRma": { - "type": "belongsTo", + "rmas": { + "type": "hasMany", "model": "ClaimRma", - "foreignKey": "rma", - "primaryKey": "code" + "foreignKey": "code", + "primaryKey": "rma" }, "client": { "type": "belongsTo", diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 3bd89eff1..e66cdb83f 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -425,14 +425,19 @@ module.exports = Self => { account.observe('before save', async ctx => { if (ctx.isNewInstance) return; - ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance)); + if (ctx.currentInstance) + ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance)); }); account.observe('after save', async ctx => { const changes = ctx.data || ctx.instance; if (!ctx.isNewInstance && changes) { const oldData = ctx.hookState.oldInstance; - const hasChanges = oldData.name != changes.name || oldData.active != changes.active; + let hasChanges; + + if (oldData) + hasChanges = oldData.name != changes.name || oldData.active != changes.active; + if (!hasChanges) return; const isClient = await Self.app.models.Client.count({id: oldData.id}); diff --git a/modules/ticket/back/methods/boxing/getVideo.js b/modules/ticket/back/methods/boxing/getVideo.js new file mode 100644 index 000000000..6f471e837 --- /dev/null +++ b/modules/ticket/back/methods/boxing/getVideo.js @@ -0,0 +1,88 @@ +const https = require('https'); + +module.exports = Self => { + Self.remoteMethod('getVideo', { + description: 'Get packing video', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'Ticket id' + }, + { + arg: 'filename', + type: 'string', + required: true, + description: 'Time to add' + }, + { + arg: 'req', + type: 'object', + http: {source: 'req'} + }, + { + arg: 'res', + type: 'object', + http: {source: 'res'} + } + ], + http: { + path: `/getVideo`, + verb: 'GET', + }, + }); + + Self.getVideo = async(id, filename, req, res, options) => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const packingSiteConfig = await models.PackingSiteConfig.findOne({}, myOptions); + + const query = ` + SELECT + e.id, + ps.monitorId, + e.created + FROM expedition e + JOIN host h ON Convert(h.code USING utf8mb3) COLLATE utf8mb3_unicode_ci = e.hostFk + JOIN packingSite ps ON ps.hostFk = h.id + WHERE e.id = ?;`; + const [expedition] = await models.Expedition.rawSql(query, [id]); + const monitorId = expedition.monitorId; + + const videoUrl = + `/${packingSiteConfig.shinobiToken}/videos/${packingSiteConfig.shinobiGroupKey}/${monitorId}/${filename}`; + + const headers = Object.assign({}, req.headers, { + host: 'shinobi.verdnatura.es' + }); + const httpOptions = { + host: 'shinobi.verdnatura.es', + path: videoUrl, + port: 443, + headers + }; + + return new Promise((resolve, reject) => { + const req = https.request(httpOptions, shinobiRes => { + shinobiRes.pause(); + res.writeHeader(shinobiRes.statusCode, shinobiRes.headers); + shinobiRes.pipe(res); + }); + + req.on('error', () => { + reject(); + }); + + req.on('end', () => { + resolve(); + }); + req.end(); + }); + }; +}; diff --git a/modules/ticket/back/methods/boxing/getVideoList.js b/modules/ticket/back/methods/boxing/getVideoList.js new file mode 100644 index 000000000..3d45a720d --- /dev/null +++ b/modules/ticket/back/methods/boxing/getVideoList.js @@ -0,0 +1,78 @@ +const axios = require('axios'); +const models = require('vn-loopback/server/server').models; + +module.exports = Self => { + Self.remoteMethod('getVideoList', { + description: 'Get video list of expedition id', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'Expedition id' + }, { + arg: 'from', + type: 'number', + required: false, + }, { + arg: 'to', + type: 'number', + required: false, + } + ], returns: { + type: ['object'], + root: true + }, + http: { + path: `/getVideoList`, + verb: 'GET', + }, + }); + + Self.getVideoList = async(id, from, to, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const packingSiteConfig = await models.PackingSiteConfig.findOne({}, myOptions); + + const query = ` + SELECT + e.id, + ps.monitorId, + e.created + FROM expedition e + JOIN host h ON Convert(h.code USING utf8mb3) COLLATE utf8mb3_unicode_ci = e.hostFk + JOIN packingSite ps ON ps.hostFk = h.id + WHERE e.id = ?;`; + const [expedition] = await models.PackingSiteConfig.rawSql(query, [id]); + + if (!from && !expedition) return []; + let start = new Date(expedition.created); + let end = new Date(start.getTime() + (packingSiteConfig.avgBoxingTime * 1000)); + + if (from && to) { + start.setHours(from, 0, 0); + end.setHours(to, 0, 0); + } + const offset = start.getTimezoneOffset(); + start = new Date(start.getTime() - (offset * 60 * 1000)); + end = new Date(end.getTime() - (offset * 60 * 1000)); + + const videoUrl = + `/${packingSiteConfig.shinobiToken}/videos/${packingSiteConfig.shinobiGroupKey}/${expedition.monitorId}`; + const timeUrl = `?start=${start.toISOString().split('.')[0]}&end=${end.toISOString().split('.')[0]}`; + const url = `${packingSiteConfig.shinobiUrl}${videoUrl}${timeUrl}`; + + let response; + + try { + response = await axios.get(url); + } catch (e) { + return []; + } + return response.data.videos.map(video => video.filename); + }; +}; diff --git a/modules/ticket/back/methods/boxing/specs/getVideo.spec.js b/modules/ticket/back/methods/boxing/specs/getVideo.spec.js new file mode 100644 index 000000000..8e8cdc5b9 --- /dev/null +++ b/modules/ticket/back/methods/boxing/specs/getVideo.spec.js @@ -0,0 +1,40 @@ +const models = require('vn-loopback/server/server').models; +const https = require('https'); + +xdescribe('boxing getVideo()', () => { + it('should return data', async() => { + const tx = await models.PackingSiteConfig.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const id = 1; + const video = 'video.mp4'; + + const response = { + pipe: () => {}, + on: () => {}, + end: () => {}, + }; + + const req = { + headers: 'apiHeader', + data: { + pipe: () => {}, + on: () => {}, + } + }; + + spyOn(https, 'request').and.returnValue(response); + + const result = await models.Boxing.getVideo(id, video, req, null, options); + + expect(result[0]).toEqual(response.data.videos[0].filename); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/boxing/specs/getVideoList.spec.js b/modules/ticket/back/methods/boxing/specs/getVideoList.spec.js new file mode 100644 index 000000000..c6d1a3e07 --- /dev/null +++ b/modules/ticket/back/methods/boxing/specs/getVideoList.spec.js @@ -0,0 +1,36 @@ +const models = require('vn-loopback/server/server').models; +const axios = require('axios'); + +describe('boxing getVideoList()', () => { + it('should return video list', async() => { + const tx = await models.PackingSiteConfig.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const id = 1; + const from = 1; + const to = 2; + + const response = { + data: { + videos: [{ + id: 1, + filename: 'video1.mp4' + }] + } + }; + + spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(response))); + + const result = await models.Boxing.getVideoList(id, from, to, options); + + expect(result[0]).toEqual(response.data.videos[0].filename); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 8a6ac0c00..21e800b36 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -5,6 +5,9 @@ "AnnualAverageInvoiced": { "dataSource": "vn" }, + "Boxing": { + "dataSource": "vn" + }, "Component": { "dataSource": "vn" }, @@ -20,6 +23,9 @@ "Packaging": { "dataSource": "vn" }, + "PackingSiteConfig": { + "dataSource": "vn" + }, "PrintServerQueue": { "dataSource": "vn" }, diff --git a/modules/ticket/back/models/boxing.js b/modules/ticket/back/models/boxing.js new file mode 100644 index 000000000..c251ea0e4 --- /dev/null +++ b/modules/ticket/back/models/boxing.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/boxing/getVideo')(Self); + require('../methods/boxing/getVideoList')(Self); +}; diff --git a/modules/ticket/back/models/boxing.json b/modules/ticket/back/models/boxing.json new file mode 100644 index 000000000..5aecbcabe --- /dev/null +++ b/modules/ticket/back/models/boxing.json @@ -0,0 +1,12 @@ +{ + "name": "Boxing", + "base": "PersistedModel", + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} diff --git a/modules/ticket/back/models/packingSiteConfig.json b/modules/ticket/back/models/packingSiteConfig.json new file mode 100644 index 000000000..8f0032d41 --- /dev/null +++ b/modules/ticket/back/models/packingSiteConfig.json @@ -0,0 +1,28 @@ +{ + "name": "PackingSiteConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "packingSiteConfig" + } + }, + "properties": { + "id": { + "id": true, + "type": "number", + "description": "Identifier" + }, + "shinobiUrl": { + "type": "string" + }, + "shinobiGroupKey":{ + "type":"string" + }, + "shinobiToken":{ + "type":"string" + }, + "avgBoxingTime":{ + "type":"number" + } + } +} diff --git a/modules/ticket/front/boxing/index.html b/modules/ticket/front/boxing/index.html new file mode 100644 index 000000000..7fb3b870e --- /dev/null +++ b/modules/ticket/front/boxing/index.html @@ -0,0 +1,2 @@ + + diff --git a/modules/ticket/front/boxing/index.js b/modules/ticket/front/boxing/index.js new file mode 100644 index 000000000..4e6b398f2 --- /dev/null +++ b/modules/ticket/front/boxing/index.js @@ -0,0 +1,21 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + } + + async $onInit() { + const url = await this.vnApp.getUrl(`ticket/${this.$params.id}/boxing`); + window.open(url).focus(); + } +} + +ngModule.vnComponent('vnTicketBoxing', { + template: require('./index.html'), + controller: Controller, + bindings: { + ticket: '<' + } +}); diff --git a/modules/ticket/front/index.js b/modules/ticket/front/index.js index 85a03ffb6..0558d251d 100644 --- a/modules/ticket/front/index.js +++ b/modules/ticket/front/index.js @@ -33,3 +33,4 @@ import './dms/index'; import './dms/create'; import './dms/edit'; import './sms'; +import './boxing'; diff --git a/modules/ticket/front/locale/es.yml b/modules/ticket/front/locale/es.yml index 752dd7b36..748ba210f 100644 --- a/modules/ticket/front/locale/es.yml +++ b/modules/ticket/front/locale/es.yml @@ -4,6 +4,7 @@ Agency: Agencia Amount: Importe Base to commission: Base comisionable Boxes: Cajas +Boxing: Encajado by: por Checked: Comprobado Client: Cliente @@ -45,7 +46,7 @@ Price gap: Diferencia de precio Quantity: Cantidad Remove lines: Eliminar lineas Route: Ruta -SET OK: PONER OK +SET OK: PONER OK Shipment: Salida Shipped: F. envío Some fields are invalid: Algunos campos no son válidos @@ -81,4 +82,4 @@ Sale tracking: Líneas preparadas Pictures: Fotos Log: Historial Packager: Encajador -Palletizer: Palletizador \ No newline at end of file +Palletizer: Palletizador diff --git a/modules/ticket/front/routes.json b/modules/ticket/front/routes.json index ba7cfa887..4be8e2183 100644 --- a/modules/ticket/front/routes.json +++ b/modules/ticket/front/routes.json @@ -24,7 +24,8 @@ {"state": "ticket.card.saleChecked", "icon": "assignment"}, {"state": "ticket.card.components", "icon": "icon-components"}, {"state": "ticket.card.saleTracking", "icon": "assignment"}, - {"state": "ticket.card.dms.index", "icon": "cloud_download"} + {"state": "ticket.card.dms.index", "icon": "cloud_download"}, + {"state": "ticket.card.boxing", "icon": "science"} ] }, "keybindings": [ @@ -66,7 +67,7 @@ "abstract": true, "params": { "ticket": "$ctrl.ticket" - } + } }, { "url" : "/step-one", @@ -273,6 +274,15 @@ "params": { "ticket": "$ctrl.ticket" } + }, + { + "url": "/boxing", + "state": "ticket.card.boxing", + "component": "vn-ticket-boxing", + "description": "Boxing", + "params": { + "ticket": "$ctrl.ticket" + } } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index 26c164832..f85af904c 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "node-ssh": "^11.0.0", "object-diff": "0.0.4", "object.pick": "^1.3.0", - "puppeteer": "^18.0.5", + "puppeteer": "^19.0.0", "read-chunk": "^3.2.0", "require-yaml": "0.0.1", "sharp": "^0.31.0",