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-acl_receiptPdf.sql b/db/changes/10490-august/00-acl_receiptPdf.sql new file mode 100644 index 000000000..42f84b87d --- /dev/null +++ b/db/changes/10490-august/00-acl_receiptPdf.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Receipt', 'receiptPdf', '*', 'ALLOW', 'ROLE', 'salesAssistant'); 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-ACL_workerDisableExcluded.sql b/db/changes/10491-august/00-ACL_workerDisableExcluded.sql new file mode 100644 index 000000000..2fd9e8b12 --- /dev/null +++ b/db/changes/10491-august/00-ACL_workerDisableExcluded.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalId) + VALUES ('WorkerDisableExcluded','*','*','ALLOW','hr'); \ No newline at end of file diff --git a/db/changes/10491-august/00-defaultPayDem_sameAs_production.sql b/db/changes/10491-august/00-defaultPayDem_sameAs_production.sql new file mode 100644 index 000000000..294247338 --- /dev/null +++ b/db/changes/10491-august/00-defaultPayDem_sameAs_production.sql @@ -0,0 +1,2 @@ +INSERT INTO `vn`.`payDem` (id,payDem) + VALUES (7,'0'); diff --git a/db/changes/10491-august/00-newSupplier_ACL.sql b/db/changes/10491-august/00-newSupplier_ACL.sql new file mode 100644 index 000000000..c88f3de3f --- /dev/null +++ b/db/changes/10491-august/00-newSupplier_ACL.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (model,property,accessType,principalId) + VALUES ('Supplier','newSupplier','WRITE','administrative'); 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/changes/10491-august/00-payMethodFk_Allow_Null.sql b/db/changes/10491-august/00-payMethodFk_Allow_Null.sql new file mode 100644 index 000000000..6d9931d3c --- /dev/null +++ b/db/changes/10491-august/00-payMethodFk_Allow_Null.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`supplier` MODIFY COLUMN payMethodFk tinyint(3) unsigned NULL; \ No newline at end of file diff --git a/db/changes/10491-august/00-supplierActivityFk_Allow_Null.sql b/db/changes/10491-august/00-supplierActivityFk_Allow_Null.sql new file mode 100644 index 000000000..62aac0556 --- /dev/null +++ b/db/changes/10491-august/00-supplierActivityFk_Allow_Null.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`supplier` MODIFY COLUMN supplierActivityFk varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL NULL; \ No newline at end of file diff --git a/db/changes/10491-august/ticket_closeByTicket.sql b/db/changes/10491-august/00-ticket_closeByTicket.sql similarity index 85% rename from db/changes/10491-august/ticket_closeByTicket.sql rename to db/changes/10491-august/00-ticket_closeByTicket.sql index 25b04f629..f378b1146 100644 --- a/db/changes/10491-august/ticket_closeByTicket.sql +++ b/db/changes/10491-august/00-ticket_closeByTicket.sql @@ -1,7 +1,9 @@ drop procedure `vn`.`ticket_closeByTicket`; +DELIMITER $$ +$$ create - definer = root@localhost procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int) + definer = `root`@`localhost` procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int) BEGIN /** @@ -27,5 +29,7 @@ BEGIN CALL ticket_close(); DROP TEMPORARY TABLE tmp.ticket_close; -END; +END$$ +DELIMITER ; + diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 7e59c1a54..5cadaf77c 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; @@ -918,21 +918,21 @@ INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`) (3, 'Perdida', 'LOST'); -INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`) +INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`, `hostFk`) 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`) @@ -1380,13 +1380,6 @@ INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed (7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 7', 0, 0, 'this is the note seven', 'observation seven'), (8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 8', 1, 1, '', ''); -INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`) - VALUES - (1101, 500, NULL, 0.00, 0.00, 1.00), - (1102, 1000, 2.00, 0.01, 0.05, 1.00), - (1103, 2000, 0.00, 0.00, 0.02, 1.00), - (1104, 2500, 150.00, 0.02, 0.10, 1.00); - INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeFk`, `saleTotal`, `saleWaste`, `rate`) VALUES ('CharlesXavier', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', 1, 1, '1062', '51', '4.8'), @@ -1743,12 +1736,12 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`, ( 6, 'mana', 'Mana', 1, 4, 0), ( 7, 'lack', 'Faltas', 1, 2, 0); -INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`) +INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`, `rma`) VALUES - (1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0), - (2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1), - (3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5), - (4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10); + (1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0, '02676A049183'), + (2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1, NULL), + (3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5, NULL), + (4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10, NULL); INSERT INTO `vn`.`claimObservation` (`claimFk`, `workerFk`, `text`, `created`) VALUES @@ -1790,6 +1783,23 @@ INSERT INTO `vn`.`claimConfig`(`id`, `pickupContact`, `maxResponsibility`) (1, 'Contact description', 50), (2, 'Contact description', 30); +INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`) + VALUES + (1101, 500, NULL, 0.00, 0.00, 1.00), + (1102, 1000, 2.00, 0.01, 0.05, 1.00), + (1103, 2000, 0.00, 0.00, 0.02, 1.00), + (1104, 2500, 150.00, 0.02, 0.10, 1.00); + +INSERT INTO vn.claimRma (`id`, `code`, `created`, `workerFk`) +VALUES + (1, '02676A049183', DEFAULT, 1106), + (2, '02676A049183', DEFAULT, 1106), + (3, '02676A049183', DEFAULT, 1107), + (4, '02676A049183', DEFAULT, 1107), + (5, '01837B023653', DEFAULT, 1106); + + + INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`) VALUES (1, 'Arkham Bank', 442, 1, 'h12387193H10238'), @@ -2648,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/13-supplier/03_fiscal_data.spec.js b/e2e/paths/13-supplier/03_fiscal_data.spec.js index 0238c8704..4f9581e32 100644 --- a/e2e/paths/13-supplier/03_fiscal_data.spec.js +++ b/e2e/paths/13-supplier/03_fiscal_data.spec.js @@ -31,7 +31,7 @@ describe('Supplier fiscal data path', () => { await page.clearInput(selectors.supplierFiscalData.taxNumber); await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number'); await page.clearInput(selectors.supplierFiscalData.account); - await page.write(selectors.supplierFiscalData.account, 'edited account number'); + await page.write(selectors.supplierFiscalData.account, '0123456789'); await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva'); await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas'); @@ -70,7 +70,7 @@ describe('Supplier fiscal data path', () => { it('should check the account was edited', async() => { const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value'); - expect(result).toEqual('edited account number'); + expect(result).toEqual('0123456789'); }); it('should check the sageWihholding was edited', async() => { 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.js b/modules/claim/back/models/claim-rma.js new file mode 100644 index 000000000..6a93613bd --- /dev/null +++ b/modules/claim/back/models/claim-rma.js @@ -0,0 +1,9 @@ +const LoopBackContext = require('loopback-context'); + +module.exports = Self => { + Self.observe('before save', async function(ctx) { + const changes = ctx.data || ctx.instance; + const loopBackContext = LoopBackContext.getCurrentContext(); + changes.workerFk = loopBackContext.active.accessToken.userId; + }); +}; diff --git a/modules/claim/back/models/claim-rma.json b/modules/claim/back/models/claim-rma.json index 24c17a234..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": { @@ -23,7 +23,7 @@ "relations": { "worker": { "type": "belongsTo", - "model": "worker", + "model": "Worker", "foreignKey": "workerFk" } } 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/methods/client/checkDuplicated.js b/modules/client/back/methods/client/checkDuplicated.js index acaffbf42..522cd088f 100644 --- a/modules/client/back/methods/client/checkDuplicated.js +++ b/modules/client/back/methods/client/checkDuplicated.js @@ -25,10 +25,9 @@ module.exports = Self => { const client = await Self.app.models.Client.findById(id, myOptions); - const emails = client.email ? client.email.split(',') : null; - const findParams = []; - if (emails.length) { + if (client.email) { + const emails = client.email.split(','); for (let email of emails) findParams.push({email: email}); } diff --git a/modules/client/back/methods/receipt/receiptPdf.js b/modules/client/back/methods/receipt/receiptPdf.js new file mode 100644 index 000000000..f55e05040 --- /dev/null +++ b/modules/client/back/methods/receipt/receiptPdf.js @@ -0,0 +1,55 @@ +const {Report} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('receiptPdf', { + description: 'Returns the receipt pdf', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The claim id', + http: {source: 'path'} + }, + { + arg: 'recipientId', + type: 'number', + description: 'The recipient id', + required: false + } + ], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, { + arg: 'Content-Type', + type: 'String', + http: {target: 'header'} + }, { + arg: 'Content-Disposition', + type: 'String', + http: {target: 'header'} + } + ], + http: { + path: '/:id/receipt-pdf', + verb: 'GET' + } + }); + + Self.receiptPdf = async(ctx, id) => { + const args = Object.assign({}, ctx.args); + const params = {lang: ctx.req.getLocale()}; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const report = new Report('receipt', params); + const stream = await report.toPdfStream(); + + return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; + }; +}; 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/client/back/models/receipt.js b/modules/client/back/models/receipt.js index 36a4a8952..b79102e6b 100644 --- a/modules/client/back/models/receipt.js +++ b/modules/client/back/models/receipt.js @@ -2,6 +2,7 @@ const LoopBackContext = require('loopback-context'); module.exports = function(Self) { require('../methods/receipt/filter')(Self); + require('../methods/receipt/receiptPdf')(Self); Self.validateBinded('amountPaid', isNotZero, { message: 'Amount cannot be zero', diff --git a/modules/client/front/balance/create/index.js b/modules/client/front/balance/create/index.js index c6a6e7ff9..935129574 100644 --- a/modules/client/front/balance/create/index.js +++ b/modules/client/front/balance/create/index.js @@ -144,12 +144,8 @@ class Controller extends Dialog { }) .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))) .then(() => { - if (this.viewReceipt) { - this.vnReport.show('receipt', { - receiptId: receiptId, - companyId: this.companyFk - }); - } + if (this.viewReceipt) + this.vnReport.show(`Receipts/${receiptId}/receipt-pdf`); }); } diff --git a/modules/client/front/balance/create/index.spec.js b/modules/client/front/balance/create/index.spec.js index 77fe32e0f..fa6b48ea4 100644 --- a/modules/client/front/balance/create/index.spec.js +++ b/modules/client/front/balance/create/index.spec.js @@ -85,6 +85,8 @@ describe('Client', () => { }); it('should make an http POST query and then call to the report show() method', () => { + const receiptId = 1; + jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnReport, 'show'); window.open = jest.fn(); @@ -92,14 +94,12 @@ describe('Client', () => { controller.$params = {id: 1101}; controller.viewReceipt = true; - $httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: 1}); + $httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: receiptId}); controller.responseHandler('accept'); $httpBackend.flush(); - const expectedParams = {receiptId: 1, companyId: 442}; - expect(controller.vnApp.showSuccess).toHaveBeenCalled(); - expect(controller.vnReport.show).toHaveBeenCalledWith('receipt', expectedParams); + expect(controller.vnReport.show).toHaveBeenCalledWith(`Receipts/${receiptId}/receipt-pdf`); }); }); diff --git a/modules/supplier/back/methods/supplier/filter.js b/modules/supplier/back/methods/supplier/filter.js index 3500afacd..0b473f7df 100644 --- a/modules/supplier/back/methods/supplier/filter.js +++ b/modules/supplier/back/methods/supplier/filter.js @@ -95,8 +95,8 @@ module.exports = Self => { pm.name AS payMethod, pd.payDem AS payDem FROM vn.supplier s - JOIN vn.payMethod pm ON pm.id = s.payMethodFk - JOIN vn.payDem pd ON pd.id = s.payDemFk` + LEFT JOIN vn.payMethod pm ON pm.id = s.payMethodFk + LEFT JOIN vn.payDem pd ON pd.id = s.payDemFk` ); stmt.merge(conn.makeSuffix(filter)); diff --git a/modules/supplier/back/methods/supplier/newSupplier.js b/modules/supplier/back/methods/supplier/newSupplier.js new file mode 100644 index 000000000..f4059a259 --- /dev/null +++ b/modules/supplier/back/methods/supplier/newSupplier.js @@ -0,0 +1,45 @@ +module.exports = Self => { + Self.remoteMethod('newSupplier', { + description: 'Creates a new supplier and returns it', + accessType: 'WRITE', + accepts: [{ + arg: 'params', + type: 'object', + http: {source: 'body'} + }], + returns: { + type: 'string', + root: true + }, + http: { + path: `/newSupplier`, + verb: 'POST' + } + }); + + Self.newSupplier = async params => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof(params) == 'string') + params = JSON.parse(params); + + params.nickname = params.name; + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const supplier = await models.Supplier.create(params, myOptions); + + if (tx) await tx.commit(); + + return supplier; + } catch (e) { + if (tx) await tx.rollback(); + return params; + } + }; +}; diff --git a/modules/supplier/back/methods/supplier/specs/filter.spec.js b/modules/supplier/back/methods/supplier/specs/filter.spec.js index 1f74b10ff..2620bb687 100644 --- a/modules/supplier/back/methods/supplier/specs/filter.spec.js +++ b/modules/supplier/back/methods/supplier/specs/filter.spec.js @@ -10,7 +10,7 @@ describe('Supplier filter()', () => { let result = await app.models.Supplier.filter(ctx); - expect(result.length).toEqual(1); + expect(result.length).toBeGreaterThanOrEqual(1); expect(result[0].id).toEqual(1); }); diff --git a/modules/supplier/back/methods/supplier/specs/newSupplier.spec.js b/modules/supplier/back/methods/supplier/specs/newSupplier.spec.js new file mode 100644 index 000000000..8f22a4f20 --- /dev/null +++ b/modules/supplier/back/methods/supplier/specs/newSupplier.spec.js @@ -0,0 +1,30 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); + +describe('Supplier newSupplier()', () => { + const newSupp = { + name: 'TestSupplier-1' + }; + const administrativeId = 5; + + it('should create a new supplier containing only the name', async() => { + const activeCtx = { + accessToken: {userId: administrativeId}, + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + + let result = await app.models.Supplier.newSupplier(JSON.stringify(newSupp)); + + expect(result.name).toEqual('TestSupplier-1'); + expect(result.id).toEqual(443); + + const createdSupplier = await app.models.Supplier.findById(result.id); + + expect(createdSupplier.id).toEqual(result.id); + expect(createdSupplier.name).toEqual(result.name); + expect(createdSupplier.payDemFk).toEqual(7); + expect(createdSupplier.nickname).toEqual(result.name); + }); +}); diff --git a/modules/supplier/back/models/supplier.js b/modules/supplier/back/models/supplier.js index c9af7b297..44549c65c 100644 --- a/modules/supplier/back/models/supplier.js +++ b/modules/supplier/back/models/supplier.js @@ -10,6 +10,7 @@ module.exports = Self => { require('../methods/supplier/freeAgencies')(Self); require('../methods/supplier/campaignMetricsPdf')(Self); require('../methods/supplier/campaignMetricsEmail')(Self); + require('../methods/supplier/newSupplier')(Self); Self.validatesPresenceOf('name', { message: 'The social name cannot be empty' @@ -19,13 +20,17 @@ module.exports = Self => { message: 'The supplier name must be unique' }); - Self.validatesPresenceOf('city', { - message: 'City cannot be empty' - }); + if (this.city) { + Self.validatesPresenceOf('city', { + message: 'City cannot be empty' + }); + } - Self.validatesPresenceOf('nif', { - message: 'The nif cannot be empty' - }); + if (this.nif) { + Self.validatesPresenceOf('nif', { + message: 'The nif cannot be empty' + }); + } Self.validatesUniquenessOf('nif', { message: 'TIN must be unique' @@ -57,6 +62,9 @@ module.exports = Self => { } async function tinIsValid(err, done) { + if (!this.countryFk) + return done(); + const filter = { fields: ['code'], where: {id: this.countryFk} @@ -80,6 +88,7 @@ module.exports = Self => { }); async function hasSupplierAccount(err, done) { + if (!this.payMethodFk) return done(); const payMethod = await Self.app.models.PayMethod.findById(this.payMethodFk); const supplierAccount = await Self.app.models.SupplierAccount.findOne({where: {supplierFk: this.id}}); const hasIban = supplierAccount && supplierAccount.iban; @@ -92,6 +101,7 @@ module.exports = Self => { } Self.observe('before save', async function(ctx) { + if (ctx.isNewInstance) return; const loopbackContext = LoopBackContext.getCurrentContext(); const changes = ctx.data || ctx.instance; const orgData = ctx.currentInstance; @@ -101,7 +111,7 @@ module.exports = Self => { const isPayMethodChecked = changes.isPayMethodChecked || orgData.isPayMethodChecked; const hasChanges = orgData && changes; const isPayMethodCheckedChanged = hasChanges - && orgData.isPayMethodChecked != isPayMethodChecked; + && orgData.isPayMethodChecked != isPayMethodChecked; if (isNotFinancial && isPayMethodCheckedChanged) throw new UserError('You can not modify is pay method checked'); @@ -114,7 +124,7 @@ module.exports = Self => { const socialName = changes.name || orgData.name; const hasChanges = orgData && changes; const socialNameChanged = hasChanges - && orgData.socialName != socialName; + && orgData.socialName != socialName; if ((socialNameChanged) && !isAlpha(socialName)) throw new UserError('The social name has an invalid format'); diff --git a/modules/supplier/front/create/index.html b/modules/supplier/front/create/index.html new file mode 100644 index 000000000..446a16cb6 --- /dev/null +++ b/modules/supplier/front/create/index.html @@ -0,0 +1,29 @@ + + +
+ + + + + + + + + + + + +
diff --git a/modules/supplier/front/create/index.js b/modules/supplier/front/create/index.js new file mode 100644 index 000000000..c33367dac --- /dev/null +++ b/modules/supplier/front/create/index.js @@ -0,0 +1,23 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + } + + onSubmit() { + this.$.watcher.submit().then( + json => { + this.$state.go(`supplier.card.fiscalData`, {id: json.data.id}); + } + ); + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnSupplierCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/supplier/front/index.js b/modules/supplier/front/index.js index ba2768854..9216d0781 100644 --- a/modules/supplier/front/index.js +++ b/modules/supplier/front/index.js @@ -20,3 +20,4 @@ import './address/create'; import './address/edit'; import './agency-term/index'; import './agency-term/create'; +import './create/index'; diff --git a/modules/supplier/front/index/index.html b/modules/supplier/front/index/index.html index 87e822eea..49f38cb1b 100644 --- a/modules/supplier/front/index/index.html +++ b/modules/supplier/front/index/index.html @@ -58,4 +58,7 @@ - \ No newline at end of file + + + + \ No newline at end of file diff --git a/modules/supplier/front/index/locale/es.yml b/modules/supplier/front/index/locale/es.yml index ad8a4f0bb..ce06f462c 100644 --- a/modules/supplier/front/index/locale/es.yml +++ b/modules/supplier/front/index/locale/es.yml @@ -2,4 +2,5 @@ Payment deadline: Plazo de pago Pay day: Dia de pago Account: Cuenta Pay method: Metodo de pago -Tax number: Nif \ No newline at end of file +Tax number: Nif +New supplier: Nuevo proveedor \ No newline at end of file diff --git a/modules/supplier/front/routes.json b/modules/supplier/front/routes.json index 61420b40d..75b8213cb 100644 --- a/modules/supplier/front/routes.json +++ b/modules/supplier/front/routes.json @@ -30,7 +30,7 @@ "abstract": true, "component": "vn-supplier", "description": "Suppliers" - }, + }, { "url": "/index?q", "state": "supplier.index", @@ -51,6 +51,13 @@ "params": { "supplier": "$ctrl.supplier" } + }, + { + "url": "/create", + "state": "supplier.create", + "component": "vn-supplier-create", + "acl": ["administrative"], + "description": "New supplier" }, { "url": "/basic-data", 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/modules/worker/back/model-config.json b/modules/worker/back/model-config.json index c155e331d..8c11c0d71 100644 --- a/modules/worker/back/model-config.json +++ b/modules/worker/back/model-config.json @@ -64,6 +64,9 @@ }, "WorkerTimeControlMail": { "dataSource": "vn" + }, + "WorkerDisableExcluded": { + "dataSource": "vn" } } diff --git a/modules/worker/back/models/workerDisableExcluded.json b/modules/worker/back/models/workerDisableExcluded.json new file mode 100644 index 000000000..48083748d --- /dev/null +++ b/modules/worker/back/models/workerDisableExcluded.json @@ -0,0 +1,26 @@ +{ + "name": "WorkerDisableExcluded", + "base": "VnModel", + "options": { + "mysql": { + "table": "workerDisableExcluded" + } + }, + "properties": { + "workerFk": { + "id": true, + "type": "number" + }, + "dated": { + "type": "date" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index 01681ebb8..58ac3d9e6 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -14,6 +14,20 @@ + + + Click to exclude the user from getting disabled + + + Click to allow the user to be disabled + +
+
+ + +