From d24f04912e70b826097335f4baae5ef832855b79 Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Tue, 26 May 2020 14:37:34 +0200 Subject: [PATCH 01/11] 2205 - Prevent relations to being logged in --- db/changes/10180-holyWeek/00-ACL.sql | 1 + loopback/common/models/loggable.js | 129 +++++++++++++++++-------- modules/worker/front/log/locale/es.yml | 1 + modules/worker/front/routes.json | 3 +- 4 files changed, 92 insertions(+), 42 deletions(-) create mode 100644 db/changes/10180-holyWeek/00-ACL.sql diff --git a/db/changes/10180-holyWeek/00-ACL.sql b/db/changes/10180-holyWeek/00-ACL.sql new file mode 100644 index 0000000000..d4b961e22a --- /dev/null +++ b/db/changes/10180-holyWeek/00-ACL.sql @@ -0,0 +1 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('WorkerLog', '*', 'READ', 'ALLOW', 'ROLE', 'hr'); \ No newline at end of file diff --git a/loopback/common/models/loggable.js b/loopback/common/models/loggable.js index d9116a0de9..61a5716076 100644 --- a/loopback/common/models/loggable.js +++ b/loopback/common/models/loggable.js @@ -2,6 +2,7 @@ const pick = require('object.pick'); const LoopBackContext = require('loopback-context'); module.exports = function(Self) { + // const relations = ctx.Model.relations; Self.setup = function() { Self.super_.setup.call(this); }; @@ -12,23 +13,33 @@ module.exports = function(Self) { }); Self.observe('before save', async function(ctx) { - let options = {}; + const appModels = ctx.Model.app.models; + const options = {}; + + // Check for transactions if (ctx.options && ctx.options.transaction) options.transaction = ctx.options.transaction; let oldInstance; - let oldInstanceFk; let newInstance; if (ctx.data) { - oldInstanceFk = pick(ctx.currentInstance, Object.keys(ctx.data)); + const changes = pick(ctx.currentInstance, Object.keys(ctx.data)); newInstance = await fkToValue(ctx.data, ctx); - oldInstance = await fkToValue(oldInstanceFk, ctx); + oldInstance = await fkToValue(changes, ctx); + if (ctx.where && !ctx.currentInstance) { - let fields = Object.keys(ctx.data); - ctx.oldInstances = await ctx.Model.app.models[ctx.Model.definition.name].find({where: ctx.where, fields: fields}, options); + const fields = Object.keys(ctx.data); + const modelName = modelDef.name; + + ctx.oldInstances = await appModels[modelName].find({ + where: ctx.where, + fields: fields + }, options); } } + + // Get changes from created instance if (ctx.isNewInstance) newInstance = await fkToValue(ctx.instance.__data, ctx); @@ -37,18 +48,24 @@ module.exports = function(Self) { }); Self.observe('before delete', async function(ctx) { + const appModels = ctx.Model.app.models; + const definition = ctx.Model.definition; + const relations = ctx.Model.relations; + let options = {}; if (ctx.options && ctx.options.transaction) options.transaction = ctx.options.transaction; if (ctx.where) { - let affectedModel = ctx.Model.definition.name; - let definition = ctx.Model.definition; - let deletedInstances = await ctx.Model.app.models[affectedModel].find({where: ctx.where}, options); + let affectedModel = definition.name; + let deletedInstances = await appModels[affectedModel].find({ + where: ctx.where + }, options); + let relation = definition.settings.log.relation; if (relation) { - let primaryKey = ctx.Model.relations[relation].keyFrom; + let primaryKey = relations[relation].keyFrom; let arrangedDeletedInstances = []; for (let i = 0; i < deletedInstances.length; i++) { @@ -69,6 +86,8 @@ module.exports = function(Self) { }); async function logDeletedInstances(ctx, loopBackContext) { + const appModels = ctx.Model.app.models; + const modelDef = ctx.Model.definition; let options = {}; if (ctx.options && ctx.options.transaction) options.transaction = ctx.options.transaction; @@ -78,14 +97,14 @@ module.exports = function(Self) { if (loopBackContext) userFk = loopBackContext.active.accessToken.userId; - let definition = ctx.Model.definition; + let definition = modelDef; let changedModelValue = definition.settings.log.changedModelValue; let logRecord = { originFk: instance.originFk, userFk: userFk, action: 'delete', - changedModel: ctx.Model.definition.name, + changedModel: modelDef.name, changedModelId: instance.id, changedModelValue: instance[changedModelValue], oldInstance: instance, @@ -95,26 +114,44 @@ module.exports = function(Self) { delete instance.originFk; let logModel = definition.settings.log.model; - await ctx.Model.app.models[logModel].create(logRecord, options); + await appModels[logModel].create(logRecord, options); }); } + // Get log values from a foreign key async function fkToValue(instance, ctx) { + const appModels = ctx.Model.app.models; + const relations = ctx.Model.relations; let options = {}; + + // Check for transactions if (ctx.options && ctx.options.transaction) options.transaction = ctx.options.transaction; - let cleanInstance = JSON.parse(JSON.stringify(instance)); - let result = {}; - for (let key in cleanInstance) { - let val = cleanInstance[key]; - if (val === undefined || val === null) continue; - for (let key1 in ctx.Model.relations) { - let val1 = ctx.Model.relations[key1]; - if (val1.keyFrom == key && key != 'id') { - let recordSet = await ctx.Model.app.models[val1.modelTo.modelName].findById(val, null, options); + const instanceCopy = JSON.parse(JSON.stringify(instance)); + const result = {}; + for (const key in instanceCopy) { + let value = instanceCopy[key]; + + if (value instanceof Object) + continue; + + if (value === undefined || value === null) continue; + + for (let relationName in relations) { + const relation = relations[relationName]; + if (relation.keyFrom == key && key != 'id') { + const model = relation.modelTo; + const modelName = relation.modelTo.modelName; + const properties = model && model.definition.properties; + const settings = model && model.definition.settings; + + const recordSet = await appModels[modelName].findById(value, null, options); + + const hasShowField = settings.log && settings.log.showField; + let showField = hasShowField && recordSet + && recordSet[settings.log.showField]; - let showField = val1.modelTo && val1.modelTo.definition.settings.log && val1.modelTo.definition.settings.log.showField && recordSet && recordSet[val1.modelTo.definition.settings.log.showField]; if (!showField) { const showFieldNames = [ 'name', @@ -122,7 +159,10 @@ module.exports = function(Self) { 'code' ]; for (field of showFieldNames) { - if (val1.modelTo.definition.properties && val1.modelTo.definition.properties[field] && recordSet && recordSet[field]) { + const propField = properties && properties[field]; + const recordField = recordSet && recordSet[field]; + + if (propField && recordField) { showField = field; break; } @@ -130,25 +170,29 @@ module.exports = function(Self) { } if (showField && recordSet && recordSet[showField]) { - val = recordSet[showField]; + value = recordSet[showField]; break; } - val = recordSet && recordSet.id || val; + value = recordSet && recordSet.id || value; break; } } - result[key] = val; + result[key] = value; } return result; } async function logInModel(ctx, loopBackContext) { - let options = {}; + const appModels = ctx.Model.app.models; + const definition = ctx.Model.definition; + const defSettings = ctx.Model.definition.settings; + const relations = ctx.Model.relations; + + const options = {}; if (ctx.options && ctx.options.transaction) options.transaction = ctx.options.transaction; - let definition = ctx.Model.definition; let primaryKey; for (let property in definition.properties) { if (definition.properties[property].id) { @@ -163,11 +207,11 @@ module.exports = function(Self) { // RELATIONS LOG let changedModelId; - if (ctx.instance && !definition.settings.log.relation) { + if (ctx.instance && !defSettings.log.relation) { originId = ctx.instance.id; changedModelId = ctx.instance.id; - } else if (definition.settings.log.relation) { - primaryKey = ctx.Model.relations[definition.settings.log.relation].keyFrom; + } else if (defSettings.log.relation) { + primaryKey = relations[defSettings.log.relation].keyFrom; if (ctx.where && ctx.where[primaryKey]) originId = ctx.where[primaryKey]; @@ -181,12 +225,16 @@ module.exports = function(Self) { } // Sets the changedModelValue to save and the instances changed in case its an updateAll - let showField = definition.settings.log.showField; + let showField = defSettings.log.showField; let where; if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) { changedModelId = []; where = []; - let changedInstances = await ctx.Model.app.models[definition.name].find({where: ctx.where, fields: ['id', showField, primaryKey]}, options); + let changedInstances = await appModels[definition.name].find({ + where: ctx.where, + fields: ['id', showField, primaryKey] + }, options); + changedInstances.forEach(element => { where.push(element[showField]); changedModelId.push(element.id); @@ -195,7 +243,6 @@ module.exports = function(Self) { } else if (ctx.hookState.oldInstance) where = ctx.instance[showField]; - // Set oldInstance, newInstance, userFk and action let oldInstance = {}; if (ctx.hookState.oldInstance) @@ -211,14 +258,14 @@ module.exports = function(Self) { let action = setActionType(ctx); - removeUnloggableProperties(definition, oldInstance); - removeUnloggableProperties(definition, newInstance); + removeUnloggable(definition, oldInstance); + removeUnloggable(definition, newInstance); let logRecord = { originFk: originId, userFk: userFk, action: action, - changedModel: ctx.Model.definition.name, + changedModel: definition.name, changedModelId: changedModelId, // Model property with an different data type will throw a NaN error changedModelValue: where, oldInstance: oldInstance, @@ -226,9 +273,9 @@ module.exports = function(Self) { }; let logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx); - let logModel = definition.settings.log.model; + let logModel = defSettings.log.model; - await ctx.Model.app.models[logModel].create(logsToSave, options); + await appModels[logModel].create(logsToSave, options); } /** @@ -236,7 +283,7 @@ module.exports = function(Self) { * @param {*} definition Model definition * @param {*} properties Modified object properties */ - function removeUnloggableProperties(definition, properties) { + function removeUnloggable(definition, properties) { const propList = Object.keys(properties); const propDefs = new Map(); diff --git a/modules/worker/front/log/locale/es.yml b/modules/worker/front/log/locale/es.yml index d9c204e55a..c48c571acf 100644 --- a/modules/worker/front/log/locale/es.yml +++ b/modules/worker/front/log/locale/es.yml @@ -1,3 +1,4 @@ +Date: Fecha Model: Modelo Action: Acción Author: Autor diff --git a/modules/worker/front/routes.json b/modules/worker/front/routes.json index 8447ef9c4b..7825e97359 100644 --- a/modules/worker/front/routes.json +++ b/modules/worker/front/routes.json @@ -62,7 +62,8 @@ "url" : "/log", "state": "worker.card.workerLog", "component": "vn-worker-log", - "description": "Log" + "description": "Log", + "acl": ["hr"] }, { "url": "/pbx", "state": "worker.card.pbx", From 8384918d9ee91751e29b558375d8b58a233acff0 Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Tue, 26 May 2020 14:42:45 +0200 Subject: [PATCH 02/11] Removed line --- loopback/common/models/loggable.js | 1 - 1 file changed, 1 deletion(-) diff --git a/loopback/common/models/loggable.js b/loopback/common/models/loggable.js index 61a5716076..fac02ed1cc 100644 --- a/loopback/common/models/loggable.js +++ b/loopback/common/models/loggable.js @@ -2,7 +2,6 @@ const pick = require('object.pick'); const LoopBackContext = require('loopback-context'); module.exports = function(Self) { - // const relations = ctx.Model.relations; Self.setup = function() { Self.super_.setup.call(this); }; From 3cd4ab086dce3969a125ad979389b6a29d7373d7 Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Tue, 26 May 2020 15:49:20 +0200 Subject: [PATCH 03/11] Fix --- loopback/common/models/loggable.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/loopback/common/models/loggable.js b/loopback/common/models/loggable.js index fac02ed1cc..ec6989717b 100644 --- a/loopback/common/models/loggable.js +++ b/loopback/common/models/loggable.js @@ -13,6 +13,7 @@ module.exports = function(Self) { Self.observe('before save', async function(ctx) { const appModels = ctx.Model.app.models; + const definition = ctx.Model.definition; const options = {}; // Check for transactions @@ -29,7 +30,7 @@ module.exports = function(Self) { if (ctx.where && !ctx.currentInstance) { const fields = Object.keys(ctx.data); - const modelName = modelDef.name; + const modelName = definition.name; ctx.oldInstances = await appModels[modelName].find({ where: ctx.where, @@ -86,7 +87,7 @@ module.exports = function(Self) { async function logDeletedInstances(ctx, loopBackContext) { const appModels = ctx.Model.app.models; - const modelDef = ctx.Model.definition; + const definition = ctx.Model.definition; let options = {}; if (ctx.options && ctx.options.transaction) options.transaction = ctx.options.transaction; @@ -96,14 +97,12 @@ module.exports = function(Self) { if (loopBackContext) userFk = loopBackContext.active.accessToken.userId; - let definition = modelDef; - let changedModelValue = definition.settings.log.changedModelValue; let logRecord = { originFk: instance.originFk, userFk: userFk, action: 'delete', - changedModel: modelDef.name, + changedModel: definition.name, changedModelId: instance.id, changedModelValue: instance[changedModelValue], oldInstance: instance, From 2345a922c894419464bf3360e1830479ca6118de Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Wed, 27 May 2020 13:11:41 +0200 Subject: [PATCH 04/11] now we can create thermographs for travelThermograph --- db/changes/10190-PostErte/00-ACL.sql | 1 + .../common/methods/vn-model/getEnumValues.js | 55 ++++++++++++++++ .../vn-model/specs/getEnumValues.spec.js | 18 ++++++ loopback/common/models/vn-model.js | 1 + loopback/locale/es.json | 2 +- .../back/methods/order/getSourceValues.js | 2 +- .../methods/thermograph/createThermograph.js | 60 ++++++++++++++++++ .../thermograph/getThermographModels.js | 18 ++++++ .../specs/createThermograph.spec.js | 49 +++++++++++++++ .../getThermographTemperatures.js | 18 ++++++ modules/travel/back/models/thermograph.js | 12 ++++ modules/travel/back/models/thermograph.json | 6 +- .../travel/back/models/travel-thermograph.js | 1 + .../back/models/travel-thermograph.json | 7 ++- .../front/thermograph/create/index.html | 62 +++++++++++++++++++ .../travel/front/thermograph/create/index.js | 18 ++++++ .../front/thermograph/create/index.spec.js | 25 ++++++++ .../travel/front/thermograph/locale/es.yml | 4 +- 18 files changed, 353 insertions(+), 6 deletions(-) create mode 100644 db/changes/10190-PostErte/00-ACL.sql create mode 100644 loopback/common/methods/vn-model/getEnumValues.js create mode 100644 loopback/common/methods/vn-model/specs/getEnumValues.spec.js create mode 100644 modules/travel/back/methods/thermograph/createThermograph.js create mode 100644 modules/travel/back/methods/thermograph/getThermographModels.js create mode 100644 modules/travel/back/methods/thermograph/specs/createThermograph.spec.js create mode 100644 modules/travel/back/methods/travel-thermograph/getThermographTemperatures.js create mode 100644 modules/travel/back/models/thermograph.js diff --git a/db/changes/10190-PostErte/00-ACL.sql b/db/changes/10190-PostErte/00-ACL.sql new file mode 100644 index 0000000000..d19901d481 --- /dev/null +++ b/db/changes/10190-PostErte/00-ACL.sql @@ -0,0 +1 @@ +UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213'; diff --git a/loopback/common/methods/vn-model/getEnumValues.js b/loopback/common/methods/vn-model/getEnumValues.js new file mode 100644 index 0000000000..0bc8f8eb6a --- /dev/null +++ b/loopback/common/methods/vn-model/getEnumValues.js @@ -0,0 +1,55 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + /** + * Returns a set of allowed values defined on table scheme + * @param {String} column - Model or Table column name + * @return {Array} - Array of set values + */ + Self.getEnumValues = async function(column) { + let model = this.app.models[this.modelName].definition; + let properties = model.properties; + let tableName = this.modelName; + let schema = null; + + if (model.settings && model.settings.mysql) { + let tableSplit = model.settings.mysql.table.split('.'); + tableName = tableSplit.pop(); + schema = tableSplit.pop() || null; + } + + let property = properties[column]; + + if (!property) + throw new UserError(`Column does not exist`); + + let columnName = property.mysql + ? property.mysql.columnName + : column; + + let columnInfo = await this.rawSql( + `SELECT column_type columnType + FROM information_schema.columns + WHERE table_name = ? + AND table_schema = IFNULL(?, DATABASE()) + AND column_name = ?`, + [tableName, schema, columnName] + ); + + if (!columnInfo || !columnInfo[0]) + throw new UserError(`Cannot fetch column values`); + + let setValues; + setValues = columnInfo[0].columnType + .replace(/^enum\((.*)\)$/i, '$1') + .replace(/'/g, '') + .match(new RegExp(/(\w+)+/, 'ig')); + + let values = []; + setValues.forEach(setValue => { + values.push({value: setValue}); + }); + + return values; + }; +}; diff --git a/loopback/common/methods/vn-model/specs/getEnumValues.spec.js b/loopback/common/methods/vn-model/specs/getEnumValues.spec.js new file mode 100644 index 0000000000..b49a952e4f --- /dev/null +++ b/loopback/common/methods/vn-model/specs/getEnumValues.spec.js @@ -0,0 +1,18 @@ +const app = require('vn-loopback/server/server'); + +describe('Model getEnumValues()', () => { + it('should extend getEnumValues properties to any model passed', () => { + let exampleModel = app.models.TravelThermograph; + + expect(exampleModel.getEnumValues).toBeDefined(); + }); + + it('should return an array of enum values from a given column', async() => { + let result = await app.models.TravelThermograph.getSetValues('temperature'); + + expect(result.length).toEqual(3); + expect(result[0].value).toEqual('enum'); + expect(result[1].value).toEqual('COOL'); + expect(result[2].value).toEqual('WARM'); + }); +}); diff --git a/loopback/common/models/vn-model.js b/loopback/common/models/vn-model.js index d65ca71df0..f56183df2e 100644 --- a/loopback/common/models/vn-model.js +++ b/loopback/common/models/vn-model.js @@ -6,6 +6,7 @@ module.exports = function(Self) { Self.ParameterizedSQL = ParameterizedSQL; require('../methods/vn-model/getSetValues')(Self); + require('../methods/vn-model/getEnumValues')(Self); Object.assign(Self, { setup() { diff --git a/loopback/locale/es.json b/loopback/locale/es.json index eeb18fb73c..d77b6d290f 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -132,5 +132,5 @@ "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", "This ticket is deleted": "Este ticket está eliminado", "A travel with this data already exists": "Ya existe un travel con estos datos", - "AMOUNT_NOT_MATCH_GROUPING": "AMOUNT_NOT_MATCH_GROUPING" + "This thermograph id already exists": "La id del termógrafo ya existe" } \ No newline at end of file diff --git a/modules/order/back/methods/order/getSourceValues.js b/modules/order/back/methods/order/getSourceValues.js index da3685c629..5e9f0e6dc6 100644 --- a/modules/order/back/methods/order/getSourceValues.js +++ b/modules/order/back/methods/order/getSourceValues.js @@ -12,7 +12,7 @@ module.exports = Self => { } }); - Self.getSourceValues = async () => { + Self.getSourceValues = async() => { return Self.getSetValues('sourceApp'); }; }; diff --git a/modules/travel/back/methods/thermograph/createThermograph.js b/modules/travel/back/methods/thermograph/createThermograph.js new file mode 100644 index 0000000000..bfca208fe7 --- /dev/null +++ b/modules/travel/back/methods/thermograph/createThermograph.js @@ -0,0 +1,60 @@ +module.exports = Self => { + Self.remoteMethod('createThermograph', { + description: 'Creates a new thermograph', + accessType: 'WRITE', + accepts: [{ + arg: 'thermographId', + type: 'String', + description: 'The thermograph id', + required: true + }, { + arg: 'model', + type: 'String', + description: 'The thermograph model', + required: true + }, { + arg: 'temperature', + type: 'String', + description: 'The thermograph temperature', + required: true + }, { + arg: 'warehouseId', + type: 'Number', + description: 'The warehouse id', + required: true + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/createThermograph`, + verb: 'POST' + } + }); + + Self.createThermograph = async(thermographId, model, temperature, warehouseId) => { + const models = Self.app.models; + const tx = await Self.beginTransaction({}); + + try { + const options = {transaction: tx}; + const thermograph = await models.Thermograph.create({ + id: thermographId, + model: model + }, options); + + await Self.rawSql(` + INSERT INTO travelThermograph(thermographFk, warehouseFk, temperature, created) + VALUES (?, ?,?, NOW()) + `, [thermograph.id, warehouseId, temperature], options); + + await tx.commit(); + + return thermograph; + } catch (err) { + await tx.rollback(); + throw err; + } + }; +}; diff --git a/modules/travel/back/methods/thermograph/getThermographModels.js b/modules/travel/back/methods/thermograph/getThermographModels.js new file mode 100644 index 0000000000..188c3a5308 --- /dev/null +++ b/modules/travel/back/methods/thermograph/getThermographModels.js @@ -0,0 +1,18 @@ +module.exports = Self => { + Self.remoteMethod('getThermographModels', { + description: 'Gets the thermograph models', + accessType: 'READ', + returns: { + type: ['String'], + root: true + }, + http: { + path: `/getThermographModels`, + verb: 'GET' + } + }); + + Self.getThermographModels = async() => { + return Self.getEnumValues('model'); + }; +}; diff --git a/modules/travel/back/methods/thermograph/specs/createThermograph.spec.js b/modules/travel/back/methods/thermograph/specs/createThermograph.spec.js new file mode 100644 index 0000000000..733b713f03 --- /dev/null +++ b/modules/travel/back/methods/thermograph/specs/createThermograph.spec.js @@ -0,0 +1,49 @@ +const app = require('vn-loopback/server/server'); + +describe('Termograph createThermograph()', () => { + const models = app.models; + const thermographId = '99999-1'; + const model = 'DISPOSABLE'; + const temperature = 'COOL'; + const warehouseId = 1; + let createdThermograph; + + afterAll(async done => { + let travelThermograpToDelete = await models.TravelThermograph.findOne({where: {thermographFk: createdThermograph.id}}); + let thermograpToDelete = await models.Thermograph.findById(createdThermograph.id); + + await travelThermograpToDelete.destroy(); + await thermograpToDelete.destroy(); + + done(); + }); + + it(`should create a thermograph which is saved in both thermograph and travelThermograph`, async() => { + let createdTravelThermograpth = await models.TravelThermograph.findOne({where: {thermographFk: thermographId}}); + + expect(createdTravelThermograpth).toBeNull(); + + createdThermograph = await models.Thermograph.createThermograph(thermographId, model, temperature, warehouseId); + + expect(createdThermograph.id).toEqual(thermographId); + expect(createdThermograph.model).toEqual(model); + + createdTravelThermograpth = await models.TravelThermograph.findOne({where: {thermographFk: thermographId}}); + + expect(createdTravelThermograpth.warehouseFk).toEqual(warehouseId); + expect(createdTravelThermograpth.temperature).toEqual(temperature); + }); + + it(`should not be able to created duplicated entries`, async() => { + let error; + + try { + await models.Thermograph.createThermograph(thermographId, model, temperature, warehouseId); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.message).toBe('This thermograph id already exists'); + }); +}); diff --git a/modules/travel/back/methods/travel-thermograph/getThermographTemperatures.js b/modules/travel/back/methods/travel-thermograph/getThermographTemperatures.js new file mode 100644 index 0000000000..1d510b5137 --- /dev/null +++ b/modules/travel/back/methods/travel-thermograph/getThermographTemperatures.js @@ -0,0 +1,18 @@ +module.exports = Self => { + Self.remoteMethod('getThermographTemperatures', { + description: 'Gets the thermograph temperatures', + accessType: 'READ', + returns: { + type: ['String'], + root: true + }, + http: { + path: `/getThermographTemperatures`, + verb: 'GET' + } + }); + + Self.getThermographTemperatures = async() => { + return Self.getEnumValues('temperature'); + }; +}; diff --git a/modules/travel/back/models/thermograph.js b/modules/travel/back/models/thermograph.js new file mode 100644 index 0000000000..3f95a06701 --- /dev/null +++ b/modules/travel/back/models/thermograph.js @@ -0,0 +1,12 @@ +let UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + require('../methods/thermograph/createThermograph')(Self); + require('../methods/thermograph/getThermographModels')(Self); + + Self.rewriteDbError(function(err) { + if (err.code === 'ER_DUP_ENTRY') + return new UserError(`This thermograph id already exists`); + return err; + }); +}; diff --git a/modules/travel/back/models/thermograph.json b/modules/travel/back/models/thermograph.json index 421ae43419..2519fffc40 100644 --- a/modules/travel/back/models/thermograph.json +++ b/modules/travel/back/models/thermograph.json @@ -10,10 +10,12 @@ "id": { "type": "String", "id": true, - "description": "Identifier" + "description": "Identifier", + "required": true }, "model": { - "type": "String" + "type": "String", + "required": true } } } diff --git a/modules/travel/back/models/travel-thermograph.js b/modules/travel/back/models/travel-thermograph.js index 0d70edd7e6..a16e68b985 100644 --- a/modules/travel/back/models/travel-thermograph.js +++ b/modules/travel/back/models/travel-thermograph.js @@ -1,4 +1,5 @@ module.exports = Self => { require('../methods/travel-thermograph/allowedContentTypes')(Self); + require('../methods/travel-thermograph/getThermographTemperatures')(Self); }; diff --git a/modules/travel/back/models/travel-thermograph.json b/modules/travel/back/models/travel-thermograph.json index b8f7fa41a1..70ee0de07e 100644 --- a/modules/travel/back/models/travel-thermograph.json +++ b/modules/travel/back/models/travel-thermograph.json @@ -21,10 +21,15 @@ "type": "Date" }, "temperature": { - "type": "String" + "type": "String", + "required": true }, "result": { "type": "String" + }, + "warehouseFk": { + "type": "Number", + "required": true } }, "relations": { diff --git a/modules/travel/front/thermograph/create/index.html b/modules/travel/front/thermograph/create/index.html index 4b1fc8cf4f..90327bdac5 100644 --- a/modules/travel/front/thermograph/create/index.html +++ b/modules/travel/front/thermograph/create/index.html @@ -17,6 +17,18 @@ where="{travelFk: null}" show-field="thermographFk" value-field="thermographFk"> + + {{thermographFk}} + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/travel/front/thermograph/create/index.js b/modules/travel/front/thermograph/create/index.js index 6c04649911..19d9fec15b 100644 --- a/modules/travel/front/thermograph/create/index.js +++ b/modules/travel/front/thermograph/create/index.js @@ -55,6 +55,24 @@ class Controller extends Section { }); } + onAddThermographClick(event) { + this.thermographModel = 'DISPOSABLE'; + this.temperature = 'COOL'; + event.preventDefault(); + this.newThermograph = { + thermographId: this.thermographId, + warehouseId: this.warehouseId, + temperature: this.temperature, + model: this.thermographModel + }; + this.$.newThermographDialog.show(); + } + + onNewThermographAccept() { + return this.$http.post(`Thermographs/createThermograph`, this.newThermograph) + .then(res => this.dms.thermographId = res.data.id); + } + onSubmit() { const query = `Travels/${this.travel.id}/createThermograph`; const options = { diff --git a/modules/travel/front/thermograph/create/index.spec.js b/modules/travel/front/thermograph/create/index.spec.js index 58fbe3991f..f835f64118 100644 --- a/modules/travel/front/thermograph/create/index.spec.js +++ b/modules/travel/front/thermograph/create/index.spec.js @@ -63,5 +63,30 @@ describe('Ticket', () => { expect(controller.allowedContentTypes).toEqual('application/pdf, image/png, image/jpg'); }); }); + + describe('onAddThermographClick()', () => { + it('should call the show() function of the create thermograph dialog', () => { + controller.$.newThermographDialog = {show: () => {}}; + jest.spyOn(controller.$.newThermographDialog, 'show'); + const event = new Event('click'); + jest.spyOn(event, 'preventDefault'); + + controller.onAddThermographClick(event); + + expect(event.preventDefault).toHaveBeenCalledTimes(1); + expect(controller.$.newThermographDialog.show).toHaveBeenCalledTimes(1); + }); + }); + + describe('onNewThermographAccept()', () => { + it('should set the created thermograph id on to the controller for the autocomplete to use it', () => { + const response = {id: 'the created id'}; + $httpBackend.when('POST', `Thermographs/createThermograph`).respond(response); + controller.onNewThermographAccept(); + $httpBackend.flush(); + + expect(controller.dms.thermographId).toEqual(response.id); + }); + }); }); }); diff --git a/modules/travel/front/thermograph/locale/es.yml b/modules/travel/front/thermograph/locale/es.yml index 9f9be564b7..0e3bc99fc7 100644 --- a/modules/travel/front/thermograph/locale/es.yml +++ b/modules/travel/front/thermograph/locale/es.yml @@ -15,4 +15,6 @@ Add thermograph: Añadir termógrafo Edit thermograph: Editar termógrafo Thermograph deleted: Termógrafo eliminado Thermograph: Termógrafo -Are you sure you want to remove the thermograph?: ¿Seguro que quieres quitar el termógrafo? \ No newline at end of file +New thermograph: Nuevo termógrafo +Are you sure you want to remove the thermograph?: ¿Seguro que quieres quitar el termógrafo? +Identifier: Identificador \ No newline at end of file From dfa74221dd74d503455e9335279b41e7b405b030 Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Wed, 27 May 2020 14:34:34 +0200 Subject: [PATCH 05/11] added crud model to preload autocompletes on demand + e2e path done --- e2e/helpers/selectors.js | 6 +++++ e2e/paths/10-travel/01_thermograph.spec.js | 25 +++++++++++++++---- .../front/thermograph/create/index.html | 14 +++++++++-- .../travel/front/thermograph/create/index.js | 12 ++++++--- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index ecf0d37e30..2f36dc584c 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -828,6 +828,12 @@ export default { }, travelThermograph: { add: 'vn-travel-thermograph-index vn-float-button[icon="add"]', + addThermographIcon: 'vn-travel-thermograph-create vn-autocomplete vn-icon[icon="add_circle"]', + newThermographId: 'vn-textfield[ng-model="$ctrl.newThermograph.thermographId"]', + newThermographModel: 'vn-autocomplete[ng-model="$ctrl.newThermograph.model"]', + newThermographWarehouse: 'vn-autocomplete[ng-model="$ctrl.newThermograph.warehouseId"]', + newThermographTemperature: 'vn-autocomplete[ng-model="$ctrl.newThermograph.temperature"]', + createThermographButton: 'form button[response="accept"]', thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]', uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="icon-attach"]', createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr', diff --git a/e2e/paths/10-travel/01_thermograph.spec.js b/e2e/paths/10-travel/01_thermograph.spec.js index 67a62381a9..e7f1e234d9 100644 --- a/e2e/paths/10-travel/01_thermograph.spec.js +++ b/e2e/paths/10-travel/01_thermograph.spec.js @@ -2,6 +2,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; describe('Travel thermograph path', () => { + const thermographName = '7H3-37H3RN4L-FL4M3'; let browser; let page; @@ -26,10 +27,18 @@ describe('Travel thermograph path', () => { await page.waitForState('travel.card.thermograph.create'); }); - it('should select the thermograph and then the file to upload', async() => { + it('should click on the add thermograph icon of the thermograph autocomplete', async() => { + await page.waitToClick(selectors.travelThermograph.addThermographIcon); + await page.write(selectors.travelThermograph.newThermographId, thermographName); + await page.autocompleteSearch(selectors.travelThermograph.newThermographModel, 'TEMPMATE'); + await page.autocompleteSearch(selectors.travelThermograph.newThermographWarehouse, 'Warehouse Two'); + await page.autocompleteSearch(selectors.travelThermograph.newThermographTemperature, 'WARM'); + await page.waitToClick(selectors.travelThermograph.createThermographButton); + }); + + it('should select the file to upload', async() => { let currentDir = process.cwd(); let filePath = `${currentDir}/e2e/dms/ecc/3.jpeg`; - await page.autocompleteSearch(selectors.travelThermograph.thermographID, '138350-0'); const [fileChooser] = await Promise.all([ page.waitForFileChooser(), @@ -38,11 +47,17 @@ describe('Travel thermograph path', () => { await fileChooser.accept([filePath]); await page.waitToClick(selectors.travelThermograph.upload); + + const message = await page.waitForSnackbar(); + const state = await page.getState(); + + expect(message.type).toBe('success'); + expect(state).toBe('travel.card.thermograph.index'); }); - it('should reload the section and check everything was saved', async() => { - let createdThermograph = await page.waitToGetProperty(selectors.travelThermograph.createdThermograph, 'innerText'); + it('should check everything was saved correctly', async() => { + const result = await page.waitToGetProperty(selectors.travelThermograph.createdThermograph, 'innerText'); - expect(createdThermograph).toContain('138350-0'); + expect(result).toContain(thermographName); }); }); diff --git a/modules/travel/front/thermograph/create/index.html b/modules/travel/front/thermograph/create/index.html index 90327bdac5..0232c1b127 100644 --- a/modules/travel/front/thermograph/create/index.html +++ b/modules/travel/front/thermograph/create/index.html @@ -97,6 +97,16 @@ + + + + @@ -135,7 +145,7 @@ required="true" label="Temperature" ng-model="$ctrl.newThermograph.temperature" - url="TravelThermographs/getThermographTemperatures" + data="thermographTemperatures" show-field="value" value-field="value"> diff --git a/modules/travel/front/thermograph/create/index.js b/modules/travel/front/thermograph/create/index.js index 19d9fec15b..d398febf1c 100644 --- a/modules/travel/front/thermograph/create/index.js +++ b/modules/travel/front/thermograph/create/index.js @@ -56,15 +56,19 @@ class Controller extends Section { } onAddThermographClick(event) { - this.thermographModel = 'DISPOSABLE'; - this.temperature = 'COOL'; + const defaultTemperature = 'COOL'; + const defaultModel = 'DISPOSABLE'; + event.preventDefault(); this.newThermograph = { thermographId: this.thermographId, warehouseId: this.warehouseId, - temperature: this.temperature, - model: this.thermographModel + temperature: defaultTemperature, + model: defaultModel }; + + this.$.modelsModel.refresh(); + this.$.temperaturesModel.refresh(); this.$.newThermographDialog.show(); } From 296967ab4b573743218e75ba7f65edab5f6c596e Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Wed, 27 May 2020 14:54:29 +0200 Subject: [PATCH 06/11] fixed a front unit test with jest.fn() as spy --- modules/travel/front/thermograph/create/index.spec.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/travel/front/thermograph/create/index.spec.js b/modules/travel/front/thermograph/create/index.spec.js index f835f64118..f6ee1f2c0f 100644 --- a/modules/travel/front/thermograph/create/index.spec.js +++ b/modules/travel/front/thermograph/create/index.spec.js @@ -66,8 +66,12 @@ describe('Ticket', () => { describe('onAddThermographClick()', () => { it('should call the show() function of the create thermograph dialog', () => { - controller.$.newThermographDialog = {show: () => {}}; - jest.spyOn(controller.$.newThermographDialog, 'show'); + controller.$.newThermographDialog = {show: jest.fn()}; + controller.$.modelsModel = {refresh: jest.fn()}; + controller.$.temperaturesModel = {refresh: jest.fn()}; + // jest.spyOn(controller.$.newThermographDialog, 'show'); + // jest.spyOn(controller.$.modelsModel, 'refresh'); + // jest.spyOn(controller.$.temperaturesModel, 'refresh'); const event = new Event('click'); jest.spyOn(event, 'preventDefault'); From a858e51aeeb4afb4df57e55cbd58e98bcb9e2855 Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Wed, 27 May 2020 14:54:54 +0200 Subject: [PATCH 07/11] removed unused code --- modules/travel/front/thermograph/create/index.spec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/travel/front/thermograph/create/index.spec.js b/modules/travel/front/thermograph/create/index.spec.js index f6ee1f2c0f..23976fc96c 100644 --- a/modules/travel/front/thermograph/create/index.spec.js +++ b/modules/travel/front/thermograph/create/index.spec.js @@ -69,9 +69,7 @@ describe('Ticket', () => { controller.$.newThermographDialog = {show: jest.fn()}; controller.$.modelsModel = {refresh: jest.fn()}; controller.$.temperaturesModel = {refresh: jest.fn()}; - // jest.spyOn(controller.$.newThermographDialog, 'show'); - // jest.spyOn(controller.$.modelsModel, 'refresh'); - // jest.spyOn(controller.$.temperaturesModel, 'refresh'); + const event = new Event('click'); jest.spyOn(event, 'preventDefault'); From 5c3e8e94655be123907665e991eb2c42dbf68bf9 Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Thu, 28 May 2020 08:09:12 +0200 Subject: [PATCH 08/11] Added insert ignore --- db/changes/10180-holyWeek/00-ACL.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/changes/10180-holyWeek/00-ACL.sql b/db/changes/10180-holyWeek/00-ACL.sql index d4b961e22a..b0ab68a97c 100644 --- a/db/changes/10180-holyWeek/00-ACL.sql +++ b/db/changes/10180-holyWeek/00-ACL.sql @@ -1 +1 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('WorkerLog', '*', 'READ', 'ALLOW', 'ROLE', 'hr'); \ No newline at end of file +INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('WorkerLog', '*', 'READ', 'ALLOW', 'ROLE', 'hr'); \ No newline at end of file From 9bf509cd5d9319d44cb2095cff447e1847c5dc30 Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Thu, 28 May 2020 09:54:31 +0200 Subject: [PATCH 09/11] customs agent e2e path + small fixes --- db/changes/10190-PostErte/00-ACL.sql | 3 +++ e2e/helpers/selectors.js | 6 ++++++ e2e/paths/02-client/05_add_address.spec.js | 12 +++++++++--- modules/client/front/address/create/index.js | 2 +- modules/client/front/address/create/index.spec.js | 2 +- modules/client/front/address/edit/index.spec.js | 2 +- 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/db/changes/10190-PostErte/00-ACL.sql b/db/changes/10190-PostErte/00-ACL.sql index d19901d481..2a2673fcca 100644 --- a/db/changes/10190-PostErte/00-ACL.sql +++ b/db/changes/10190-PostErte/00-ACL.sql @@ -1 +1,4 @@ UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213'; + +INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('CustomsAgent', '*', '*', 'ALLOW', 'ROLE', 'employee'); + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 2f36dc584c..b12c470f56 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -122,6 +122,12 @@ export default { mobileInput: 'vn-textfield[ng-model="$ctrl.address.mobile"]', defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]', incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsId"]', + addNewCustomsAgent: 'vn-client-address-create vn-autocomplete[ng-model="$ctrl.address.customsAgentId"] vn-icon-button[icon="add_circle"]', + newCustomsAgentFiscalID: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.nif"]', + newCustomsAgentFiscalName: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.fiscalName"]', + newCustomsAgentStreet: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.street"]', + newCustomsAgentPhone: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.phone"]', + saveNewCustomsAgentButton: 'button[response="accept"]', customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentId"]', secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]', firstEditAddress: 'vn-client-address-index div:nth-child(1) > a', diff --git a/e2e/paths/02-client/05_add_address.spec.js b/e2e/paths/02-client/05_add_address.spec.js index 2fe238feae..f16408b343 100644 --- a/e2e/paths/02-client/05_add_address.spec.js +++ b/e2e/paths/02-client/05_add_address.spec.js @@ -61,12 +61,18 @@ describe('Client Add address path', () => { expect(message.text).toBe('Customs agent is required for a non UEE member'); }); - it(`should create a new address with all it's data`, async() => { - await page.autocompleteSearch(selectors.clientAddresses.customsAgent, 'Agent one'); + it(`should create a new custom agent and then save the address`, async() => { + await page.waitToClick(selectors.clientAddresses.addNewCustomsAgent); + await page.write(selectors.clientAddresses.newCustomsAgentFiscalID, 'ID'); + await page.write(selectors.clientAddresses.newCustomsAgentFiscalName, 'name'); + await page.write(selectors.clientAddresses.newCustomsAgentStreet, 'street'); + await page.write(selectors.clientAddresses.newCustomsAgentPhone, '555555555'); + await page.waitToClick(selectors.clientAddresses.saveNewCustomsAgentButton); + await page.waitToClick(selectors.clientAddresses.saveButton); const message = await page.waitForSnackbar(); - expect(message.type).toBe('success'); + expect(message.text).toBe('Data saved!'); }); it(`should navigate back to the addresses index`, async() => { diff --git a/modules/client/front/address/create/index.js b/modules/client/front/address/create/index.js index b1629073d0..79774cf93f 100644 --- a/modules/client/front/address/create/index.js +++ b/modules/client/front/address/create/index.js @@ -29,7 +29,7 @@ export default class Controller extends Section { onCustomAgentAccept() { return this.$http.post(`CustomsAgents`, this.newCustomsAgent) - .then(res => this.address.customsAgentFk = res.data.id); + .then(res => this.address.customsAgentId = res.data.id); } get town() { diff --git a/modules/client/front/address/create/index.spec.js b/modules/client/front/address/create/index.spec.js index fb6567dcef..4f332e75e2 100644 --- a/modules/client/front/address/create/index.spec.js +++ b/modules/client/front/address/create/index.spec.js @@ -123,7 +123,7 @@ describe('Client', () => { controller.onCustomAgentAccept(); $httpBackend.flush(); - expect(controller.address.customsAgentFk).toEqual(1); + expect(controller.address.customsAgentId).toEqual(1); }); }); }); diff --git a/modules/client/front/address/edit/index.spec.js b/modules/client/front/address/edit/index.spec.js index b67138b6dc..c4c1a78b55 100644 --- a/modules/client/front/address/edit/index.spec.js +++ b/modules/client/front/address/edit/index.spec.js @@ -64,7 +64,7 @@ describe('Client', () => { }); describe('onCustomAgentAccept()', () => { - it(`should create a new customs agent and then set the customsAgentFk property on the address`, () => { + it(`should now create a new customs agent and then set the customsAgentFk property on the address`, () => { const expectedResult = {id: 1, fiscalName: 'Customs agent one'}; $httpBackend.when('POST', 'CustomsAgents').respond(200, expectedResult); controller.onCustomAgentAccept(); From 2e2e8cf957e15016423d190edea135869513b0fc Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Sat, 30 May 2020 16:32:05 +0200 Subject: [PATCH 10/11] Updated node LTS version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8960c8d002..52f854b6e9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Salix is also the scientific name of a beautifull tree! :) Required applications. * Visual Studio Code -* Node.js = 10.15.3 LTS +* Node.js = 12.17.0 LTS * Docker In Visual Studio Code we use the ESLint extension. Open Visual Studio Code, press Ctrl+P and paste the following command. From b411f49805d2a3f55519179a0aca24b9131e1952 Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Sat, 30 May 2020 16:51:41 +0200 Subject: [PATCH 11/11] Updated to node LTS version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5a65b9b18b..a574e61fdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update \ ca-certificates \ gnupg2 \ libfontconfig \ - && curl -sL https://deb.nodesource.com/setup_10.x | bash - \ + && curl -sL https://deb.nodesource.com/setup_12.x | bash - \ && apt-get install -y --no-install-recommends \ nodejs \ && apt-get purge -y --auto-remove \