diff --git a/back/methods/chat/getServiceAuth.js b/back/methods/chat/getServiceAuth.js new file mode 100644 index 0000000000..8270921090 --- /dev/null +++ b/back/methods/chat/getServiceAuth.js @@ -0,0 +1,55 @@ +const axios = require('axios'); +const tokenLifespan = 10; +module.exports = Self => { + Self.remoteMethodCtx('getServiceAuth', { + description: 'Authenticates with the service and request a new token', + accessType: 'READ', + accepts: [], + returns: { + type: 'object', + root: true + }, + http: { + path: `/getServiceAuth`, + verb: 'GET' + } + }); + + Self.getServiceAuth = async() => { + if (!this.login) + this.login = await requestToken(); + + if (!this.login) return; + + if (Date.now() > this.login.expires) + this.login = await requestToken(); + + return this.login; + }; + + /** + * Requests a new Rocketchat token + */ + async function requestToken() { + const models = Self.app.models; + const chatConfig = await models.ChatConfig.findOne(); + + const {data} = await axios.post(`${chatConfig.api}/login`, { + user: chatConfig.user, + password: chatConfig.password + }); + + const requestData = data.data; + if (requestData) { + return { + host: chatConfig.host, + api: chatConfig.api, + auth: { + userId: requestData.userId, + token: requestData.authToken + }, + expires: Date.now() + (1000 * 60 * tokenLifespan) + }; + } + } +}; diff --git a/back/methods/chat/send.js b/back/methods/chat/send.js index 209dfb0352..67e0dbb87d 100644 --- a/back/methods/chat/send.js +++ b/back/methods/chat/send.js @@ -1,4 +1,4 @@ -const got = require('got'); +const axios = require('axios'); module.exports = Self => { Self.remoteMethodCtx('send', { description: 'Send a RocketChat message', @@ -30,122 +30,35 @@ module.exports = Self => { const sender = await models.Account.findById(accessToken.userId); const recipient = to.replace('@', ''); - if (sender.name != recipient) { - let {body} = await sendMessage(sender, to, message); - if (body) - body = JSON.parse(body); - else - body = false; - - return body; - } - - return false; + if (sender.name != recipient) + return sendMessage(sender, to, message); }; async function sendMessage(sender, channel, message) { - const config = await getConfig(); - const avatar = `${config.host}/avatar/${sender.name}`; - const uri = `${config.api}/chat.postMessage`; - - return sendAuth(uri, { - 'channel': channel, - 'avatar': avatar, - 'alias': sender.nickname, - 'text': message - }).catch(async error => { - if (error.statusCode === 401) { - this.auth = null; - - return sendMessage(sender, channel, message); - } - - throw new Error(error.message); - }); - } - - /** - * Returns a rocketchat token - * @return {Object} userId and authToken - */ - async function getAuthToken() { - if (!this.auth || this.auth && !this.auth.authToken) { - const config = await getConfig(); - const uri = `${config.api}/login`; - let {body} = await send(uri, { - user: config.user, - password: config.password - }); - - if (body) { - body = JSON.parse(body); - this.auth = body.data; - } - } - - return this.auth; - } - - /** - * Returns a rocketchat config - * @return {Object} Auth config - */ - async function getConfig() { - if (!this.chatConfig) { - const models = Self.app.models; - - this.chatConfig = await models.ChatConfig.findOne(); - } - - return this.chatConfig; - } - - /** - * Send unauthenticated request - * @param {*} uri - Request uri - * @param {*} params - Request params - * @param {*} options - Request options - * - * @return {Object} Request response - */ - async function send(uri, params, options = {}) { if (process.env.NODE_ENV !== 'production') { return new Promise(resolve => { return resolve({ - body: JSON.stringify( - {statusCode: 200, message: 'Fake notification sent'} - ) + statusCode: 200, + message: 'Fake notification sent' }); }); } - const defaultOptions = { - form: params - }; + const login = await Self.getServiceAuth(); + const avatar = `${login.host}/avatar/${sender.name}`; - if (options) Object.assign(defaultOptions, options); - - return got.post(uri, defaultOptions); - } - - /** - * Send authenticated request - * @param {*} uri - Request uri - * @param {*} body - Request params - * - * @return {Object} Request response - */ - async function sendAuth(uri, body) { - const login = await getAuthToken(); const options = { - headers: {} + headers: { + 'X-Auth-Token': login.auth.token, + 'X-User-Id': login.auth.userId + }, }; - if (login) { - options.headers['X-Auth-Token'] = login.authToken; - options.headers['X-User-Id'] = login.userId; - } - - return send(uri, body, options); + return axios.post(`${login.api}/chat.postMessage`, { + 'channel': channel, + 'avatar': avatar, + 'alias': sender.nickname, + 'text': message + }, options); } }; diff --git a/back/methods/chat/sendCheckingPresence.js b/back/methods/chat/sendCheckingPresence.js index fcde20130c..429ecdab01 100644 --- a/back/methods/chat/sendCheckingPresence.js +++ b/back/methods/chat/sendCheckingPresence.js @@ -1,21 +1,23 @@ +const axios = require('axios'); + module.exports = Self => { Self.remoteMethodCtx('sendCheckingPresence', { - description: 'Sends a RocketChat message to a working worker or department channel', + description: 'Sends a RocketChat message to a connected user or department channel', accessType: 'WRITE', accepts: [{ - arg: 'workerId', - type: 'Number', + arg: 'recipientId', + type: 'number', required: true, - description: 'The worker id of the destinatary' + description: 'The recipient user id' }, { arg: 'message', - type: 'String', + type: 'string', required: true, description: 'The message' }], returns: { - type: 'Object', + type: 'object', root: true }, http: { @@ -33,30 +35,61 @@ module.exports = Self => { Object.assign(myOptions, options); const models = Self.app.models; - const account = await models.Account.findById(recipientId, null, myOptions); const userId = ctx.req.accessToken.userId; + const recipient = await models.Account.findById(recipientId, null, myOptions); + // Prevent sending messages to yourself if (recipientId == userId) return false; - if (!account) + if (!recipient) throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`); - const query = `SELECT worker_isWorking(?) isWorking`; - const [result] = await Self.rawSql(query, [recipientId], myOptions); + const {data} = await Self.getUserStatus(recipient.name); + if (data) { + if (data.status === 'offline') { + // Send message to department room + const workerDepartment = await models.WorkerDepartment.findById(recipientId, { + include: { + relation: 'department' + } + }, myOptions); + const department = workerDepartment && workerDepartment.department(); + const channelName = department && department.chatName; - if (!result.isWorking) { - const workerDepartment = await models.WorkerDepartment.findById(recipientId, { - include: { - relation: 'department' - } - }, myOptions); - const department = workerDepartment && workerDepartment.department(); - const channelName = department && department.chatName; + if (channelName) + return Self.send(ctx, `#${channelName}`, `@${recipient.name} ➔ ${message}`); + } else + return Self.send(ctx, `@${recipient.name}`, message); + } + }; - if (channelName) - return Self.send(ctx, `#${channelName}`, `@${account.name} ➔ ${message}`); + /** + * Returns the current user status on Rocketchat + * + * @param {string} username - The recipient user name + * @return {Promise} - The request promise + */ + Self.getUserStatus = async function getUserStatus(username) { + if (process.env.NODE_ENV !== 'production') { + return new Promise(resolve => { + return resolve({ + data: { + status: 'online' + } + }); + }); } - return Self.send(ctx, `@${account.name}`, message); + const login = await Self.getServiceAuth(); + + const options = { + params: {username}, + headers: { + 'X-Auth-Token': login.auth.token, + 'X-User-Id': login.auth.userId + }, + }; + + return axios.get(`${login.api}/users.getStatus`, options); }; }; diff --git a/back/methods/chat/spec/sendCheckingPresence.spec.js b/back/methods/chat/spec/sendCheckingPresence.spec.js index e9c61fd21f..2c48ef02ca 100644 --- a/back/methods/chat/spec/sendCheckingPresence.spec.js +++ b/back/methods/chat/spec/sendCheckingPresence.spec.js @@ -1,46 +1,62 @@ -const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; describe('Chat sendCheckingPresence()', () => { const today = new Date(); today.setHours(6, 0); const ctx = {req: {accessToken: {userId: 1}}}; - const chatModel = app.models.Chat; + const chatModel = models.Chat; const departmentId = 23; const workerId = 1107; - it(`should call send() method with the worker name if he's currently working then return a response`, async() => { + it(`should call to send() method with "@HankPym" as recipient argument`, async() => { spyOn(chatModel, 'send').and.callThrough(); - - const timeEntry = await app.models.WorkerTimeControl.create({ - userFk: workerId, - timed: today, - manual: false, - direction: 'in' - }); + spyOn(chatModel, 'getUserStatus').and.returnValue( + new Promise(resolve => { + return resolve({ + data: { + status: 'online' + } + }); + }) + ); const response = await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something'); expect(response.statusCode).toEqual(200); expect(response.message).toEqual('Fake notification sent'); expect(chatModel.send).toHaveBeenCalledWith(ctx, '@HankPym', 'I changed something'); - - // restores - await app.models.WorkerTimeControl.destroyById(timeEntry.id); }); - it(`should call to send() method with the worker department channel if he's not currently working then return a response`, async() => { + it(`should call to send() method with "#cooler" as recipient argument`, async() => { spyOn(chatModel, 'send').and.callThrough(); + spyOn(chatModel, 'getUserStatus').and.returnValue( + new Promise(resolve => { + return resolve({ + data: { + status: 'offline' + } + }); + }) + ); - const department = await app.models.Department.findById(departmentId); - await department.updateAttribute('chatName', 'cooler'); + const tx = await models.Claim.beginTransaction({}); - const response = await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something'); + try { + const options = {transaction: tx}; - expect(response.statusCode).toEqual(200); - expect(response.message).toEqual('Fake notification sent'); - expect(chatModel.send).toHaveBeenCalledWith(ctx, '#cooler', '@HankPym ➔ I changed something'); + const department = await models.Department.findById(departmentId, null, options); + await department.updateAttribute('chatName', 'cooler'); - // restores - await department.updateAttribute('chatName', null); + const response = await chatModel.sendCheckingPresence(ctx, workerId, 'I changed something'); + + expect(response.statusCode).toEqual(200); + expect(response.message).toEqual('Fake notification sent'); + expect(chatModel.send).toHaveBeenCalledWith(ctx, '#cooler', '@HankPym ➔ I changed something'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js new file mode 100644 index 0000000000..c6712bb658 --- /dev/null +++ b/back/methods/docuware/checkFile.js @@ -0,0 +1,93 @@ +const got = require('got'); + +module.exports = Self => { + Self.remoteMethodCtx('checkFile', { + description: 'Check if exist docuware file', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The id', + http: {source: 'path'} + }, + { + arg: 'fileCabinet', + type: 'string', + required: true, + description: 'The fileCabinet name' + }, + { + arg: 'dialog', + type: 'string', + required: true, + description: 'The dialog name' + } + ], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/:id/checkFile`, + verb: 'POST' + } + }); + + Self.checkFile = async function(ctx, id, fileCabinet, dialog) { + const myUserId = ctx.req.accessToken.userId; + if (!myUserId) + return false; + + const models = Self.app.models; + const docuwareConfig = await models.DocuwareConfig.findOne(); + const docuwareInfo = await models.Docuware.findOne({ + where: { + code: fileCabinet, + dialogName: dialog + } + }); + + const docuwareUrl = docuwareConfig.url; + const cookie = docuwareConfig.token; + const fileCabinetName = docuwareInfo.fileCabinetName; + const find = docuwareInfo.find; + const options = { + 'headers': { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Cookie': cookie + } + }; + const searchFilter = { + condition: [ + { + DBName: find, + Value: [id] + } + ] + }; + + try { + // get fileCabinetId + const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); + const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; + const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; + + // get dialog + const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); + const dialogJson = JSON.parse(dialogResponse.body).Dialog; + const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id; + + // get docuwareID + Object.assign(options, {'body': JSON.stringify(searchFilter)}); + const response = await got.post( + `${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options); + JSON.parse(response.body).Items[0].Id; + + return true; + } catch (error) { + return false; + } + }; +}; diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js new file mode 100644 index 0000000000..489a07e343 --- /dev/null +++ b/back/methods/docuware/download.js @@ -0,0 +1,120 @@ +/* eslint max-len: ["error", { "code": 180 }]*/ +const got = require('got'); +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('download', { + description: 'Download an docuware PDF', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The id', + http: {source: 'path'} + }, + { + arg: 'fileCabinet', + type: 'string', + description: 'The id', + http: {source: 'path'} + }, + { + arg: 'dialog', + type: 'string', + description: 'The id', + http: {source: 'path'} + } + ], + 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/download/:fileCabinet/:dialog`, + verb: 'GET' + } + }); + + Self.download = async function(ctx, id, fileCabinet, dialog) { + const myUserId = ctx.req.accessToken.userId; + if (!myUserId) + throw new UserError(`You don't have enough privileges`); + + const models = Self.app.models; + const docuwareConfig = await models.DocuwareConfig.findOne(); + const docuwareInfo = await models.Docuware.findOne({ + where: { + code: fileCabinet, + dialogName: dialog + } + }); + + const docuwareUrl = docuwareConfig.url; + const cookie = docuwareConfig.token; + const fileCabinetName = docuwareInfo.fileCabinetName; + const find = docuwareInfo.find; + const options = { + 'headers': { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Cookie': cookie + } + }; + const searchFilter = { + condition: [ + { + DBName: find, + Value: [id] + } + ] + }; + + try { + // get fileCabinetId + const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); + const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; + const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; + + // get dialog + const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); + const dialogJson = JSON.parse(dialogResponse.body).Dialog; + const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id; + + // get docuwareID + Object.assign(options, {'body': JSON.stringify(searchFilter)}); + const response = await got.post(`${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options); + const docuwareId = JSON.parse(response.body).Items[0].Id; + + // download & save file + const fileName = `filename="${id}.pdf"`; + const contentType = 'application/pdf'; + const downloadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents/${docuwareId}/FileDownload?targetFileType=Auto&keepAnnotations=false`; + const downloadOptions = { + 'headers': { + 'Cookie': cookie + } + }; + + const stream = got.stream(downloadUri, downloadOptions); + + return [stream, contentType, fileName]; + } catch (error) { + if (error.code === 'ENOENT') + throw new UserError('The DOCUWARE PDF document does not exists'); + + throw error; + } + }; +}; diff --git a/back/methods/docuware/specs/checkFile.spec.js b/back/methods/docuware/specs/checkFile.spec.js new file mode 100644 index 0000000000..2ebde0df4b --- /dev/null +++ b/back/methods/docuware/specs/checkFile.spec.js @@ -0,0 +1,64 @@ +const models = require('vn-loopback/server/server').models; +const got = require('got'); + +describe('docuware download()', () => { + const ticketId = 1; + const userId = 9; + const ctx = { + req: { + + accessToken: {userId: userId}, + headers: {origin: 'http://localhost:5000'}, + } + }; + + const fileCabinetName = 'deliveryClientTest'; + const dialogDisplayName = 'find'; + const dialogName = 'findTest'; + + const gotGetResponse = { + body: JSON.stringify( + { + FileCabinet: [ + {Id: 12, Name: fileCabinetName} + ], + Dialog: [ + {Id: 34, DisplayName: dialogDisplayName} + ] + }) + }; + + it('should return exist file in docuware', async() => { + const gotPostResponse = { + body: JSON.stringify( + { + Items: [ + {Id: 56} + ], + }) + }; + + spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); + spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse))); + + const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName); + + expect(result).toEqual(true); + }); + + it('should return not exist file in docuware', async() => { + const gotPostResponse = { + body: JSON.stringify( + { + Items: [], + }) + }; + + spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); + spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse))); + + const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName); + + expect(result).toEqual(false); + }); +}); diff --git a/back/methods/docuware/specs/download.spec.js b/back/methods/docuware/specs/download.spec.js new file mode 100644 index 0000000000..436063fd8e --- /dev/null +++ b/back/methods/docuware/specs/download.spec.js @@ -0,0 +1,50 @@ +const models = require('vn-loopback/server/server').models; +const got = require('got'); +const stream = require('stream'); + +describe('docuware download()', () => { + const userId = 9; + const ticketId = 1; + const ctx = { + req: { + + accessToken: {userId: userId}, + headers: {origin: 'http://localhost:5000'}, + } + }; + + it('should return the downloaded file name', async() => { + const fileCabinetName = 'deliveryClientTest'; + const dialogDisplayName = 'find'; + const dialogName = 'findTest'; + const gotGetResponse = { + body: JSON.stringify( + { + FileCabinet: [ + {Id: 12, Name: fileCabinetName} + ], + Dialog: [ + {Id: 34, DisplayName: dialogDisplayName} + ] + }) + }; + + const gotPostResponse = { + body: JSON.stringify( + { + Items: [ + {Id: 56} + ], + }) + }; + + spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); + spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse))); + spyOn(got, 'stream').and.returnValue(new stream.PassThrough({objectMode: true})); + + const result = await models.Docuware.download(ctx, ticketId, fileCabinetName, dialogName); + + expect(result[1]).toEqual('application/pdf'); + expect(result[2]).toEqual(`filename="${ticketId}.pdf"`); + }); +}); diff --git a/back/model-config.json b/back/model-config.json index 8ad15a16a7..4c79d565b8 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -44,6 +44,12 @@ "DmsType": { "dataSource": "vn" }, + "Docuware": { + "dataSource": "vn" + }, + "DocuwareConfig": { + "dataSource": "vn" + }, "EmailUser": { "dataSource": "vn" }, diff --git a/back/models/chat.js b/back/models/chat.js index 5487569c10..7d8468aae8 100644 --- a/back/models/chat.js +++ b/back/models/chat.js @@ -1,4 +1,5 @@ module.exports = Self => { + require('../methods/chat/getServiceAuth')(Self); require('../methods/chat/send')(Self); require('../methods/chat/sendCheckingPresence')(Self); require('../methods/chat/notifyIssues')(Self); diff --git a/back/models/docuware-config.json b/back/models/docuware-config.json new file mode 100644 index 0000000000..8ca76d8bad --- /dev/null +++ b/back/models/docuware-config.json @@ -0,0 +1,32 @@ +{ + "name": "DocuwareConfig", + "description": "Docuware config", + "base": "VnModel", + "options": { + "mysql": { + "table": "docuwareConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "url": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "acls": [ + { + "property": "*", + "accessType": "*", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/back/models/docuware.js b/back/models/docuware.js new file mode 100644 index 0000000000..8fd8065edb --- /dev/null +++ b/back/models/docuware.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/docuware/download')(Self); + require('../methods/docuware/checkFile')(Self); +}; diff --git a/back/models/docuware.json b/back/models/docuware.json new file mode 100644 index 0000000000..fb2ed919ea --- /dev/null +++ b/back/models/docuware.json @@ -0,0 +1,38 @@ +{ + "name": "Docuware", + "description": "Docuware sections", + "base": "VnModel", + "options": { + "mysql": { + "table": "docuware" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "code": { + "type": "string" + }, + "fileCabinetName": { + "type": "string" + }, + "dialogName": { + "type": "string" + }, + "find": { + "type": "string" + } + }, + "acls": [ + { + "property": "*", + "accessType": "*", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/db/changes/10420-valentines/00-ImportTicketAcl.sql b/db/changes/10420-valentines/00-ImportTicketAcl.sql new file mode 100644 index 0000000000..98192a39c2 --- /dev/null +++ b/db/changes/10420-valentines/00-ImportTicketAcl.sql @@ -0,0 +1,2 @@ +DELETE FROM salix.ACL +WHERE model = 'ClaimEnd' AND property = 'importTicketSales'; diff --git a/db/changes/10420-valentines/00-aclDocuware.sql b/db/changes/10420-valentines/00-aclDocuware.sql new file mode 100644 index 0000000000..21ed66c4c7 --- /dev/null +++ b/db/changes/10420-valentines/00-aclDocuware.sql @@ -0,0 +1,3 @@ +INSERT INTO salix.ACL +(model, property, accessType, permission, principalType, principalId) +VALUES('Docuware', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10420-valentines/00-docuware.sql b/db/changes/10420-valentines/00-docuware.sql new file mode 100644 index 0000000000..7cabd135f8 --- /dev/null +++ b/db/changes/10420-valentines/00-docuware.sql @@ -0,0 +1,11 @@ +CREATE TABLE `vn`.`docuware` ( + `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `code` varchar(50) NULL, + `fileCabinetName` varchar(50) NULL, + `dialogName` varchar(255) DEFAULT NULL, + `find` varchar(50) DEFAULT NULL +); + +INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName` , `find`) + VALUES + ('deliveryClient', 'Albaranes cliente', 'findTicket', 'N__ALBAR_N'); \ No newline at end of file diff --git a/db/changes/10420-valentines/00-docuwareConfig.sql b/db/changes/10420-valentines/00-docuwareConfig.sql new file mode 100644 index 0000000000..1ba19af6d5 --- /dev/null +++ b/db/changes/10420-valentines/00-docuwareConfig.sql @@ -0,0 +1,9 @@ +CREATE TABLE `vn`.`docuwareConfig` ( + `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `url` varchar(75) NULL, + `token` varchar(1000) DEFAULT NULL +); + +INSERT INTO `vn`.`docuwareConfig` (`url`) + VALUES + ('https://verdnatura.docuware.cloud/docuware/platform'); \ No newline at end of file diff --git a/db/changes/10430-ash/delete.keep b/db/changes/10430-ash/delete.keep new file mode 100644 index 0000000000..8fe7322e3d --- /dev/null +++ b/db/changes/10430-ash/delete.keep @@ -0,0 +1 @@ +delete file \ No newline at end of file diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 07eaf23fdd..03daded01c 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1703,10 +1703,10 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`) INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `observation`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created` ) VALUES - (1, CURDATE(), 1, 'observation one', 1101, 18, 3, 0, CURDATE()), - (2, CURDATE(), 2, 'observation two', 1101, 18, 3, 0, CURDATE()), - (3, CURDATE(), 3, 'observation three', 1101, 18, 1, 1, CURDATE()), - (4, CURDATE(), 3, 'observation four', 1104, 18, 5, 0, CURDATE()); + (1, CURDATE(), 1, 'Cu nam labores lobortis definiebas, ei aliquyam salutatus persequeris quo, cum eu nemore fierent dissentiunt. Per vero dolor id, vide democritum scribentur eu vim, pri erroribus temporibus ex.', 1101, 18, 3, 0, CURDATE()), + (2, CURDATE(), 2, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.', 1101, 18, 3, 0, CURDATE()), + (3, CURDATE(), 3, 'An vim commodo dolorem volutpat, cu expetendis voluptatum usu, et mutat consul adversarium his. His natum numquam legimus an, diam fabulas mei ut. Melius fabellas sadipscing vel id. Partem diceret mandamus mea ne, has te tempor nostrud. Aeque nostro eum no.', 1101, 18, 1, 1, CURDATE()), + (4, CURDATE(), 3, 'Wisi forensibus mnesarchum in cum. Per id impetus abhorreant, his no magna definiebas, inani rationibus in quo. Ut vidisse dolores est, ut quis nominavi mel. Ad pri quod apeirian concludaturque.', 1104, 18, 5, 0, CURDATE()); INSERT INTO `vn`.`claimBeginning`(`id`, `claimFk`, `saleFk`, `quantity`) VALUES @@ -2443,3 +2443,11 @@ INSERT INTO `bs`.`defaulter` (`clientFk`, `amount`, `created`, `defaulterSinced` (1103, 500, CURDATE(), CURDATE()), (1107, 500, CURDATE(), CURDATE()), (1109, 500, CURDATE(), CURDATE()); + +INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName` , `find`) + VALUES + ('deliveryClientTest', 'deliveryClientTest', 'findTest', 'word'); + +INSERT INTO `vn`.`docuwareConfig` (`url`) + VALUES + ('https://verdnatura.docuware.cloud/docuware/platform'); \ No newline at end of file diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index f0a5c37b5f..0ce29ef073 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -305,11 +305,11 @@ export default { anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr', }, clientDefaulter: { - anyClient: 'vn-client-defaulter-index vn-tbody > vn-tr', - firstClientName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2) > span', - firstSalesPersonName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(3) > span', - firstObservation: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]', - allDefaulterCheckbox: 'vn-client-defaulter-index vn-thead vn-multi-check', + anyClient: 'vn-client-defaulter-index tbody > tr', + firstClientName: 'vn-client-defaulter-index tbody > tr:nth-child(1) > td:nth-child(2) > span', + firstSalesPersonName: 'vn-client-defaulter-index tbody > tr:nth-child(1) > td:nth-child(3) > span', + firstObservation: 'vn-client-defaulter-index tbody > tr:nth-child(1) > td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]', + allDefaulterCheckbox: 'vn-client-defaulter-index thead vn-multi-check', addObservationButton: 'vn-client-defaulter-index vn-button[icon="icon-notes"]', observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', saveButton: 'button[response="accept"]' @@ -680,13 +680,13 @@ export default { header: 'vn-claim-summary > vn-card > h5', state: 'vn-claim-summary vn-label-value[label="State"] > section > span', observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"]', - firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span', + firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span', firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img', itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor', itemDescriptorPopoverItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a', - firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span', + firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span', firstDevelopmentWorkerGoToClientButton: '.vn-popover vn-worker-descriptor vn-quick-link[icon="person"] > a', - firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(6) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span', + firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span', firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor' }, claimBasicData: { @@ -722,10 +722,7 @@ export default { }, claimAction: { importClaimButton: 'vn-claim-action vn-button[label="Import claim"]', - importTicketButton: 'vn-claim-action vn-button[label="Import ticket"]', - secondImportableTicket: '.vn-popover.shown .content > div > vn-table > div > vn-tbody > vn-tr:nth-child(2)', - firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]', - secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]', + anyLine: 'vn-claim-action vn-tbody > vn-tr', firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]', isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]' }, diff --git a/e2e/paths/02-client/21_defaulter.spec.js b/e2e/paths/02-client/21_defaulter.spec.js index 89b5c5761d..3630a958ae 100644 --- a/e2e/paths/02-client/21_defaulter.spec.js +++ b/e2e/paths/02-client/21_defaulter.spec.js @@ -28,8 +28,8 @@ describe('Client defaulter path', () => { const salesPersonName = await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText'); - expect(clientName).toEqual('Ororo Munroe'); - expect(salesPersonName).toEqual('salesPerson'); + expect(clientName).toEqual('Batman'); + expect(salesPersonName).toEqual('salesPersonNick'); }); it('should first observation not changed', async() => { @@ -65,6 +65,7 @@ describe('Client defaulter path', () => { it('should first observation changed', async() => { const message = await page.waitForSnackbar(); + await page.waitForSelector(selectors.clientDefaulter.firstObservation); const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value'); expect(message.text).toContain('Observation saved!'); diff --git a/e2e/paths/06-claim/04_claim_action.spec.js b/e2e/paths/06-claim/04_claim_action.spec.js index 1170e0c9cf..62a0ac232a 100644 --- a/e2e/paths/06-claim/04_claim_action.spec.js +++ b/e2e/paths/06-claim/04_claim_action.spec.js @@ -24,22 +24,6 @@ describe('Claim action path', () => { expect(message.text).toContain('Data saved!'); }); - it('should import the second importable ticket', async() => { - await page.waitToClick(selectors.claimAction.importTicketButton); - await page.waitToClick(selectors.claimAction.secondImportableTicket); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - }); - - it('should edit the second line destination field', async() => { - await page.waitForContentLoaded(); - await page.autocompleteSearch(selectors.claimAction.secondLineDestination, 'Bueno'); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - }); - it('should delete the first line', async() => { await page.waitToClick(selectors.claimAction.firstDeleteLine); const message = await page.waitForSnackbar(); @@ -47,18 +31,11 @@ describe('Claim action path', () => { expect(message.text).toContain('Data saved!'); }); - it('should refresh the view to check the remaining line is the expected one', async() => { + it('should refresh the view to check not have lines', async() => { await page.reloadSection('claim.card.action'); - const result = await page.waitToGetProperty(selectors.claimAction.firstLineDestination, 'value'); + const result = await page.countElement(selectors.claimAction.anyLine); - expect(result).toEqual('Bueno'); - }); - - it('should delete the current first line', async() => { - await page.waitToClick(selectors.claimAction.firstDeleteLine); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); + expect(result).toEqual(0); }); it('should check the "is paid with mana" checkbox', async() => { diff --git a/e2e/paths/06-claim/05_summary.spec.js b/e2e/paths/06-claim/05_summary.spec.js index cea5edb559..589b3b6cb8 100644 --- a/e2e/paths/06-claim/05_summary.spec.js +++ b/e2e/paths/06-claim/05_summary.spec.js @@ -1,3 +1,4 @@ + import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; @@ -38,7 +39,7 @@ describe('Claim summary path', () => { it('should display the observation', async() => { const result = await page.waitToGetProperty(selectors.claimSummary.observation, 'value'); - expect(result).toContain('observation four'); + expect(result).toContain('Wisi forensibus mnesarchum in cum. Per id impetus abhorreant'); }); it('should display the claimed line(s)', async() => { diff --git a/modules/claim/back/methods/claim-end/importTicketSales.js b/modules/claim/back/methods/claim-end/importTicketSales.js deleted file mode 100644 index 6dd64be363..0000000000 --- a/modules/claim/back/methods/claim-end/importTicketSales.js +++ /dev/null @@ -1,61 +0,0 @@ -module.exports = Self => { - Self.remoteMethodCtx('importTicketSales', { - description: 'Imports lines from claimBeginning to a new ticket with specific shipped, landed dates, agency and company', - accessType: 'WRITE', - accepts: [{ - arg: 'params', - type: 'object', - http: {source: 'body'} - }], - returns: { - type: ['Object'], - root: true - }, - http: { - path: `/importTicketSales`, - verb: 'POST' - } - }); - - Self.importTicketSales = async(ctx, params, options) => { - let models = Self.app.models; - let userId = ctx.req.accessToken.userId; - - let tx; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } - - try { - const worker = await models.Worker.findOne({where: {userFk: userId}}, myOptions); - - let ticketSales = await models.Sale.find({ - where: {ticketFk: params.ticketFk} - }, myOptions); - - let claimEnds = []; - ticketSales.forEach(sale => { - claimEnds.push({ - saleFk: sale.id, - claimFk: params.claimFk, - workerFk: worker.id - }); - }); - - const createdClaimEnds = await Self.create(claimEnds, myOptions); - - if (tx) await tx.commit(); - - return createdClaimEnds; - } catch (e) { - if (tx) await tx.rollback(); - throw e; - } - }; -}; diff --git a/modules/claim/back/methods/claim-end/specs/importTicketSales.spec.js b/modules/claim/back/methods/claim-end/specs/importTicketSales.spec.js deleted file mode 100644 index 93f11e21fe..0000000000 --- a/modules/claim/back/methods/claim-end/specs/importTicketSales.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('Claim importTicketSales()', () => { - it('should import sales to a claim actions from an specific ticket', async() => { - const ctx = {req: {accessToken: {userId: 5}}}; - - const tx = await app.models.Entry.beginTransaction({}); - try { - const options = {transaction: tx}; - - const claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { - claimFk: 1, - ticketFk: 1 - }, options); - - expect(claimEnds.length).toEqual(4); - expect(claimEnds[0].saleFk).toEqual(1); - expect(claimEnds[2].saleFk).toEqual(3); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/claim/back/methods/claim/regularizeClaim.js b/modules/claim/back/methods/claim/regularizeClaim.js index d1fe7c13ef..29c7320f54 100644 --- a/modules/claim/back/methods/claim/regularizeClaim.js +++ b/modules/claim/back/methods/claim/regularizeClaim.js @@ -1,6 +1,7 @@ module.exports = Self => { Self.remoteMethodCtx('regularizeClaim', { - description: 'Imports lines from claimBeginning to a new ticket with specific shipped, landed dates, agency and company', + description: `Imports lines from claimBeginning to a new ticket + with specific shipped, landed dates, agency and company`, accessType: 'WRITE', accepts: [{ arg: 'id', diff --git a/modules/claim/back/methods/claim/specs/createFromSales.spec.js b/modules/claim/back/methods/claim/specs/createFromSales.spec.js index 097dcc0d92..9151c361e0 100644 --- a/modules/claim/back/methods/claim/specs/createFromSales.spec.js +++ b/modules/claim/back/methods/claim/specs/createFromSales.spec.js @@ -57,7 +57,7 @@ describe('Claim createFromSales()', () => { const todayMinusEightDays = new Date(); todayMinusEightDays.setDate(todayMinusEightDays.getDate() - 8); - const ticket = await models.Ticket.findById(ticketId, options); + const ticket = await models.Ticket.findById(ticketId, null, options); await ticket.updateAttribute('landed', todayMinusEightDays, options); const claim = await models.Claim.createFromSales(ctx, ticketId, newSale, options); @@ -88,7 +88,7 @@ describe('Claim createFromSales()', () => { const todayMinusEightDays = new Date(); todayMinusEightDays.setDate(todayMinusEightDays.getDate() - 8); - const ticket = await models.Ticket.findById(ticketId, options); + const ticket = await models.Ticket.findById(ticketId, null, options); await ticket.updateAttribute('landed', todayMinusEightDays, options); await models.Claim.createFromSales(ctx, ticketId, newSale, options); diff --git a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js index 8ea310772e..bf26d22554 100644 --- a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js @@ -1,9 +1,10 @@ -const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; describe('claim regularizeClaim()', () => { + const userId = 18; const ctx = { req: { - accessToken: {userId: 18}, + accessToken: {userId: userId}, headers: {origin: 'http://localhost'} } }; @@ -11,8 +12,9 @@ describe('claim regularizeClaim()', () => { return params.nickname; }; - const chatModel = app.models.Chat; - const claimFk = 1; + const chatModel = models.Chat; + const claimId = 1; + const ticketId = 1; const pendentState = 1; const resolvedState = 3; const trashDestination = 2; @@ -21,27 +23,40 @@ describe('claim regularizeClaim()', () => { let claimEnds = []; let trashTicket; + async function importTicket(ticketId, claimId, userId, options) { + const ticketSales = await models.Sale.find({ + where: {ticketFk: ticketId} + }, options); + const claimEnds = []; + for (let sale of ticketSales) { + claimEnds.push({ + saleFk: sale.id, + claimFk: claimId, + workerFk: userId + }); + } + + return await models.ClaimEnd.create(claimEnds, options); + } + it('should send a chat message with value "Trash" and then change claim state to resolved', async() => { - const tx = await app.models.Claim.beginTransaction({}); + const tx = await models.Claim.beginTransaction({}); try { const options = {transaction: tx}; spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { - claimFk: claimFk, - ticketFk: 1 - }, options); + claimEnds = await importTicket(ticketId, claimId, userId, options); for (claimEnd of claimEnds) await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options); - let claimBefore = await app.models.Claim.findById(claimFk, null, options); - await app.models.Claim.regularizeClaim(ctx, claimFk, options); - let claimAfter = await app.models.Claim.findById(claimFk, null, options); + let claimBefore = await models.Claim.findById(claimId, null, options); + await models.Claim.regularizeClaim(ctx, claimId, options); + let claimAfter = await models.Claim.findById(claimId, null, options); - trashTicket = await app.models.Ticket.findOne({where: {addressFk: 12}}, options); + trashTicket = await models.Ticket.findOne({where: {addressFk: 12}}, options); expect(trashTicket.addressFk).toEqual(trashAddress); expect(claimBefore.claimStateFk).toEqual(pendentState); @@ -57,22 +72,19 @@ describe('claim regularizeClaim()', () => { }); it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => { - const tx = await app.models.Claim.beginTransaction({}); + const tx = await models.Claim.beginTransaction({}); try { const options = {transaction: tx}; spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { - claimFk: claimFk, - ticketFk: 1 - }, options); + claimEnds = await importTicket(ticketId, claimId, userId, options); for (claimEnd of claimEnds) await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); - await app.models.Claim.regularizeClaim(ctx, claimFk, options); + await models.Claim.regularizeClaim(ctx, claimId, options); expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); @@ -85,22 +97,19 @@ describe('claim regularizeClaim()', () => { }); it('should send a chat message to the salesPerson when claim isPickUp is enabled', async() => { - const tx = await app.models.Claim.beginTransaction({}); + const tx = await models.Claim.beginTransaction({}); try { const options = {transaction: tx}; spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { - claimFk: claimFk, - ticketFk: 1 - }, options); + claimEnds = await importTicket(ticketId, claimId, userId, options); for (claimEnd of claimEnds) await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); - await app.models.Claim.regularizeClaim(ctx, claimFk, options); + await models.Claim.regularizeClaim(ctx, claimId, options); expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); diff --git a/modules/claim/back/models/claim-end.js b/modules/claim/back/models/claim-end.js deleted file mode 100644 index 73531cdfcd..0000000000 --- a/modules/claim/back/models/claim-end.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = Self => { - require('../methods/claim-end/importTicketSales')(Self); -}; diff --git a/modules/claim/front/action/index.html b/modules/claim/front/action/index.html index eeac01d785..6c0e2402d0 100644 --- a/modules/claim/front/action/index.html +++ b/modules/claim/front/action/index.html @@ -24,15 +24,9 @@ - - - - - - -
- - - - ID - F. envio - Agencia - Almacen - - - - - {{::ticket.id}} - {{::ticket.shipped | date: 'dd/MM/yyyy'}} - {{::ticket.agencyMode.name}} - {{::ticket.warehouse.name}} - - - -
-
diff --git a/modules/claim/front/action/index.js b/modules/claim/front/action/index.js index cddc6873fd..4b32142119 100644 --- a/modules/claim/front/action/index.js +++ b/modules/claim/front/action/index.js @@ -60,36 +60,6 @@ export default class Controller extends Section { }); } - showLastTickets(event) { - let pastWeek = new Date(); - pastWeek.setDate(-7); - - let filter = { - include: [ - {relation: 'agencyMode', fields: ['name']}, - {relation: 'warehouse', fields: ['name']} - ], - where: { - created: {gt: pastWeek}, - clientFk: this.claim.clientFk - } - }; - this.$.lastTicketsModel.filter = filter; - this.$.lastTicketsModel.refresh(); - this.$.lastTicketsPopover.show(event); - } - - importTicketLines(ticketFk) { - let data = {claimFk: this.$params.id, ticketFk: ticketFk}; - - let query = `ClaimEnds/importTicketSales`; - this.$http.post(query, data).then(() => { - this.vnApp.showSuccess(this.$t('Data saved!')); - this.$.lastTicketsPopover.hide(); - this.$.model.refresh(); - }); - } - regularize() { const query = `Claims/${this.$params.id}/regularizeClaim`; return this.$http.post(query).then(() => { diff --git a/modules/claim/front/action/index.spec.js b/modules/claim/front/action/index.spec.js index 9fb9b75ebf..f25f2eb3c1 100644 --- a/modules/claim/front/action/index.spec.js +++ b/modules/claim/front/action/index.spec.js @@ -67,35 +67,6 @@ describe('claim', () => { }); }); - describe('showLastTickets()', () => { - it('should get a list of tickets and call lastTicketsPopover show() method', () => { - jest.spyOn(controller.$.lastTicketsModel, 'refresh'); - jest.spyOn(controller.$.lastTicketsPopover, 'show'); - - controller.showLastTickets({}); - - expect(controller.$.lastTicketsModel.refresh).toHaveBeenCalled(); - expect(controller.$.lastTicketsPopover.show).toHaveBeenCalled(); - }); - }); - - describe('importTicketLines()', () => { - it('should perform a post query and add lines from an existent ticket', () => { - jest.spyOn(controller.$.model, 'refresh'); - jest.spyOn(controller.vnApp, 'showSuccess'); - jest.spyOn(controller.$.lastTicketsPopover, 'hide'); - - let data = {claimFk: 1, ticketFk: 1}; - $httpBackend.expect('POST', `ClaimEnds/importTicketSales`, data).respond({}); - controller.importTicketLines(1); - $httpBackend.flush(); - - expect(controller.$.model.refresh).toHaveBeenCalledWith(); - expect(controller.vnApp.showSuccess).toHaveBeenCalled(); - expect(controller.$.lastTicketsPopover.hide).toHaveBeenCalledWith(); - }); - }); - describe('regularize()', () => { it('should perform a post query and reload the claim card', () => { jest.spyOn(controller.card, 'reload'); diff --git a/modules/claim/front/action/locale/es.yml b/modules/claim/front/action/locale/es.yml index 3ff25dca5e..22b2740b35 100644 --- a/modules/claim/front/action/locale/es.yml +++ b/modules/claim/front/action/locale/es.yml @@ -3,8 +3,6 @@ Action: Actuaciones Total claimed: Total Reclamado Import claim: Importar reclamacion Imports claim details: Importa detalles de la reclamacion -Import ticket: Importar ticket -Imports ticket lines: Importa las lineas de un ticket Regularize: Regularizar Do you want to insert greuges?: Desea insertar greuges? Insert greuges on client card: Insertar greuges en la ficha del cliente diff --git a/modules/claim/front/summary/index.html b/modules/claim/front/summary/index.html index c1d056eed8..282c55b006 100644 --- a/modules/claim/front/summary/index.html +++ b/modules/claim/front/summary/index.html @@ -41,28 +41,14 @@ value="{{$ctrl.summary.claim.worker.user.nickname}}"> - + - - - - - +

Action

+ + + + + + diff --git a/modules/claim/front/summary/style.scss b/modules/claim/front/summary/style.scss index e0542dea00..e812136584 100644 --- a/modules/claim/front/summary/style.scss +++ b/modules/claim/front/summary/style.scss @@ -7,4 +7,7 @@ vn-claim-summary { .photo .image { border-radius: 3px; } + vn-textarea *{ + height: 80px; + } } \ No newline at end of file diff --git a/modules/client/back/methods/client/specs/updateFiscalData.spec.js b/modules/client/back/methods/client/specs/updateFiscalData.spec.js index 75273a39ff..7c0bc05998 100644 --- a/modules/client/back/methods/client/specs/updateFiscalData.spec.js +++ b/modules/client/back/methods/client/specs/updateFiscalData.spec.js @@ -35,7 +35,7 @@ describe('Client updateFiscalData', () => { try { const options = {transaction: tx}; - const client = await models.Client.findById(clientId, options); + const client = await models.Client.findById(clientId, null, options); await client.updateAttribute('isTaxDataChecked', false, options); const ctx = {req: {accessToken: {userId: salesAssistantId}}}; diff --git a/modules/client/back/methods/defaulter/filter.js b/modules/client/back/methods/defaulter/filter.js index c06d1c51b2..095b9b1c17 100644 --- a/modules/client/back/methods/defaulter/filter.js +++ b/modules/client/back/methods/defaulter/filter.js @@ -56,18 +56,18 @@ module.exports = Self => { FROM ( SELECT DISTINCT c.id clientFk, - c.name clientName, + c.socialName clientName, c.salesPersonFk, - u.name salesPersonName, + u.nickname salesPersonName, d.amount, co.created, - CONCAT(DATE(co.created), ' ', co.text) observation, + co.text observation, uw.id workerFk, - uw.name workerName, + uw.nickname workerName, c.creditInsurance, d.defaulterSinced FROM vn.defaulter d - JOIN vn.client c ON c.id = d.clientFk + JOIN vn.client c ON c.id = d.clientFk LEFT JOIN vn.clientObservation co ON co.clientFk = c.id LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user uw ON uw.id = co.workerFk diff --git a/modules/client/back/methods/defaulter/specs/filter.spec.js b/modules/client/back/methods/defaulter/specs/filter.spec.js index 145bb51321..ca14d1e437 100644 --- a/modules/client/back/methods/defaulter/specs/filter.spec.js +++ b/modules/client/back/methods/defaulter/specs/filter.spec.js @@ -47,12 +47,12 @@ describe('defaulter filter()', () => { try { const options = {transaction: tx}; - const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}}; + const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'spider'}}; const result = await models.Defaulter.filter(ctx, null, options); const firstRow = result[0]; - expect(firstRow.clientName).toEqual('Bruce Wayne'); + expect(firstRow.clientName).toEqual('Spider man'); await tx.rollback(); } catch (e) { diff --git a/modules/client/front/defaulter/index.html b/modules/client/front/defaulter/index.html index 121556df24..59f7b7d676 100644 --- a/modules/client/front/defaulter/index.html +++ b/modules/client/front/defaulter/index.html @@ -15,17 +15,18 @@ model="model"> - - - -
+ + + +
Total
+ value="{{$ctrl.balanceDueTotal | currency: 'EUR': 2}}">
@@ -38,90 +39,109 @@ icon="icon-notes">
-
- - - - - - - - Client - Comercial - - Balance D. - - - Author - - Last observation - - Credit I. - - From - - - - - - - - - - - {{::defaulter.clientName}} - - - - - {{::defaulter.salesPersonName | dashIfEmpty}} - - - {{::defaulter.amount}} - - - {{::defaulter.workerName | dashIfEmpty}} - - - - - - - {{::defaulter.creditInsurance}} - {{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}} - - - -
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Client + + Comercial + + Balance D. + + Author + + Last observation + + Last observation D. + + Credit I. + + From +
+ + + + + {{::defaulter.clientName}} + + + + {{::defaulter.salesPersonName | dashIfEmpty}} + + {{::defaulter.amount | currency: 'EUR': 2}} + + {{::defaulter.workerName | dashIfEmpty}} + + + + + + + {{::defaulter.created | date: 'dd/MM/yyyy'}} + + {{::defaulter.creditInsurance | currency: 'EUR': 2}}{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}
+
+ + + vn-id="client-descriptor"> + vn-id="worker-descriptor"> - - - - - Filter by selection - - - Exclude selection - - - Remove filter - - - Remove all filters - - - Copy value - - - - 0) { - for (let defaulter of this.checked) - balanceDueTotal += defaulter.amount; - - return balanceDueTotal; - } + for (let defaulter of defaulters) + balanceDueTotal += defaulter.amount; return balanceDueTotal; } @@ -32,6 +76,22 @@ export default class Controller extends Section { return checkedLines; } + chipColor(date) { + const day = 24 * 60 * 60 * 1000; + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const observationShipped = new Date(date); + observationShipped.setHours(0, 0, 0, 0); + + const difference = today - observationShipped; + + if (difference > (day * 20)) + return 'alert'; + if (difference > (day * 10)) + return 'warning'; + } + onResponse() { if (!this.defaulter.observation) throw new UserError(`The message can't be empty`); @@ -52,7 +112,10 @@ export default class Controller extends Section { exprBuilder(param, value) { switch (param) { + case 'creditInsurance': + case 'amount': case 'clientName': + case 'workerFk': case 'salesPersonFk': return {[`d.${param}`]: value}; } diff --git a/modules/client/front/defaulter/index.spec.js b/modules/client/front/defaulter/index.spec.js index 6428952ec6..8d6c8c307e 100644 --- a/modules/client/front/defaulter/index.spec.js +++ b/modules/client/front/defaulter/index.spec.js @@ -39,11 +39,7 @@ describe('client defaulter', () => { describe('balanceDueTotal() getter', () => { it('should return balance due total', () => { const data = controller.$.model.data; - data[1].checked = true; - data[2].checked = true; - - const checkedRows = controller.checked; - const expectedAmount = checkedRows[0].amount + checkedRows[1].amount; + const expectedAmount = data[0].amount + data[1].amount + data[2].amount; const result = controller.balanceDueTotal; @@ -51,6 +47,31 @@ describe('client defaulter', () => { }); }); + describe('chipColor()', () => { + it('should return undefined when the date is the present', () => { + let today = new Date(); + let result = controller.chipColor(today); + + expect(result).toEqual(undefined); + }); + + it('should return warning when the date is 10 days in the past', () => { + let pastDate = new Date(); + pastDate = pastDate.setDate(pastDate.getDate() - 11); + let result = controller.chipColor(pastDate); + + expect(result).toEqual('warning'); + }); + + it('should return alert when the date is 20 days in the past', () => { + let pastDate = new Date(); + pastDate = pastDate.setDate(pastDate.getDate() - 21); + let result = controller.chipColor(pastDate); + + expect(result).toEqual('alert'); + }); + }); + describe('onResponse()', () => { it('should return error for empty message', () => { let error; diff --git a/modules/client/front/defaulter/locale/es.yml b/modules/client/front/defaulter/locale/es.yml index 172a3125dc..3f046e8d67 100644 --- a/modules/client/front/defaulter/locale/es.yml +++ b/modules/client/front/defaulter/locale/es.yml @@ -1,7 +1,9 @@ -Last observation: Última observación Add observation: Añadir observación -Search client: Buscar clientes Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s) -Credit I.: Crédito A. Balance D.: Saldo V. +Credit I.: Crédito A. +Last observation: Última observación +Last observation D.: Fecha última O. +Last observation date: Fecha última observación +Search client: Buscar clientes Worker who made the last observation: Trabajador que ha realizado la última observación \ No newline at end of file diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html index 3b30f891cd..859486ab17 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.html +++ b/modules/invoiceOut/front/descriptor-menu/index.html @@ -10,7 +10,6 @@ name="showInvoicePdf" translate> Show invoice... -
Send invoice... - { Object.assign(myOptions, options); const route = await Self.app.models.Route.findById(id, null, myOptions); + const zoneAgencyModes = await Self.app.models.ZoneAgencyMode.find({ where: { agencyModeFk: route.agencyModeFk @@ -52,6 +53,12 @@ module.exports = Self => { fields: ['id', 'name'] } }, + { + relation: 'zone', + scope: { + fields: ['id', 'name'] + } + }, { relation: 'address', scope: { diff --git a/modules/route/back/methods/route/specs/unlink.spec.js b/modules/route/back/methods/route/specs/unlink.spec.js new file mode 100644 index 0000000000..808cedccc1 --- /dev/null +++ b/modules/route/back/methods/route/specs/unlink.spec.js @@ -0,0 +1,33 @@ +const models = require('vn-loopback/server/server').models; + +describe('route unlink()', () => { + it('should show no tickets since the link between zone and route for the give agencymode was removed', async() => { + const tx = await models.ZoneAgencyMode.beginTransaction({}); + const agencyModeId = 1; + const zoneId = 1; + routeId = 1; + + try { + const options = {transaction: tx}; + + let zoneAgencyModes = await models.ZoneAgencyMode.find(null, options); + let tickets = await models.Route.getSuggestedTickets(routeId, options); + + expect(zoneAgencyModes.length).toEqual(4); + expect(tickets.length).toEqual(3); + + await models.Route.unlink(agencyModeId, zoneId, options); + + zoneAgencyModes = await models.ZoneAgencyMode.find(null, options); + tickets = await models.Route.getSuggestedTickets(routeId, options); + + expect(zoneAgencyModes.length).toEqual(3); + expect(tickets.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/route/back/methods/route/unlink.js b/modules/route/back/methods/route/unlink.js new file mode 100644 index 0000000000..5a847e3377 --- /dev/null +++ b/modules/route/back/methods/route/unlink.js @@ -0,0 +1,42 @@ +module.exports = Self => { + Self.remoteMethod('unlink', { + description: 'Removes the matching entries from zoneAgencyMode', + accessType: 'WRITE', + accepts: [ + { + arg: 'agencyModeId', + type: 'number', + required: true, + description: 'The agencyMode id', + }, + { + arg: 'zoneId', + type: 'number', + required: true, + description: 'The zone id', + }, + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/unlink`, + verb: 'POST' + } + }); + + Self.unlink = async(agencyModeId, zoneId, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const where = { + agencyModeFk: agencyModeId, + zoneFk: zoneId + }; + + await Self.app.models.ZoneAgencyMode.destroyAll(where, myOptions); + }; +}; diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js index 1cfe0927e0..c82d1722e9 100644 --- a/modules/route/back/models/route.js +++ b/modules/route/back/models/route.js @@ -8,6 +8,7 @@ module.exports = Self => { require('../methods/route/insertTicket')(Self); require('../methods/route/clone')(Self); require('../methods/route/getSuggestedTickets')(Self); + require('../methods/route/unlink')(Self); Self.validate('kmStart', validateDistance, { message: 'Distance must be lesser than 1000' diff --git a/modules/route/front/tickets/index.html b/modules/route/front/tickets/index.html index 7d515b67cf..970c7574bc 100644 --- a/modules/route/front/tickets/index.html +++ b/modules/route/front/tickets/index.html @@ -150,7 +150,7 @@ PC Address - Warehouse + Zone @@ -174,7 +174,15 @@ {{::ticket.address.city}} {{::ticket.address.postalCode}} {{::ticket.address.street}} - {{::ticket.warehouse.name}} + + {{::ticket.zone.name}} + + + @@ -196,3 +204,11 @@ + + + + \ No newline at end of file diff --git a/modules/route/front/tickets/index.js b/modules/route/front/tickets/index.js index fd763e32f8..e74cbcd409 100644 --- a/modules/route/front/tickets/index.js +++ b/modules/route/front/tickets/index.js @@ -37,6 +37,19 @@ class Controller extends Section { }); } + unlinkZone(ticket) { + const params = { + agencyModeId: this.route.agencyModeFk, + zoneId: ticket.zoneFk, + }; + + const query = `Routes/unlink`; + this.$http.post(query, params).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + this.$.possibleTicketsModel.refresh(); + }); + } + getSelectedItems(items) { const selectedItems = []; diff --git a/modules/route/front/tickets/index.spec.js b/modules/route/front/tickets/index.spec.js index fbbe943600..092445e6f6 100644 --- a/modules/route/front/tickets/index.spec.js +++ b/modules/route/front/tickets/index.spec.js @@ -1,3 +1,4 @@ +/* eslint max-len: ["error", { "code": 150 }]*/ import './index'; describe('Route', () => { @@ -73,6 +74,32 @@ describe('Route', () => { }); }); + describe('unlink()', () => { + it('should call the route unlink endpoint with the agency and zone ids', () => { + controller.$.possibleTicketsModel = {refresh: jest.fn()}; + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.route = { + agencyModeFk: 1 + }; + + const ticket = { + zoneFk: 2, + }; + const params = { + agencyModeId: controller.route.agencyModeFk, + zoneId: ticket.zoneFk, + }; + + $httpBackend.expectPOST(`Routes/unlink`, params).respond('ok'); + controller.unlinkZone(ticket); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.$.possibleTicketsModel.refresh).toHaveBeenCalledWith(); + }); + }); + describe('getSelectedItems()', () => { it('should return the selected items', () => { let items = [ diff --git a/modules/route/front/tickets/locale/es.yml b/modules/route/front/tickets/locale/es.yml index c38d4115c3..6d63b4b6e4 100644 --- a/modules/route/front/tickets/locale/es.yml +++ b/modules/route/front/tickets/locale/es.yml @@ -11,4 +11,5 @@ The selected ticket is not suitable for this route: El ticket seleccionado no es PC: CP The route's vehicle doesn't have a delivery point: El vehículo de la ruta no tiene un punto de entrega The route doesn't have a vehicle: La ruta no tiene un vehículo -Population: Población \ No newline at end of file +Population: Población +Unlink selected zone?: Desvincular zona seleccionada? diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index ae5642cf32..d613fb5de3 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -20,14 +20,22 @@ - Show as PDF + as PDF + + as PDF + - Show as CSV + as CSV diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 9d4381f7c5..841dfa4099 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -84,6 +84,7 @@ class Controller extends Section { .then(() => { this.canStowaway(); this.isTicketEditable(); + this.hasDocuware(); }); } @@ -122,6 +123,15 @@ class Controller extends Section { }); } + hasDocuware() { + const params = { + fileCabinet: 'deliveryClient', + dialog: 'findTicket' + }; + this.$http.post(`Docuwares/${this.id}/checkFile`, params) + .then(res => this.hasDocuwareFile = res.data); + } + showCsvDeliveryNote() { this.vnReport.showCsv('delivery-note', { recipientId: this.ticket.client.id, diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 288c7508ba..0a80d08843 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -206,7 +206,8 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { it('should make a query and show a success snackbar', () => { jest.spyOn(controller.vnApp, 'showSuccess'); - $httpBackend.whenGET(`Tickets/16`).respond(); + $httpBackend.whenPOST(`Docuwares/${ticket.id}/checkFile`).respond(); + $httpBackend.whenGET(`Tickets/${ticket.id}`).respond(); $httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/createPdf`).respond(); controller.createPdfInvoice(); $httpBackend.flush(); @@ -275,4 +276,12 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { }); }); }); + + describe('hasDocuware()', () => { + it('should call hasDocuware method', () => { + $httpBackend.whenPOST(`Docuwares/${ticket.id}/checkFile`).respond(); + controller.hasDocuware(); + $httpBackend.flush(); + }); + }); }); diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index 1f4ee710cb..4a61556db8 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -1,7 +1,7 @@ Show Delivery Note...: Ver albarán... Send Delivery Note...: Enviar albarán... -Show as PDF: Ver como PDF -Show as CSV: Ver como CSV +as PDF: como PDF +as CSV: como CSV Send PDF: Enviar PDF Send CSV: Enviar CSV Send CSV Delivery Note: Enviar albarán en CSV diff --git a/modules/zone/back/models/agency.json b/modules/zone/back/models/agency.json index 9269b3db64..edec36f874 100644 --- a/modules/zone/back/models/agency.json +++ b/modules/zone/back/models/agency.json @@ -9,11 +9,11 @@ "properties": { "id": { "id": true, - "type": "Number", + "type": "number", "forceId": false }, "name": { - "type": "String", + "type": "string", "required": false } } diff --git a/modules/zone/back/models/zone.json b/modules/zone/back/models/zone.json index ad43bd6f6e..5d5970173a 100644 --- a/modules/zone/back/models/zone.json +++ b/modules/zone/back/models/zone.json @@ -13,10 +13,10 @@ "properties": { "id": { "id": true, - "type": "Number" + "type": "number" }, "name": { - "type": "String", + "type": "string", "required": true }, "hour": { @@ -24,22 +24,22 @@ "required": true }, "travelingDays": { - "type": "Number" + "type": "number" }, "price": { - "type": "Number" + "type": "number" }, "bonus": { - "type": "Number" + "type": "number" }, "isVolumetric": { - "type": "Boolean" + "type": "boolean" }, "inflation": { - "type": "Number" + "type": "number" }, "itemMaxSize": { - "type": "Number" + "type": "number" } }, "relations": { diff --git a/print/templates/reports/invoice/sql/intrastat.sql b/print/templates/reports/invoice/sql/intrastat.sql index e391056ec5..6bf72c158a 100644 --- a/print/templates/reports/invoice/sql/intrastat.sql +++ b/print/templates/reports/invoice/sql/intrastat.sql @@ -2,9 +2,13 @@ SELECT ir.id AS code, ir.description AS description, CAST(SUM(IFNULL(i.stems,1) * s.quantity) AS DECIMAL(10,2)) as stems, - CAST(SUM( weight) AS DECIMAL(10,2)) as netKg, + CAST(SUM(IF(sv.physicalWeight, sv.physicalWeight, i.density * sub.cm3delivery/1000000)) AS DECIMAL(10,2)) netKg, CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) AS subtotal - FROM vn.sale s + FROM vn.sale s + LEFT JOIN (SELECT ic.itemFk, ic.cm3, ic.cm3delivery + FROM vn.itemCost ic + WHERE ic.cm3 + GROUP BY ic.itemFk) sub ON s.itemFk = sub.itemFk LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id LEFT JOIN vn.ticket t ON t.id = s.ticketFk LEFT JOIN vn.invoiceOut io ON io.ref = t.refFk