diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index a1412f431..19fe66cc1 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -523,7 +523,7 @@ export default { }, itemLog: { anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', - fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(2) td.after', + fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(4) td.after', }, ticketSummary: { header: 'vn-ticket-summary > vn-card > h5', diff --git a/e2e/paths/04-item/10_item_log.spec.js b/e2e/paths/04-item/10_item_log.spec.js index 2a885fe6f..46979a761 100644 --- a/e2e/paths/04-item/10_item_log.spec.js +++ b/e2e/paths/04-item/10_item_log.spec.js @@ -59,6 +59,6 @@ describe('Item log path', () => { const fifthLineCreatedProperty = await page .waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText'); - expect(fifthLineCreatedProperty).toEqual('Coral y materiales similares'); + expect(fifthLineCreatedProperty).toEqual('05080000'); }); }); diff --git a/e2e/paths/11-zone/02_descriptor.spec.js b/e2e/paths/11-zone/02_descriptor.spec.js index 1de84d601..12a1c8f68 100644 --- a/e2e/paths/11-zone/02_descriptor.spec.js +++ b/e2e/paths/11-zone/02_descriptor.spec.js @@ -37,6 +37,6 @@ describe('Zone descriptor path', () => { await page.accessToSection('ticket.card.log'); const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText'); - expect(lastChanges).toContain('Arreglar'); + expect(lastChanges).toContain('1'); }); }); diff --git a/loopback/common/models/loggable.js b/loopback/common/models/loggable.js index fd355a59a..360c84566 100644 --- a/loopback/common/models/loggable.js +++ b/loopback/common/models/loggable.js @@ -1,4 +1,3 @@ -const pick = require('object.pick'); const LoopBackContext = require('loopback-context'); module.exports = function(Self) { @@ -7,421 +6,10 @@ module.exports = function(Self) { }; Self.observe('before save', async function(ctx) { - const models = ctx.Model.app.models; - const definition = ctx.Model.definition; - const Model = models[definition.name]; - let opts = ctx.options; - if (!opts) opts = ctx.options = {}; - - // await txBegin(ctx); - - // try { - // await grabUserLog(ctx, 'login'); - - let oldInstance; - let newInstance; - if (ctx.data) { - const changes = pick(ctx.currentInstance, Object.keys(ctx.data)); - newInstance = ctx.data; - oldInstance = changes; - - if (ctx.where && !ctx.currentInstance) { - const fields = Object.keys(ctx.data); - - ctx.oldInstances = await Model.find({ - where: ctx.where, - fields: fields - }, opts); - } - } - - // Get changes from created instance - if (ctx.isNewInstance) - newInstance = ctx.instance.__data; - - ctx.hookState.oldInstance = oldInstance; - ctx.hookState.newInstance = newInstance; - // } catch (e) { - // await txEnd(ctx, true); - // } - }); - - Self.observe('after save', async function(ctx) { - const loopBackContext = LoopBackContext.getCurrentContext(); - const models = ctx.Model.app.models; - const definition = ctx.Model.definition; - const settings = ctx.Model.definition.settings; - const relations = ctx.Model.relations; - - let opts = ctx.options; - if (!opts) opts = ctx.options = {}; - - // try { - let primaryKey; - for (let property in definition.properties) { - if (definition.properties[property].id) { - primaryKey = property; - break; - } - } - - if (!primaryKey) throw new Error('Primary key not found'); - let originId; - - // RELATIONS LOG - let changedModelId; - - if (ctx.instance && !settings.log.relation) { - originId = ctx.instance.id; - changedModelId = ctx.instance.id; - } else if (settings.log.relation) { - primaryKey = relations[settings.log.relation].keyFrom; - - if (ctx.where && ctx.where[primaryKey]) - originId = ctx.where[primaryKey]; - else if (ctx.instance) { - originId = ctx.instance[primaryKey]; - changedModelId = ctx.instance.id; - } - } else { - originId = ctx.currentInstance.id; - changedModelId = ctx.currentInstance.id; - } - - // Sets the changedModelValue to save and the instances changed in case its an updateAll - let showField = settings.log.showField; - let where; - if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) { - changedModelId = []; - where = []; - let changedInstances = await models[definition.name].find({ - where: ctx.where, - fields: ['id', showField, primaryKey] - }, opts); - - changedInstances.forEach(element => { - where.push(element[showField]); - changedModelId.push(element.id); - originId = element[primaryKey]; - }); - } else if (ctx.hookState.oldInstance) - where = ctx.instance[showField]; - - // Set oldInstance, newInstance, userFk and action - let oldInstance = {}; - if (ctx.hookState.oldInstance) - Object.assign(oldInstance, ctx.hookState.oldInstance); - - let newInstance = {}; - if (ctx.hookState.newInstance) - Object.assign(newInstance, ctx.hookState.newInstance); - - let userFk; - if (loopBackContext) - userFk = loopBackContext.active.accessToken.userId; - - const action = setActionType(ctx); - - removeUnloggable(definition, oldInstance); - removeUnloggable(definition, newInstance); - - oldInstance = await fkToValue(oldInstance, ctx); - newInstance = await fkToValue(newInstance, ctx); - - // Prevent log with no new changes - const hasNewChanges = Object.keys(newInstance).length; - if (!hasNewChanges) return; - - let logRecord = { - originFk: originId, - userFk: userFk, - action: action, - changedModel: definition.name, - changedModelId: changedModelId, // Model property with an different data type will throw a NaN error - changedModelValue: where, - oldInstance: oldInstance, - newInstance: newInstance - }; - - const logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx); - const logModel = settings.log.model; - await models[logModel].create(logsToSave, opts); - - // await grabUserLog(ctx, 'logout'); - // } catch (e) { - // await txEnd(ctx, true); - // throw e; - // } - - // await txEnd(ctx); + ctx.options.httpCtx = LoopBackContext.getCurrentContext(); }); Self.observe('before delete', async function(ctx) { - const models = ctx.Model.app.models; - const definition = ctx.Model.definition; - const relations = ctx.Model.relations; - let opts = ctx.options; - if (!opts) opts = ctx.options = {}; - - // await txBegin(ctx); - - // try { - // await grabUserLog(ctx, 'login'); - - if (ctx.where) { - const affectedModel = definition.name; - let deletedInstances = await models[affectedModel].find({ - where: ctx.where - }, opts); - - const relation = definition.settings.log.relation; - - if (relation) { - const primaryKey = relations[relation].keyFrom; - - let arrangedDeletedInstances = []; - for (let i = 0; i < deletedInstances.length; i++) { - if (primaryKey) - deletedInstances[i].originFk = deletedInstances[i][primaryKey]; - let arrangedInstance = await fkToValue(deletedInstances[i], ctx); - arrangedDeletedInstances[i] = arrangedInstance; - } - ctx.hookState.oldInstance = arrangedDeletedInstances; - } - } - // } catch (e) { - // await txEnd(ctx, true); - // } + ctx.options.httpCtx = LoopBackContext.getCurrentContext(); }); - - Self.observe('after delete', async function(ctx) { - const loopBackContext = LoopBackContext.getCurrentContext(); - const models = ctx.Model.app.models; - const definition = ctx.Model.definition; - const settings = definition.settings; - let opts = ctx.options; - if (!opts) opts = ctx.options = {}; - - // try { - if (ctx.hookState.oldInstance) { - ctx.hookState.oldInstance.forEach(async instance => { - let userFk; - if (loopBackContext) - userFk = loopBackContext.active.accessToken.userId; - - const changedModelValue = settings.log.changedModelValue; - const logRecord = { - originFk: instance.originFk, - userFk: userFk, - action: 'delete', - changedModel: definition.name, - changedModelId: instance.id, - changedModelValue: instance[changedModelValue], - oldInstance: instance, - newInstance: {} - }; - - delete instance.originFk; - - const logModel = settings.log.model; - await models[logModel].create(logRecord, opts); - }); - } - // await grabUserLog(ctx, 'logout'); - // } catch (e) { - // await txEnd(ctx, true); - // throw e; - // } - - // await txEnd(ctx); - }); - - // Get log values from a foreign key - async function fkToValue(instance, ctx) { - const models = ctx.Model.app.models; - const relations = ctx.Model.relations; - let opts = ctx.options; - if (!opts) opts = ctx.options = {}; - - const instanceCopy = JSON.parse(JSON.stringify(instance)); - const result = {}; - for (const key in instanceCopy) { - let value = instanceCopy[key]; - - if (value instanceof Object || value === undefined) - continue; - - if (value) { - for (const 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 models[modelName].findById(value, null, opts); - - const hasShowField = settings.log && settings.log.showField; - let showField = hasShowField && recordSet - && recordSet[settings.log.showField]; - - if (!showField) { - const showFieldNames = [ - 'name', - 'description', - 'code', - 'nickname' - ]; - for (field of showFieldNames) { - const propField = properties && properties[field]; - const recordField = recordSet && recordSet[field]; - - if (propField && recordField) { - showField = field; - break; - } - } - } - - if (showField && recordSet && recordSet[showField]) { - value = recordSet[showField]; - break; - } - - value = recordSet && recordSet.id || value; - break; - } - } - } - result[key] = value; - } - return result; - } - - /** - * Removes unwanted properties - * @param {*} definition Model definition - * @param {*} properties Modified object properties - */ - function removeUnloggable(definition, properties) { - const objectCopy = Object.assign({}, properties); - const propList = Object.keys(objectCopy); - const propDefs = new Map(); - - for (let property in definition.properties) { - const propertyDef = definition.properties[property]; - propDefs.set(property, propertyDef); - } - - for (let property of propList) { - const propertyDef = propDefs.get(property); - const firstChar = property.substring(0, 1); - const isPrivate = firstChar == '$'; - - if (isPrivate || !propertyDef) - delete properties[property]; - - if (!propertyDef) continue; - - if (propertyDef.log === false) - delete properties[property]; - else if (propertyDef.logValue === false) - properties[property] = null; - } - } - - // this function retuns all the instances changed in case this is an updateAll - function setLogsToSave(changedInstances, changedInstancesIds, logRecord, ctx) { - let promises = []; - if (changedInstances && typeof changedInstances == 'object') { - for (let i = 0; i < changedInstances.length; i++) { - logRecord.changedModelId = changedInstancesIds[i]; - logRecord.changedModelValue = changedInstances[i]; - if (ctx.oldInstances) - logRecord.oldInstance = ctx.oldInstances[i]; - promises.push(JSON.parse(JSON.stringify(logRecord))); - } - } else - return logRecord; - - return promises; - } - - function setActionType(ctx) { - let oldInstance = ctx.hookState.oldInstance; - let newInstance = ctx.hookState.newInstance; - - if (oldInstance && newInstance) - return 'update'; - else if (!oldInstance && newInstance) - return 'insert'; - - return 'delete'; - } - - /** - * The functions txBegin and txEnd are used to transactionate the loggable. - * When a new transaction is created, they add a txLevel because in some cases - * the transactions are performed in a recursive way. - * - * The function grabUserLog check if the option grabUser is active in the Model and - * login or logout the user in the database depending on the action. - * - * Now they are commented out because the way to handle the errors - * that occur in the database that leave opened transactions has not been found. - * (https://redmine.verdnatura.es/issues/5036) - **/ - - // async function txBegin(ctx) { - // const opts = ctx.options || {}; - // const models = ctx.Model.app.models; - // const definition = ctx.Model.definition; - // const Model = models[definition.name]; - - // if (opts.txLevel) - // opts.txLevel++; - // else if (!opts.transaction) { - // opts.txLevel = 1; - // opts.transaction = await Model.beginTransaction({}); - // } - // } - - // async function txEnd(ctx, undoChanges) { - // const opts = ctx.options || {}; - - // if (opts.txLevel) { - // opts.txLevel--; - // if (opts.txLevel === 0) { - // const tx = opts.transaction; - // delete opts.txLevel; - // delete opts.transaction; - - // if (undoChanges) - // await tx.rollback(); - // else - // await tx.commit(); - // } - // } - // } - - // async function grabUserLog(ctx, logAction) { - // const opts = ctx.options || {}; - // const models = ctx.Model.app.models; - // const definition = ctx.Model.definition; - // const settings = definition.settings; - // const Model = models[definition.name]; - // const hasGrabUser = settings.log && settings.log.grabUser; - - // if (logAction === 'login' && hasGrabUser) { - // const loopBackContext = LoopBackContext.getCurrentContext(); - - // if (loopBackContext) { - // const userId = loopBackContext.active.accessToken.userId; - // const user = await models.Account.findById(userId, {fields: ['name']}, opts); - // await Model.rawSql(`CALL account.myUser_loginWithName(?)`, [user.name], opts); - // } - // } else if (logAction === 'logout' && hasGrabUser) - // await Model.rawSql(`CALL account.myUser_logout()`, null, opts); - // } }; diff --git a/loopback/server/connectors/vn-mysql.js b/loopback/server/connectors/vn-mysql.js index fde0ddcf6..55cc4edb3 100644 --- a/loopback/server/connectors/vn-mysql.js +++ b/loopback/server/connectors/vn-mysql.js @@ -2,6 +2,7 @@ const mysql = require('mysql'); const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const MySQL = require('loopback-connector-mysql').MySQL; const EnumFactory = require('loopback-connector-mysql').EnumFactory; +const Transaction = require('loopback-connector').Transaction; const fs = require('fs'); class VnMySQL extends MySQL { @@ -219,6 +220,315 @@ class VnMySQL extends MySQL { this.makePagination(filter) ]); } + + create(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('create', + arguments, model, ctx, opts, cb); + } + + createAll(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('createAll', + arguments, model, ctx, opts, cb); + } + + save(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('save', + arguments, model, ctx, opts, cb); + } + + updateOrCreate(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('updateOrCreate', + arguments, model, ctx, opts, cb); + } + + replaceOrCreate(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('replaceOrCreate', + arguments, model, ctx, opts, cb); + } + + destroyAll(model, where, opts, cb) { + const ctx = {where}; + this.invokeMethod('destroyAll', + arguments, model, ctx, opts, cb); + } + + update(model, where, data, opts, cb) { + const ctx = {where, data}; + this.invokeMethod('update', + arguments, model, ctx, opts, cb); + } + + replaceById(model, id, data, opts, cb) { + const ctx = {id, data}; + this.invokeMethod('replaceById', + arguments, model, ctx, opts, cb); + } + + isLoggable(model) { + const Model = this.getModelDefinition(model).model; + const settings = Model.definition.settings; + return settings.base && settings.base === 'Loggable'; + } + + invokeMethod(method, args, model, ctx, opts, cb) { + if (!this.isLoggable(model)) + return super[method].apply(this, args); + + this.invokeMethodP(method, [...args], model, ctx, opts) + .then(res => cb(...res), cb); + } + + async invokeMethodP(method, args, model, ctx, opts) { + let tx; + if (!opts.transaction) { + tx = await Transaction.begin(this, {}); + opts = Object.assign({transaction: tx, httpCtx: opts.httpCtx}, opts); + } + + try { + // Fetch old values (update|delete) or login + await this.grabUserLog(model, opts, 'login'); + + let where = ctx.where; + const id = ctx.id; + const data = ctx.data; + const idName = this.idName(model); + + const limitSet = new Set([ + 'save', + 'updateOrCreate', + 'replaceOrCreate', + 'replaceById', + 'updateAttributes', + 'update' + ]); + + const limit = limitSet.has(method); + + const opOpts = { + update: [ + 'update', + 'replaceById', + 'updateAttributes', + // |insert + 'save', + 'updateOrCreate', + 'replaceOrCreate' + ], + delete: [ + 'destroy', + 'destroyAll' + ], + insert: [ + 'create' + ] + }; + + const opMap = new Map(); + for (const op in opOpts) { + for (const met of opOpts[op]) + opMap.set(met, op); + } + const op = opMap.get(method); + + if (!where) { + if (id) where = {[idName]: id}; + else where = {[idName]: data[idName]}; + } + + let oldInstances; + let newInstances; + + // Fetch old values + switch (op) { + case 'update': + case 'delete': + // Single entity operation + const stmt = this.buildSelectStmt(op, data, idName, model, where, limit); + stmt.merge(`FOR UPDATE`); + oldInstances = await this.executeStmt(stmt, opts); + } + + const res = await new Promise(resolve => { + const fnArgs = args.slice(0, -2); + fnArgs.push(opts); + fnArgs.push((...args) => resolve(args)); + super[method].apply(this, fnArgs); + }); + + // Fetch new values + const ids = []; + + switch (op) { + case 'insert': + case 'update': { + switch (method) { + case 'createAll': + for (const row of res[1]) + ids.push(row[idName]); + break; + case 'create': + ids.push(res[1]); + break; + case 'update': + if (data[idName] != null) + ids.push(data[idName]); + break; + } + + const newWhere = ids.length ? {[idName]: ids} : where; + + const stmt = this.buildSelectStmt(op, data, idName, model, newWhere, limit); + newInstances = await this.executeStmt(stmt, opts); + } + } + + await this.createLogRecord(oldInstances, newInstances, model, opts); + await this.grabUserLog(model, opts, 'logout'); + + if (tx) await tx.commit(); + return res; + } catch (err) { + if (tx) tx.rollback(); + throw err; + } + } + + async grabUserLog(model, opts, logAction) { + const Model = this.getModelDefinition(model).model; + const settings = Model.definition.settings; + + if (!(settings.log && settings.log.grabUser)) + return; + + if (logAction === 'login') { + const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId; + const user = await Model.app.models.Account.findById(userId, {fields: ['name']}, opts); + await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts); + } else + await this.executeP(`CALL account.myUser_logout()`, null, opts); + } + + buildSelectStmt(op, data, idName, model, where, limit) { + const Model = this.getModelDefinition(model).model; + const settings = Model.definition.settings; + const properties = Object.keys(Model.definition.properties); + const log = settings.log; + + const fields = data ? Object.keys(data) : []; + if (op == 'delete') + properties.forEach(property => fields.push(property)); + else { + fields.push(idName); + if (log.relation) fields.push(Model.relations[log.relation].keyFrom); + if (log.showField) fields.push(log.showField); + else { + const showFieldNames = ['name', 'description', 'code', 'nickname']; + for (const field of showFieldNames) { + if (properties.includes(field)) { + log.showField = field; + fields.push(field); + break; + } + } + } + } + + const stmt = new ParameterizedSQL( + 'SELECT ' + + this.buildColumnNames(model, {fields}) + + ' FROM ' + + this.tableEscaped(model) + ); + stmt.merge(this.buildWhere(model, where)); + if (limit) stmt.merge(`LIMIT 1`); + + return stmt; + } + + async createLogRecord(oldInstances, newInstances, model, opts) { + function setActionType() { + if (oldInstances && newInstances) + return 'update'; + else if (!oldInstances && newInstances) + return 'insert'; + return 'delete'; + } + + const action = setActionType(); + if (!newInstances && action != 'delete') return; + + const Model = this.getModelDefinition(model).model; + const models = Model.app.models; + const definition = Model.definition; + const log = definition.settings.log; + + const primaryKey = this.idName(model); + const originRelation = log.relation; + const originFkField = originRelation + ? Model.relations[originRelation].keyFrom + : primaryKey; + + // Prevent adding logs when deleting a principal entity (Client, Zone...) + if (action == 'delete' && !originRelation) return; + + function map(instances) { + const map = new Map(); + if (!instances) return; + for (const instance of instances) + map.set(instance[primaryKey], instance); + return map; + } + + const changedModel = definition.name; + const userFk = opts.httpCtx && opts.httpCtx.active.accessToken.userId; + const oldMap = map(oldInstances); + const newMap = map(newInstances); + const ids = (oldMap || newMap).keys(); + + const logEntries = []; + + function insertValuesLogEntry(logEntry, instance) { + logEntry.originFk = instance[originFkField]; + logEntry.changedModelId = instance[primaryKey]; + if (log.showField) logEntry.changedModelValue = instance[log.showField]; + } + + for (const id of ids) { + const oldI = oldMap && oldMap.get(id); + const newI = newMap && newMap.get(id); + + const logEntry = { + action, + userFk, + changedModel, + }; + + if (newI) { + insertValuesLogEntry(logEntry, newI); + // Delete unchanged properties + if (oldI) { + Object.keys(oldI).forEach(prop => { + if (newI[prop] == oldI[prop]) { + delete newI[prop]; + delete oldI[prop]; + } + }); + } + } else + insertValuesLogEntry(logEntry, oldI); + + logEntry.oldInstance = oldI; + logEntry.newInstance = newI; + logEntries.push(logEntry); + } + await models[log.model].create(logEntries, opts); + } } exports.VnMySQL = VnMySQL; diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index e07993f79..413b7598a 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -269,6 +269,18 @@ module.exports = Self => { // Credit changes if (changes.credit !== undefined) await Self.changeCredit(ctx, finalState, changes); + + const oldInstance = {}; + if (!ctx.isNewInstance) { + const newProps = Object.keys(changes); + Object.keys(orgData.__data).forEach(prop => { + if (newProps.includes(prop)) + oldInstance[prop] = orgData[prop]; + }); + } + + ctx.hookState.oldInstance = oldInstance; + ctx.hookState.newInstance = changes; }); Self.observe('after save', async ctx => { diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index e6725e9d2..e35a2d25d 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -230,37 +230,41 @@ module.exports = Self => { } const changes = loggable.getChanges(originalTicket, updatedTicket); - const oldProperties = await loggable.translateValues(Self, changes.old); - const newProperties = await loggable.translateValues(Self, changes.new); + const hasChanges = Object.keys(changes.old).length > 0 || Object.keys(changes.new).length > 0; - await models.TicketLog.create({ - originFk: args.id, - userFk: userId, - action: 'update', - changedModel: 'Ticket', - changedModelId: args.id, - oldInstance: oldProperties, - newInstance: newProperties - }, myOptions); + if (hasChanges) { + const oldProperties = await loggable.translateValues(Self, changes.old); + const newProperties = await loggable.translateValues(Self, changes.new); - const salesPersonId = originalTicket.client().salesPersonFk; - if (salesPersonId) { - const origin = ctx.req.headers.origin; + await models.TicketLog.create({ + originFk: args.id, + userFk: userId, + action: 'update', + changedModel: 'Ticket', + changedModelId: args.id, + oldInstance: oldProperties, + newInstance: newProperties + }, myOptions); - let changesMade = ''; - for (let change in newProperties) { - let value = newProperties[change]; - let oldValue = oldProperties[change]; + const salesPersonId = originalTicket.client().salesPersonFk; + if (salesPersonId) { + const origin = ctx.req.headers.origin; - changesMade += `\r\n~${$t(change)}: ${oldValue}~ ➔ *${$t(change)}: ${value}*`; + let changesMade = ''; + for (let change in newProperties) { + let value = newProperties[change]; + let oldValue = oldProperties[change]; + + changesMade += `\r\n~${$t(change)}: ${oldValue}~ ➔ *${$t(change)}: ${value}*`; + } + + const message = $t('Changed this data from the ticket', { + ticketId: args.id, + ticketUrl: `${origin}/#!/ticket/${args.id}/sale`, + changes: changesMade + }); + await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); } - - const message = $t('Changed this data from the ticket', { - ticketId: args.id, - ticketUrl: `${origin}/#!/ticket/${args.id}/sale`, - changes: changesMade - }); - await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); } res.id = args.id; diff --git a/modules/ticket/back/methods/ticket/specs/merge.spec.js b/modules/ticket/back/methods/ticket/specs/merge.spec.js index 275484f67..0a4d2342a 100644 --- a/modules/ticket/back/methods/ticket/specs/merge.spec.js +++ b/modules/ticket/back/methods/ticket/specs/merge.spec.js @@ -2,13 +2,13 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); describe('ticket merge()', () => { - const tickets = [{ + const tickets = { originId: 13, destinationId: 12, originShipped: new Date(), destinationShipped: new Date(), workerFk: 1 - }]; + }; const activeCtx = { accessToken: {userId: 9}, @@ -37,14 +37,14 @@ describe('ticket merge()', () => { const options = {transaction: tx}; const chatNotificationBeforeMerge = await models.Chat.find(); - await models.Ticket.merge(ctx, tickets, options); + await models.Ticket.merge(ctx, [tickets], options); - const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets[0].originId}}, options); - const deletedTicket = await models.Ticket.findOne({where: {id: tickets[0].originId}}, options); - const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets[0].destinationId}}, options); + const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.originId}}, options); + const deletedTicket = await models.Ticket.findOne({where: {id: tickets.originId}}, options); + const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets.destinationId}}, options); const chatNotificationAfterMerge = await models.Chat.find(); - expect(createdTicketLog.length).toEqual(1); + expect(createdTicketLog.length).toEqual(2); expect(deletedTicket.isDeleted).toEqual(true); expect(salesTicketFuture.length).toEqual(2); expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2);