diff --git a/CHANGELOG.md b/CHANGELOG.md index 76527ac83..d4a1e147f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +## [2330.01] - 2023-07-27 + +### Added +- (Artículos -> Vista Previa) Añadido campo "Plástico reciclado" +- (Rutas -> Troncales) Nueva sección +- (Tickets -> Opciones) Opción establecer peso +- (Clientes -> SMS) Nueva sección + +### Changed +- (General -> Iconos) Añadidos nuevos iconos +- (Clientes -> Razón social) Nuevas restricciones por pais + + +### Fixed + + ## [2328.01] - 2023-07-13 ### Added diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js index c0a4e8ef3..19224057c 100644 --- a/back/methods/docuware/checkFile.js +++ b/back/methods/docuware/checkFile.js @@ -1,7 +1,5 @@ -const axios = require('axios'); - module.exports = Self => { - Self.remoteMethodCtx('checkFile', { + Self.remoteMethod('checkFile', { description: 'Check if exist docuware file', accessType: 'READ', accepts: [ @@ -17,12 +15,16 @@ module.exports = Self => { required: true, description: 'The fileCabinet name' }, + { + arg: 'filter', + type: 'object', + description: 'The filter' + }, { arg: 'signed', type: 'boolean', - required: true, description: 'If pdf is necessary to be signed' - } + }, ], returns: { type: 'object', @@ -34,7 +36,7 @@ module.exports = Self => { } }); - Self.checkFile = async function(ctx, id, fileCabinet, signed) { + Self.checkFile = async function(id, fileCabinet, filter, signed) { const models = Self.app.models; const action = 'find'; @@ -45,39 +47,34 @@ module.exports = Self => { } }); - const searchFilter = { - condition: [ - { - DBName: docuwareInfo.findById, - - Value: [id] - } - ], - sortOrder: [ - { - Field: 'FILENAME', - Direction: 'Desc' - } - ] - }; + if (!filter) { + filter = { + condition: [ + { + DBName: docuwareInfo.findById, + Value: [id] + } + ], + sortOrder: [ + { + Field: 'FILENAME', + Direction: 'Desc' + } + ] + }; + } + if (signed) { + filter.condition.push({ + DBName: 'ESTADO', + Value: ['Firmado'] + }); + } try { - const options = await Self.getOptions(); - - const fileCabinetId = await Self.getFileCabinet(fileCabinet); - const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId); - - const response = await axios.post( - `${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, - searchFilter, - options.headers - ); - const [documents] = response.data.Items; + const response = await Self.get(fileCabinet, filter); + const [documents] = response.Items; if (!documents) return false; - const state = documents.Fields.find(field => field.FieldName == 'ESTADO'); - if (signed && state.Item != 'Firmado') return false; - return {id: documents.Id}; } catch (error) { return false; diff --git a/back/methods/docuware/core.js b/back/methods/docuware/core.js index 2053ddf85..74d922236 100644 --- a/back/methods/docuware/core.js +++ b/back/methods/docuware/core.js @@ -1,59 +1,6 @@ const axios = require('axios'); module.exports = Self => { - /** - * Returns the dialog id - * - * @param {string} code - The fileCabinet name - * @param {string} action - The fileCabinet name - * @param {string} fileCabinetId - Optional The fileCabinet name - * @return {number} - The fileCabinet id - */ - Self.getDialog = async(code, action, fileCabinetId) => { - const docuwareInfo = await Self.app.models.Docuware.findOne({ - where: { - code: code, - action: action - } - }); - if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code); - - const options = await Self.getOptions(); - - if (!process.env.NODE_ENV) - return Math.round(); - - const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers); - const dialogs = response.data.Dialog; - const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id; - - return dialogId; - }; - - /** - * Returns the fileCabinetId - * - * @param {string} code - The fileCabinet code - * @return {number} - The fileCabinet id - */ - Self.getFileCabinet = async code => { - const options = await Self.getOptions(); - const docuwareInfo = await Self.app.models.Docuware.findOne({ - where: { - code: code - } - }); - - if (!process.env.NODE_ENV) - return Math.round(); - - const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers); - const fileCabinets = fileCabinetResponse.data.FileCabinet; - const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id; - - return fileCabinetId; - }; - /** * Returns basic headers * @@ -75,4 +22,139 @@ module.exports = Self => { headers }; }; + + /** + * Returns the dialog id + * + * @param {string} code - The fileCabinet name + * @param {string} action - The fileCabinet name + * @param {string} fileCabinetId - Optional The fileCabinet name + * @return {number} - The fileCabinet id + */ + Self.getDialog = async(code, action, fileCabinetId) => { + if (!process.env.NODE_ENV) + return Math.floor(Math.random() + 100); + + const docuwareInfo = await Self.app.models.Docuware.findOne({ + where: { + code, + action + } + }); + if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code); + + const options = await Self.getOptions(); + + const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers); + const dialogs = response.data.Dialog; + const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id; + + return dialogId; + }; + + /** + * Returns the fileCabinetId + * + * @param {string} code - The fileCabinet code + * @return {number} - The fileCabinet id + */ + Self.getFileCabinet = async code => { + if (!process.env.NODE_ENV) + return Math.floor(Math.random() + 100); + + const options = await Self.getOptions(); + const docuwareInfo = await Self.app.models.Docuware.findOne({ + where: { + code + } + }); + + const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers); + const fileCabinets = fileCabinetResponse.data.FileCabinet; + const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id; + + return fileCabinetId; + }; + + /** + * Returns docuware data + * + * @param {string} code - The fileCabinet code + * @param {object} filter - The filter for docuware + * @param {object} parse - The fields parsed + * @return {object} - The data + */ + Self.get = async(code, filter, parse) => { + if (!process.env.NODE_ENV) return; + + const options = await Self.getOptions(); + const fileCabinetId = await Self.getFileCabinet(code); + const dialogId = await Self.getDialog(code, 'find', fileCabinetId); + + const data = await axios.post( + `${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, + filter, + options.headers + ); + return parser(data.data, parse); + }; + + /** + * Returns docuware data + * + * @param {string} code - The fileCabinet code + * @param {any} id - The id of docuware + * @param {object} parse - The fields parsed + * @return {object} - The data + */ + Self.getById = async(code, id, parse) => { + if (!process.env.NODE_ENV) return; + + const docuwareInfo = await Self.app.models.Docuware.findOne({ + fields: ['findById'], + where: { + code, + action: 'find' + } + }); + const filter = { + condition: [ + { + DBName: docuwareInfo.findById, + Value: [id] + } + ] + }; + + return Self.get(code, filter, parse); + }; + + /** + * Returns docuware data filtered + * + * @param {array} data - The data + * @param {object} parse - The fields parsed + * @return {object} - The data parsed + */ + function parser(data, parse) { + if (!(data && data.Items)) return data; + + const parsed = []; + for (item of data.Items) { + const itemParsed = {}; + item.Fields.map(field => { + if (field.ItemElementName.includes('Date')) field.Item = toDate(field.Item); + if (!parse) return itemParsed[field.FieldLabel] = field.Item; + if (parse[field.FieldLabel]) + itemParsed[parse[field.FieldLabel]] = field.Item; + }); + parsed.push(itemParsed); + } + return parsed; + } + + function toDate(value) { + if (!value) return; + return new Date(Number(value.substring(6, 19))); + } }; diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js index 56d006ee7..a0d72ce01 100644 --- a/back/methods/docuware/download.js +++ b/back/methods/docuware/download.js @@ -3,7 +3,7 @@ const axios = require('axios'); const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethodCtx('download', { + Self.remoteMethod('download', { description: 'Download an docuware PDF', accessType: 'READ', accepts: [ @@ -16,8 +16,12 @@ module.exports = Self => { { arg: 'fileCabinet', type: 'string', - description: 'The file cabinet', - http: {source: 'path'} + description: 'The file cabinet' + }, + { + arg: 'filter', + type: 'object', + description: 'The filter' } ], returns: [ @@ -36,14 +40,15 @@ module.exports = Self => { } ], http: { - path: `/:id/download/:fileCabinet`, + path: `/:id/download`, verb: 'GET' } }); - Self.download = async function(ctx, id, fileCabinet) { + Self.download = async function(id, fileCabinet, filter) { const models = Self.app.models; - const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true); + + const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, filter); if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists'); const fileCabinetId = await Self.getFileCabinet(fileCabinet); diff --git a/back/methods/docuware/specs/checkFile.spec.js b/back/methods/docuware/specs/checkFile.spec.js index dd11951cc..8460bb561 100644 --- a/back/methods/docuware/specs/checkFile.spec.js +++ b/back/methods/docuware/specs/checkFile.spec.js @@ -1,57 +1,15 @@ const models = require('vn-loopback/server/server').models; -const axios = require('axios'); describe('docuware download()', () => { const ticketId = 1; - const userId = 9; - const ctx = { - req: { - - accessToken: {userId: userId}, - headers: {origin: 'http://localhost:5000'}, - } - }; const docuwareModel = models.Docuware; const fileCabinetName = 'deliveryNote'; - beforeAll(() => { - spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); - spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); - }); - it('should return false if there are no documents', async() => { - const response = { - data: { - Items: [] - } - }; - spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response))); + spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve({Items: []})))); - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true); - - expect(result).toEqual(false); - }); - - it('should return false if the document is unsigned', async() => { - const response = { - data: { - Items: [ - { - Id: 1, - Fields: [ - { - FieldName: 'ESTADO', - Item: 'Unsigned' - } - ] - } - ] - } - }; - spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response))); - - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true); + const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true); expect(result).toEqual(false); }); @@ -59,23 +17,21 @@ describe('docuware download()', () => { it('should return the document data', async() => { const docuwareId = 1; const response = { - data: { - Items: [ - { - Id: docuwareId, - Fields: [ - { - FieldName: 'ESTADO', - Item: 'Firmado' - } - ] - } - ] - } + Items: [ + { + Id: docuwareId, + Fields: [ + { + FieldName: 'ESTADO', + Item: 'Firmado' + } + ] + } + ] }; - spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response))); + spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response)))); - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true); + const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true); expect(result.id).toEqual(docuwareId); }); diff --git a/back/methods/docuware/specs/core.spec.js b/back/methods/docuware/specs/core.spec.js new file mode 100644 index 000000000..cdf8a3b62 --- /dev/null +++ b/back/methods/docuware/specs/core.spec.js @@ -0,0 +1,135 @@ +const axios = require('axios'); +const models = require('vn-loopback/server/server').models; + +describe('Docuware core', () => { + beforeAll(() => { + process.env.NODE_ENV = 'testing'; + }); + + afterAll(() => { + delete process.env.NODE_ENV; + }); + + describe('getOptions()', () => { + it('should return url and headers', async() => { + const result = await models.Docuware.getOptions(); + + expect(result.url).toBeDefined(); + expect(result.headers).toBeDefined(); + }); + }); + + describe('getDialog()', () => { + it('should return dialogId', async() => { + const dialogs = { + data: { + Dialog: [ + { + DisplayName: 'find', + Id: 'getDialogTest' + } + ] + } + }; + spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs))); + const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId'); + + expect(result).toEqual('getDialogTest'); + }); + }); + + describe('getFileCabinet()', () => { + it('should return fileCabinetId', async() => { + const code = 'deliveryNote'; + const docuwareInfo = await models.Docuware.findOne({ + where: { + code + } + }); + const dialogs = { + data: { + FileCabinet: [ + { + Name: docuwareInfo.fileCabinetName, + Id: 'getFileCabinetTest' + } + ] + } + }; + spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs))); + const result = await models.Docuware.getFileCabinet(code); + + expect(result).toEqual('getFileCabinetTest'); + }); + }); + + describe('get()', () => { + it('should return data without parse', async() => { + spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); + spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); + const data = { + data: { + id: 1 + } + }; + spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data))); + const result = await models.Docuware.get('deliveryNote'); + + expect(result.id).toEqual(1); + }); + + it('should return data with parse', async() => { + spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); + spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); + const data = { + data: { + Items: [{ + Fields: [ + { + ItemElementName: 'integer', + FieldLabel: 'firstRequiredField', + Item: 1 + }, + { + ItemElementName: 'string', + FieldLabel: 'secondRequiredField', + Item: 'myName' + }, + { + ItemElementName: 'integer', + FieldLabel: 'notRequiredField', + Item: 2 + } + ] + }] + } + }; + const parse = { + 'firstRequiredField': 'id', + 'secondRequiredField': 'name', + }; + spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data))); + const [result] = await models.Docuware.get('deliveryNote', null, parse); + + expect(result.id).toEqual(1); + expect(result.name).toEqual('myName'); + expect(result.notRequiredField).not.toBeDefined(); + }); + }); + + describe('getById()', () => { + it('should return data', async() => { + spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); + spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); + const data = { + data: { + id: 1 + } + }; + spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data))); + const result = await models.Docuware.getById('deliveryNote', 1); + + expect(result.id).toEqual(1); + }); + }); +}); diff --git a/back/methods/docuware/specs/download.spec.js b/back/methods/docuware/specs/download.spec.js index fcc1671a6..bc580a079 100644 --- a/back/methods/docuware/specs/download.spec.js +++ b/back/methods/docuware/specs/download.spec.js @@ -39,7 +39,7 @@ describe('docuware download()', () => { spyOn(docuwareModel, 'checkFile').and.returnValue({}); spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true})); - const result = await models.Docuware.download(ctx, ticketId, fileCabinetName); + const result = await models.Docuware.download(ticketId, fileCabinetName); expect(result[1]).toEqual('application/pdf'); expect(result[2]).toEqual(`filename="${ticketId}.pdf"`); diff --git a/back/methods/viaexpress-config/internationalExpedition.js b/back/methods/viaexpress-config/internationalExpedition.js new file mode 100644 index 000000000..698bb1dac --- /dev/null +++ b/back/methods/viaexpress-config/internationalExpedition.js @@ -0,0 +1,45 @@ +const axios = require('axios'); +const {DOMParser} = require('xmldom'); + +module.exports = Self => { + Self.remoteMethod('internationalExpedition', { + description: 'Create an expedition and return a label', + accessType: 'WRITE', + accepts: [{ + arg: 'expeditionFk', + type: 'number', + required: true + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/internationalExpedition`, + verb: 'POST' + } + }); + + Self.internationalExpedition = async expeditionFk => { + const models = Self.app.models; + + const viaexpressConfig = await models.ViaexpressConfig.findOne({ + fields: ['url'] + }); + + const renderedXml = await models.ViaexpressConfig.renderer(expeditionFk); + const response = await axios.post(`${viaexpressConfig.url}ServicioVxClientes.asmx`, renderedXml, { + headers: { + 'Content-Type': 'application/soap+xml; charset=utf-8' + } + }); + + const xmlString = response.data; + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlString, 'text/xml'); + const referenciaVxElement = xmlDoc.getElementsByTagName('ReferenciaVx')[0]; + const referenciaVx = referenciaVxElement.textContent; + + return referenciaVx; + }; +}; diff --git a/back/methods/viaexpress-config/renderer.js b/back/methods/viaexpress-config/renderer.js new file mode 100644 index 000000000..e9abce5ca --- /dev/null +++ b/back/methods/viaexpress-config/renderer.js @@ -0,0 +1,126 @@ +const fs = require('fs'); +const ejs = require('ejs'); + +module.exports = Self => { + Self.remoteMethod('renderer', { + description: 'Renders the data from an XML', + accessType: 'READ', + accepts: [{ + arg: 'expeditionFk', + type: 'number', + required: true + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/renderer`, + verb: 'GET' + } + }); + + Self.renderer = async expeditionFk => { + const models = Self.app.models; + + const viaexpressConfig = await models.ViaexpressConfig.findOne({ + fields: ['client', 'user', 'password', 'defaultWeight', 'deliveryType'] + }); + + const expedition = await models.Expedition.findOne({ + fields: ['id', 'ticketFk'], + where: {id: expeditionFk}, + include: [ + { + relation: 'ticket', + scope: { + fields: ['shipped', 'addressFk', 'clientFk', 'companyFk'], + include: [ + { + relation: 'client', + scope: { + fields: ['mobile', 'phone', 'email'] + } + }, + { + relation: 'address', + scope: { + fields: [ + 'nickname', + 'street', + 'postalCode', + 'city', + 'mobile', + 'phone', + 'provinceFk' + ], + include: { + relation: 'province', + scope: { + fields: ['name', 'countryFk'], + include: { + relation: 'country', + scope: { + fields: ['code'], + } + } + + } + } + } + }, + { + relation: 'company', + scope: { + fields: ['clientFk'], + include: { + relation: 'client', + scope: { + fields: ['socialName', 'mobile', 'phone', 'email', 'defaultAddressFk'], + include: { + relation: 'defaultAddress', + scope: { + fields: [ + 'street', + 'postalCode', + 'city', + 'mobile', + 'phone', + 'provinceFk' + ], + include: { + relation: 'province', + scope: { + fields: ['name'] + } + } + } + } + } + } + } + } + ] + } + + } + ] + }); + + const ticket = expedition.ticket(); + const sender = ticket.company().client(); + const shipped = ticket.shipped.toISOString(); + const data = { + viaexpressConfig, + sender, + senderAddress: sender.defaultAddress(), + client: ticket.client(), + address: ticket.address(), + shipped + }; + + const template = fs.readFileSync(__dirname + '/template.ejs', 'utf-8'); + const renderedXml = ejs.render(template, data); + return renderedXml; + }; +}; diff --git a/back/methods/viaexpress-config/template.ejs b/back/methods/viaexpress-config/template.ejs new file mode 100644 index 000000000..0b6eb468c --- /dev/null +++ b/back/methods/viaexpress-config/template.ejs @@ -0,0 +1,52 @@ + + + + + + <%= viaexpressConfig.defaultWeight %> + 1 + 0 + <%= shipped %> + 0 + <%= viaexpressConfig.deliveryType %> + 0 + 0 + 0 + 0 + 0 + + + 0 + + + + <%= sender.socialName %> + <%= senderAddress.street %> + <%= senderAddress.postalCode %> + <%= senderAddress.city %> + <%= senderAddress.province().name %> + + <%= senderAddress.mobile || senderAddress.phone || sender.mobile || sender.phone %> + <%= sender.email %> + + + <%= address.nickname %> + <%= address.street %> + <%= address.postalCode %> + <%= address.city %> + + <%= address.province().name %> + + <%= address.mobile || address.phone || client.mobile || client.phone %> + <%= client.email %> + <%= address.province().country().code %> + + + <%= viaexpressConfig.client %> + <%= viaexpressConfig.user %> + <%= viaexpressConfig.password %> + + + + + diff --git a/back/methods/vn-user/addAlias.js b/back/methods/vn-user/addAlias.js deleted file mode 100644 index 9fe43e713..000000000 --- a/back/methods/vn-user/addAlias.js +++ /dev/null @@ -1,68 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethod('addAlias', { - description: 'Add an alias if the user has the grant', - accessType: 'WRITE', - accepts: [ - { - arg: 'ctx', - type: 'Object', - http: {source: 'context'} - }, - { - arg: 'id', - type: 'number', - required: true, - description: 'The user id', - http: {source: 'path'} - }, - { - arg: 'mailAlias', - type: 'number', - description: 'The new alias for user', - required: true - } - ], - http: { - path: `/:id/addAlias`, - verb: 'POST' - } - }); - - Self.addAlias = async function(ctx, id, mailAlias, options) { - const models = Self.app.models; - const userId = ctx.req.accessToken.userId; - - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions); - - if (!user.hasGrant) - throw new UserError(`You don't have grant privilege`); - - const account = await models.Account.findById(userId, { - fields: ['id'], - include: { - relation: 'aliases', - scope: { - fields: ['mailAlias'] - } - } - }, myOptions); - - const aliases = account.aliases().map(alias => alias.mailAlias); - - const hasAlias = aliases.includes(mailAlias); - if (!hasAlias) - throw new UserError(`You cannot assign an alias that you are not assigned to`); - - return models.MailAliasAccount.create({ - mailAlias: mailAlias, - account: id - }, myOptions); - }; -}; diff --git a/back/methods/vn-user/removeAlias.js b/back/methods/vn-user/removeAlias.js deleted file mode 100644 index 0424c3e96..000000000 --- a/back/methods/vn-user/removeAlias.js +++ /dev/null @@ -1,55 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethod('removeAlias', { - description: 'Remove alias if the user has the grant', - accessType: 'WRITE', - accepts: [ - { - arg: 'ctx', - type: 'Object', - http: {source: 'context'} - }, - { - arg: 'id', - type: 'number', - required: true, - description: 'The user id', - http: {source: 'path'} - }, - { - arg: 'mailAlias', - type: 'number', - description: 'The alias to delete', - required: true - } - ], - http: { - path: `/:id/removeAlias`, - verb: 'POST' - } - }); - - Self.removeAlias = async function(ctx, id, mailAlias, options) { - const models = Self.app.models; - const userId = ctx.req.accessToken.userId; - - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - const canRemoveAlias = await models.ACL.checkAccessAcl(ctx, 'VnUser', 'canRemoveAlias', 'WRITE'); - - if (userId != id && !canRemoveAlias) throw new UserError(`You don't have grant privilege`); - - const mailAliasAccount = await models.MailAliasAccount.findOne({ - where: { - mailAlias: mailAlias, - account: id - } - }, myOptions); - - await mailAliasAccount.destroy(myOptions); - }; -}; diff --git a/back/model-config.json b/back/model-config.json index 0e37bf527..b88956dee 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -150,6 +150,9 @@ }, "PrintConfig": { "dataSource": "vn" + }, + "ViaexpressConfig": { + "dataSource": "vn" } } diff --git a/back/models/company.json b/back/models/company.json index f16c5762f..d7e88cd11 100644 --- a/back/models/company.json +++ b/back/models/company.json @@ -18,11 +18,21 @@ }, "expired": { "type": "date" + }, + "supplierAccountFk": { + "type": "number" } }, "scope": { "where" :{ "expired": null } + }, + "relations": { + "client": { + "type": "belongsTo", + "model": "Client", + "foreignKey": "clientFk" + } } } diff --git a/back/models/country.json b/back/models/country.json index 8fa25b88e..fd540d819 100644 --- a/back/models/country.json +++ b/back/models/country.json @@ -22,6 +22,9 @@ }, "isUeeMember": { "type": "boolean" + }, + "isSocialNameUnique": { + "type": "boolean" } }, "relations": { @@ -39,4 +42,4 @@ "permission": "ALLOW" } ] -} \ No newline at end of file +} diff --git a/back/models/docuware.json b/back/models/docuware.json index dec20eede..b1a6a8bce 100644 --- a/back/models/docuware.json +++ b/back/models/docuware.json @@ -28,5 +28,12 @@ "findById": { "type": "string" } + }, + "relations": { + "dmsType": { + "type": "belongsTo", + "model": "DmsType", + "foreignKey": "dmsTypeFk" + } } } diff --git a/back/models/viaexpress-config.js b/back/models/viaexpress-config.js new file mode 100644 index 000000000..d0335b28b --- /dev/null +++ b/back/models/viaexpress-config.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/viaexpress-config/internationalExpedition')(Self); + require('../methods/viaexpress-config/renderer')(Self); +}; diff --git a/back/models/viaexpress-config.json b/back/models/viaexpress-config.json new file mode 100644 index 000000000..8df24201b --- /dev/null +++ b/back/models/viaexpress-config.json @@ -0,0 +1,34 @@ +{ + "name": "ViaexpressConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "viaexpressConfig" + } + }, + "properties": { + "id": { + "type": "number", + "required": true + }, + "url": { + "type": "string", + "required": true + }, + "client": { + "type": "string" + }, + "user": { + "type": "string" + }, + "password": { + "type": "string" + }, + "defaultWeight": { + "type": "number" + }, + "deliveryType": { + "type": "string" + } + } +} diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 11d4bf250..a7ce12073 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -12,8 +12,6 @@ module.exports = function(Self) { require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/renew-token')(Self); - require('../methods/vn-user/addAlias')(Self); - require('../methods/vn-user/removeAlias')(Self); Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); diff --git a/db/changes/232401/00-ACLgetVehiclesSorted.sql b/db/changes/232401/00-ACLgetVehiclesSorted.sql deleted file mode 100644 index 6625f0d5c..000000000 --- a/db/changes/232401/00-ACLgetVehiclesSorted.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) - VALUES - ('Vehicle','sorted','WRITE','ALLOW','employee'); \ No newline at end of file diff --git a/db/changes/232601/00-aclAddAlias.sql b/db/changes/232601/00-aclAddAlias.sql deleted file mode 100644 index cc96f5ad8..000000000 --- a/db/changes/232601/00-aclAddAlias.sql +++ /dev/null @@ -1,11 +0,0 @@ -INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) - VALUES - ('VnUser', 'addAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee'); - -INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) - VALUES - ('VnUser', 'removeAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee'); - -INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) - VALUES - ('VnUser', 'canRemoveAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement'); diff --git a/db/changes/232601/00-acl_viaexpressConfig.sql b/db/changes/232601/00-acl_viaexpressConfig.sql new file mode 100644 index 000000000..d4c186dd4 --- /dev/null +++ b/db/changes/232601/00-acl_viaexpressConfig.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('ViaexpressConfig', 'internationalExpedition', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('ViaexpressConfig', 'renderer', 'READ', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/232601/00-viaexpress.sql b/db/changes/232601/00-viaexpress.sql new file mode 100644 index 000000000..42cf3b647 --- /dev/null +++ b/db/changes/232601/00-viaexpress.sql @@ -0,0 +1,10 @@ +CREATE TABLE `vn`.`viaexpressConfig` ( + id int auto_increment NOT NULL, + url varchar(100) NOT NULL, + client varchar(100) NOT NULL, + user varchar(100) NOT NULL, + password varchar(100) NOT NULL, + defaultWeight decimal(10,2) NOT NULL, + deliveryType varchar(5) NOT NULL, + CONSTRAINT viaexpressConfig_PK PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; diff --git a/db/changes/232602/01-aclAddAlias.sql b/db/changes/232602/01-aclAddAlias.sql new file mode 100644 index 000000000..d4df3cd44 --- /dev/null +++ b/db/changes/232602/01-aclAddAlias.sql @@ -0,0 +1,8 @@ +DELETE FROM `salix`.`ACL` WHERE model = 'MailAliasAccount'; + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('MailAliasAccount', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('MailAliasAccount', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('MailAliasAccount', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('MailAliasAccount', 'canEditAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement'); diff --git a/db/changes/232802/01-aclWorkerDisable.sql b/db/changes/232802/01-aclWorkerDisable.sql new file mode 100644 index 000000000..149dd6f15 --- /dev/null +++ b/db/changes/232802/01-aclWorkerDisable.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('WorkerDisableExcluded', '*', 'READ', 'ALLOW', 'ROLE', 'itManagement'), + ('WorkerDisableExcluded', '*', 'WRITE', 'ALLOW', 'ROLE', 'itManagement'); diff --git a/db/changes/233001/00-clientSms.sql b/db/changes/233001/00-clientSms.sql new file mode 100644 index 000000000..e1e34f6b2 --- /dev/null +++ b/db/changes/233001/00-clientSms.sql @@ -0,0 +1,15 @@ +CREATE TABLE `vn`.`clientSms` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `clientFk` int(11) NOT NULL, + `smsFk` mediumint(8) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `clientSms_FK` (`clientFk`), + KEY `clientSms_FK_1` (`smsFk`), + CONSTRAINT `clientSms_FK` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON UPDATE CASCADE, + CONSTRAINT `clientSms_FK_1` FOREIGN KEY (`smsFk`) REFERENCES `sms` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('ClientSms', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('ClientSms', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/233001/00-company.sql b/db/changes/233001/00-company.sql new file mode 100644 index 000000000..a3b61b9cc --- /dev/null +++ b/db/changes/233001/00-company.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`company` MODIFY COLUMN sage200Company int(2) DEFAULT 10 NOT NULL; diff --git a/db/changes/233001/00-noUniqueSocialName.sql b/db/changes/233001/00-noUniqueSocialName.sql new file mode 100644 index 000000000..0dc4c832f --- /dev/null +++ b/db/changes/233001/00-noUniqueSocialName.sql @@ -0,0 +1,2 @@ +ALTER TABLE `vn`.`country` +ADD COLUMN `isSocialNameUnique` tinyint(1) NOT NULL DEFAULT 1; diff --git a/db/changes/232801/00-roadmap.sql b/db/changes/233001/00-roadmap.sql similarity index 69% rename from db/changes/232801/00-roadmap.sql rename to db/changes/233001/00-roadmap.sql index a2835160f..9b5db54eb 100644 --- a/db/changes/232801/00-roadmap.sql +++ b/db/changes/233001/00-roadmap.sql @@ -6,5 +6,3 @@ ALTER TABLE `vn`.`roadmap` CHANGE name name varchar(45) CHARACTER SET utf8mb3 CO ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL; ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales'; -ALTER TABLE `vn`.`expeditionTruck` DROP FOREIGN KEY expeditionTruck_FK_2; -ALTER TABLE `vn`.`expeditionTruck` ADD CONSTRAINT expeditionTruck_FK_2 FOREIGN KEY (roadmapFk) REFERENCES vn.roadmap(id) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/db/changes/232801/00-roadmapACL.sql b/db/changes/233001/00-roadmapACL.sql similarity index 100% rename from db/changes/232801/00-roadmapACL.sql rename to db/changes/233001/00-roadmapACL.sql diff --git a/db/changes/233201/.gitkeep b/db/changes/233201/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/db/changes/233201/00-transferClient.sql b/db/changes/233201/00-transferClient.sql new file mode 100644 index 000000000..8a7ce0543 --- /dev/null +++ b/db/changes/233201/00-transferClient.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`) + VALUES ('Ticket','transferClient','WRITE','ALLOW','ROLE','administrative'); \ No newline at end of file diff --git a/db/changes/233201/00-updatePrice.sql b/db/changes/233201/00-updatePrice.sql new file mode 100644 index 000000000..959943d6f --- /dev/null +++ b/db/changes/233201/00-updatePrice.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`) + VALUES ('Ticket','canEditWeekly','WRITE','ALLOW','ROLE','buyer'); diff --git a/db/changes/233201/00-workerDocuware.sql b/db/changes/233201/00-workerDocuware.sql new file mode 100644 index 000000000..2f2c4a1cd --- /dev/null +++ b/db/changes/233201/00-workerDocuware.sql @@ -0,0 +1,10 @@ +ALTER TABLE `vn`.`docuware` ADD dmsTypeFk INT(11) DEFAULT NULL NULL; +ALTER TABLE `vn`.`docuware` ADD CONSTRAINT docuware_FK FOREIGN KEY (dmsTypeFk) REFERENCES `vn`.`dmsType`(id) ON DELETE RESTRICT ON UPDATE CASCADE; +INSERT INTO `vn`.`docuware` (code, fileCabinetName, `action`, dialogName, findById, dmsTypeFk) + VALUES + ('hr', 'RRHH', 'find', 'Búsqueda', 'N__DOCUMENTO', NULL); -- set dmsTypeFk 3 when deploy in production + +INSERT INTO `salix`.`url` (appName, environment, url) + VALUES + ('docuware', 'production', 'https://verdnatura.docuware.cloud/DocuWare/Platform/'); + diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index eaa00a3de..338a73225 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -579,13 +579,13 @@ INSERT INTO `vn`.`supplierAccount`(`id`, `supplierFk`, `iban`, `bankEntityFk`) VALUES (241, 442, 'ES111122333344111122221111', 128); -INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary`) +INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary` , `clientFk`) VALUES - (69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL), - (442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport'), - (567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport'), - (791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL), - (1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport'); + (69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL , NULL), + (442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport' , 1101), + (567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport' , NULL), + (791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL , NULL), + (1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport' , NULL); INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`) VALUES diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 977c8a837..93288efa7 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -875,7 +875,7 @@ export default { }, routeTickets: { - firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-input-number[ng-model="ticket.priority"]', + firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-td-editable', firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check', buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]', firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]', diff --git a/e2e/paths/08-route/04_tickets.spec.js b/e2e/paths/08-route/04_tickets.spec.js index ccd5562c2..c890162a1 100644 --- a/e2e/paths/08-route/04_tickets.spec.js +++ b/e2e/paths/08-route/04_tickets.spec.js @@ -18,8 +18,7 @@ describe('Route tickets path', () => { }); it('should modify the first ticket priority', async() => { - await page.clearInput(selectors.routeTickets.firstTicketPriority); - await page.type(selectors.routeTickets.firstTicketPriority, '9'); + await page.writeOnEditableTD(selectors.routeTickets.firstTicketPriority, '9'); await page.keyboard.press('Enter'); const message = await page.waitForSnackbar(); diff --git a/front/core/services/app.js b/front/core/services/app.js index fb0a08777..6c80fafcc 100644 --- a/front/core/services/app.js +++ b/front/core/services/app.js @@ -66,7 +66,11 @@ export default class App { return this.logger.$http.get('Urls/findOne', {filter}) .then(res => { - return res.data.url + route; + if (res && res.data) + return res.data.url + route; + }) + .catch(() => { + this.showError('Direction not found'); }); } } diff --git a/front/core/services/locale/es.yml b/front/core/services/locale/es.yml index cf8801b52..e9811e38f 100644 --- a/front/core/services/locale/es.yml +++ b/front/core/services/locale/es.yml @@ -3,4 +3,5 @@ Could not contact the server: No se ha podido contactar con el servidor, asegura Please enter your username: Por favor introduce tu nombre de usuario It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión -Access denied: Acción no permitida \ No newline at end of file +Access denied: Acción no permitida +Direction not found: Dirección no encontrada diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 030afbe9e..135bb2be9 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -178,5 +178,9 @@ "The renew period has not been exceeded": "The renew period has not been exceeded", "You can not use the same password": "You can not use the same password", "Valid priorities": "Valid priorities: %d", - "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}" + "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}", + "You don't have enough privileges.": "You don't have enough privileges.", + "This ticket is locked.": "This ticket is locked.", + "This ticket is not editable.": "This ticket is not editable.", + "The ticket doesn't exist.": "The ticket doesn't exist." } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 784ff5e6e..ac62d62e1 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -305,5 +305,11 @@ "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", "Valid priorities": "Prioridades válidas: %d", "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", - "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado" -} + "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", + "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", + "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", + "You don't have enough privileges.": "No tienes suficientes permisos.", + "This ticket is locked.": "Este ticket está bloqueado.", + "This ticket is not editable.": "Este ticket no es editable.", + "The ticket doesn't exist.": "No existe el ticket." +} \ No newline at end of file diff --git a/modules/account/back/models/mail-alias-account.js b/modules/account/back/models/mail-alias-account.js new file mode 100644 index 000000000..6f5213f24 --- /dev/null +++ b/modules/account/back/models/mail-alias-account.js @@ -0,0 +1,55 @@ + +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.observe('before save', async ctx => { + const changes = ctx.currentInstance || ctx.instance; + + await Self.hasGrant(ctx, changes.mailAlias); + }); + + Self.observe('before delete', async ctx => { + const mailAliasAccount = await Self.findById(ctx.where.id); + + await Self.hasGrant(ctx, mailAliasAccount.mailAlias); + }); + + /** + * Checks if current user has + * grant to add/remove alias + * + * @param {Object} ctx - Request context + * @param {Interger} mailAlias - mailAlias id + * @return {Boolean} True for user with grant + */ + Self.hasGrant = async function(ctx, mailAlias) { + const models = Self.app.models; + const accessToken = {req: {accessToken: ctx.options.accessToken}}; + const userId = accessToken.req.accessToken.userId; + + const canEditAlias = await models.ACL.checkAccessAcl(accessToken, 'MailAliasAccount', 'canEditAlias', 'WRITE'); + if (canEditAlias) return true; + + const user = await models.VnUser.findById(userId, {fields: ['hasGrant']}); + if (!user.hasGrant) + throw new UserError(`You don't have grant privilege`); + + const account = await models.Account.findById(userId, { + fields: ['id'], + include: { + relation: 'aliases', + scope: { + fields: ['mailAlias'] + } + } + }); + + const aliases = account.aliases().map(alias => alias.mailAlias); + + const hasAlias = aliases.includes(mailAlias); + if (!hasAlias) + throw new UserError(`You cannot assign/remove an alias that you are not assigned to`); + + return true; + }; +}; diff --git a/modules/account/front/aliases/index.js b/modules/account/front/aliases/index.js index e0c738ee4..0fc806a71 100644 --- a/modules/account/front/aliases/index.js +++ b/modules/account/front/aliases/index.js @@ -21,11 +21,12 @@ export default class Controller extends Section { } onAddClick() { + this.addData = {account: this.$params.id}; this.$.dialog.show(); } onAddSave() { - return this.$http.post(`VnUsers/${this.$params.id}/addAlias`, this.addData) + return this.$http.post(`MailAliasAccounts`, this.addData) .then(() => this.refresh()) .then(() => this.vnApp.showSuccess( this.$t('Subscribed to alias!')) @@ -33,12 +34,11 @@ export default class Controller extends Section { } onRemove(row) { - const params = { - mailAlias: row.mailAlias - }; - return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params) - .then(() => this.refresh()) - .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); + return this.$http.delete(`MailAliasAccounts/${row.id}`) + .then(() => { + this.$.data.splice(this.$.data.indexOf(row), 1); + this.vnApp.showSuccess(this.$t('Unsubscribed from alias!')); + }); } } diff --git a/modules/account/front/aliases/index.spec.js b/modules/account/front/aliases/index.spec.js index 61f71949c..466f1e1e9 100644 --- a/modules/account/front/aliases/index.spec.js +++ b/modules/account/front/aliases/index.spec.js @@ -25,9 +25,8 @@ describe('component vnUserAliases', () => { describe('onAddSave()', () => { it('should add the new row', () => { controller.addData = {account: 1}; - controller.$params = {id: 1}; - $httpBackend.expectPOST('VnUsers/1/addAlias').respond(); + $httpBackend.expectPOST('MailAliasAccounts').respond(); $httpBackend.expectGET('MailAliasAccounts').respond('foo'); controller.onAddSave(); $httpBackend.flush(); @@ -42,14 +41,12 @@ describe('component vnUserAliases', () => { {id: 1, alias: 'foo'}, {id: 2, alias: 'bar'} ]; - controller.$params = {id: 1}; - $httpBackend.expectPOST('VnUsers/1/removeAlias').respond(); - $httpBackend.expectGET('MailAliasAccounts').respond(controller.$.data[1]); + $httpBackend.expectDELETE('MailAliasAccounts/1').respond(); controller.onRemove(controller.$.data[0]); $httpBackend.flush(); - expect(controller.$.data).toEqual({id: 2, alias: 'bar'}); + expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]); expect(controller.vnApp.showSuccess).toHaveBeenCalled(); }); }); diff --git a/modules/client/back/methods/client/canBeInvoiced.js b/modules/client/back/methods/client/canBeInvoiced.js index 567d491f1..843e9549f 100644 --- a/modules/client/back/methods/client/canBeInvoiced.js +++ b/modules/client/back/methods/client/canBeInvoiced.js @@ -1,3 +1,5 @@ +const UserError = require('vn-loopback/util/user-error'); + module.exports = function(Self) { Self.remoteMethodCtx('canBeInvoiced', { description: 'Change property isEqualizated in all client addresses', @@ -9,6 +11,12 @@ module.exports = function(Self) { required: true, description: 'Client id', http: {source: 'path'} + }, + { + arg: 'companyFk', + description: 'The company id', + type: 'number', + required: true } ], returns: { @@ -22,18 +30,29 @@ module.exports = function(Self) { } }); - Self.canBeInvoiced = async(id, options) => { + Self.canBeInvoiced = async(id, companyFk, options) => { const models = Self.app.models; - const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); const client = await models.Client.findById(id, { - fields: ['id', 'isTaxDataChecked', 'hasToInvoice'] + fields: ['id', 'isTaxDataChecked', 'hasToInvoice', 'payMethodFk'], + include: + { + relation: 'payMethod', + scope: { + fields: ['code'] + } + } }, myOptions); + const company = await models.Company.findById(companyFk, {fields: ['supplierAccountFk']}, myOptions); + + if (client.payMethod().code === 'wireTransfer' && !company.supplierAccountFk) + throw new UserError('The company has not informed the supplier account for bank transfers'); + if (client.isTaxDataChecked && client.hasToInvoice) return true; diff --git a/modules/client/back/methods/client/sendSms.js b/modules/client/back/methods/client/sendSms.js index 83b7c8d6e..270d7e5b5 100644 --- a/modules/client/back/methods/client/sendSms.js +++ b/modules/client/back/methods/client/sendSms.js @@ -7,7 +7,7 @@ module.exports = Self => { arg: 'id', type: 'number', required: true, - description: 'The ticket id', + description: 'The client id', http: {source: 'path'} }, { @@ -33,6 +33,12 @@ module.exports = Self => { Self.sendSms = async(ctx, id, destination, message) => { const models = Self.app.models; const sms = await models.Sms.send(ctx, destination, message); + + await models.ClientSms.create({ + clientFk: id, + smsFk: sms.id + }); + return sms; }; }; diff --git a/modules/client/back/methods/client/specs/canBeInvoiced.spec.js b/modules/client/back/methods/client/specs/canBeInvoiced.spec.js index 2f11d8013..397be3c92 100644 --- a/modules/client/back/methods/client/specs/canBeInvoiced.spec.js +++ b/modules/client/back/methods/client/specs/canBeInvoiced.spec.js @@ -4,6 +4,7 @@ const LoopBackContext = require('loopback-context'); describe('client canBeInvoiced()', () => { const userId = 19; const clientId = 1101; + const companyId = 442; const activeCtx = { accessToken: {userId: userId} }; @@ -23,7 +24,7 @@ describe('client canBeInvoiced()', () => { const client = await models.Client.findById(clientId, null, options); await client.updateAttribute('isTaxDataChecked', false, options); - const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); + const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options); expect(canBeInvoiced).toEqual(false); @@ -43,7 +44,7 @@ describe('client canBeInvoiced()', () => { const client = await models.Client.findById(clientId, null, options); await client.updateAttribute('hasToInvoice', false, options); - const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); + const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options); expect(canBeInvoiced).toEqual(false); @@ -60,7 +61,7 @@ describe('client canBeInvoiced()', () => { try { const options = {transaction: tx}; - const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); + const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options); expect(canBeInvoiced).toEqual(true); diff --git a/modules/client/back/methods/defaulter/filter.js b/modules/client/back/methods/defaulter/filter.js index 56afb64db..220cb957b 100644 --- a/modules/client/back/methods/defaulter/filter.js +++ b/modules/client/back/methods/defaulter/filter.js @@ -70,11 +70,12 @@ module.exports = Self => { c.creditInsurance, d.defaulterSinced, cn.country, + c.countryFk, pm.name payMethod FROM vn.defaulter d JOIN vn.client c ON c.id = d.clientFk JOIN vn.country cn ON cn.id = c.countryFk - JOIN vn.payMethod pm ON pm.id = c.payMethodFk + JOIN vn.payMethod pm ON pm.id = c.payMethodFk 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/model-config.json b/modules/client/back/model-config.json index 1e06ea1c0..bc48ec360 100644 --- a/modules/client/back/model-config.json +++ b/modules/client/back/model-config.json @@ -95,6 +95,9 @@ "ClientSample": { "dataSource": "vn" }, + "ClientSms": { + "dataSource": "vn" + }, "Sms": { "dataSource": "vn" }, diff --git a/modules/client/back/models/client-sms.json b/modules/client/back/models/client-sms.json new file mode 100644 index 000000000..b2244ebbb --- /dev/null +++ b/modules/client/back/models/client-sms.json @@ -0,0 +1,26 @@ +{ + "name": "ClientSms", + "base": "VnModel", + "options": { + "mysql": { + "table": "clientSms" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "clientFk": { + "type": "number" + } + }, + "relations": { + "sms": { + "type": "belongsTo", + "model": "Sms", + "foreignKey": "smsFk" + } + } +} diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index ca279ef71..8369fa906 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -41,7 +41,18 @@ module.exports = Self => { }); async function socialNameIsUnique(err, done) { + if (!this.countryFk) + return done(); + const filter = { + include: { + relation: 'country', + scope: { + fields: { + isSocialNameUnique: true, + }, + }, + }, where: { and: [ {socialName: this.socialName}, @@ -50,9 +61,13 @@ module.exports = Self => { ] } }; - const client = await Self.app.models.Client.findOne(filter); - if (client) + + const client = await Self.app.models.Country.findById(this.countryFk, {fields: ['isSocialNameUnique']}); + const existingClient = await Self.findOne(filter); + + if (existingClient && (existingClient.country().isSocialNameUnique || client.isSocialNameUnique)) err(); + done(); } diff --git a/modules/client/back/models/client.json b/modules/client/back/models/client.json index 5f56a1ed2..66a67ec2e 100644 --- a/modules/client/back/models/client.json +++ b/modules/client/back/models/client.json @@ -181,6 +181,11 @@ "model": "Country", "foreignKey": "countryFk" }, + "isSocialNameUnique": { + "type": "belongsTo", + "model": "Country", + "foreignKey": "countryFk" + }, "contactChannel": { "type": "belongsTo", "model": "ContactChannel", diff --git a/modules/client/back/models/defaulter.json b/modules/client/back/models/defaulter.json index 03d68ea71..ef22c2429 100644 --- a/modules/client/back/models/defaulter.json +++ b/modules/client/back/models/defaulter.json @@ -33,7 +33,7 @@ "country": { "type": "belongsTo", "model": "Country", - "foreignKey": "country" + "foreignKey": "countryFk" }, "payMethod": { "type": "belongsTo", @@ -41,4 +41,4 @@ "foreignKey": "payMethod" } } -} \ No newline at end of file +} diff --git a/modules/client/front/defaulter/index.html b/modules/client/front/defaulter/index.html index 3ea88088b..33bb751f1 100644 --- a/modules/client/front/defaulter/index.html +++ b/modules/client/front/defaulter/index.html @@ -60,7 +60,7 @@ Comercial - + Country {{::defaulter.payMethod}} - + {{::defaulter.amount | currency: 'EUR': 2}} + + + + + + + Sender + Destination + Message + Status + Created + + + + + + + {{::clientSms.sms.sender.name}} + + + {{::clientSms.sms.destination}} + {{::clientSms.sms.message}} + {{::clientSms.sms.status}} + {{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}} + + + + + + + diff --git a/modules/client/front/sms/index.js b/modules/client/front/sms/index.js new file mode 100644 index 000000000..6ad64282e --- /dev/null +++ b/modules/client/front/sms/index.js @@ -0,0 +1,39 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + + this.filter = { + fields: ['id', 'smsFk'], + include: { + relation: 'sms', + scope: { + fields: [ + 'senderFk', + 'sender', + 'destination', + 'message', + 'statusCode', + 'status', + 'created'], + include: { + relation: 'sender', + scope: { + fields: ['name'] + } + } + } + } + }; + } +} + +ngModule.vnComponent('vnClientSms', { + template: require('./index.html'), + controller: Controller, + bindings: { + client: '<' + } +}); diff --git a/modules/client/front/sms/locale/es.yml b/modules/client/front/sms/locale/es.yml new file mode 100644 index 000000000..6d1e9e147 --- /dev/null +++ b/modules/client/front/sms/locale/es.yml @@ -0,0 +1,2 @@ +Sender: Remitente +Number sender: Número remitente diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js index 51905ccb8..82e0bf078 100644 --- a/modules/invoiceIn/back/models/invoice-in.js +++ b/modules/invoiceIn/back/models/invoice-in.js @@ -1,3 +1,5 @@ +const UserError = require('vn-loopback/util/user-error'); + module.exports = Self => { require('../methods/invoice-in/filter')(Self); require('../methods/invoice-in/summary')(Self); @@ -7,4 +9,9 @@ module.exports = Self => { require('../methods/invoice-in/invoiceInPdf')(Self); require('../methods/invoice-in/invoiceInEmail')(Self); require('../methods/invoice-in/getSerial')(Self); + Self.rewriteDbError(function(err) { + if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn')) + return new UserError(`This invoice has a linked vehicle.`); + return err; + }); }; diff --git a/modules/item/back/methods/item/activeBuyers.js b/modules/item/back/methods/item/activeBuyers.js deleted file mode 100644 index e16ff877b..000000000 --- a/modules/item/back/methods/item/activeBuyers.js +++ /dev/null @@ -1,44 +0,0 @@ -const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; -const mergeFilters = require('vn-loopback/util/filter').mergeFilters; - -module.exports = Self => { - Self.remoteMethod('activeBuyers', { - description: 'Returns a list of buyers for the given item type', - accepts: [{ - arg: 'filter', - type: 'object', - description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string` - }], - returns: { - type: ['object'], - root: true - }, - http: { - path: `/activeBuyers`, - verb: 'GET' - } - }); - - Self.activeBuyers = async(filter, options) => { - const conn = Self.dataSource.connector; - const where = {isActive: true}; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - filter = mergeFilters(filter, {where}); - - let stmt = new ParameterizedSQL( - `SELECT DISTINCT w.id workerFk, w.firstName, w.lastName, u.name, u.nickname - FROM worker w - JOIN itemType it ON it.workerFk = w.id - JOIN account.user u ON u.id = w.id - JOIN item i ON i.typeFk = it.id`, - null, myOptions); - - stmt.merge(conn.makeSuffix(filter)); - - return conn.executeStmt(stmt); - }; -}; diff --git a/modules/item/back/methods/item/specs/activeBuyers.spec.js b/modules/item/back/methods/item/specs/activeBuyers.spec.js deleted file mode 100644 index 5bf36756f..000000000 --- a/modules/item/back/methods/item/specs/activeBuyers.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -const models = require('vn-loopback/server/server').models; - -describe('Worker activeBuyers', () => { - it('should return the buyers in itemType as result', async() => { - const tx = await models.Item.beginTransaction({}); - - try { - const options = {transaction: tx}; - const filter = {}; - const result = await models.Item.activeBuyers(filter, options); - const firstWorker = result[0]; - const secondWorker = result[1]; - - expect(result.length).toEqual(2); - expect(firstWorker.nickname).toEqual('logisticBossNick'); - expect(secondWorker.nickname).toEqual('buyerNick'); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/item/back/models/item-shelving.json b/modules/item/back/models/item-shelving.json index be379adca..8628bfeee 100644 --- a/modules/item/back/models/item-shelving.json +++ b/modules/item/back/models/item-shelving.json @@ -25,6 +25,9 @@ "type": "boolean" }, "visible": { + "type": "number" + }, + "userFk": { "type": "number" } }, diff --git a/modules/item/back/models/item.js b/modules/item/back/models/item.js index b8baa97ea..61c5c2588 100644 --- a/modules/item/back/models/item.js +++ b/modules/item/back/models/item.js @@ -14,7 +14,6 @@ module.exports = Self => { require('../methods/item/getWasteByWorker')(Self); require('../methods/item/getWasteByItem')(Self); require('../methods/item/createIntrastat')(Self); - require('../methods/item/activeBuyers')(Self); require('../methods/item/buyerWasteEmail')(Self); require('../methods/item/labelPdf')(Self); diff --git a/modules/item/front/request-search-panel/index.html b/modules/item/front/request-search-panel/index.html index a76684776..9d35fbca4 100644 --- a/modules/item/front/request-search-panel/index.html +++ b/modules/item/front/request-search-panel/index.html @@ -1,7 +1,7 @@
@@ -26,7 +26,7 @@ search-function="{firstName: $search}" value-field="id" where="{role: {inq: ['logistic', 'buyer']}}" - label="Atender"> + label="Buyer"> {{nickname}} @@ -57,7 +57,7 @@ {{firstName}} {{lastName}} - +
- - - - - - - - + + + {{ticket.priority}} + + + + + + {{::ticket.street}} { }; const country = await Self.app.models.Country.findOne(filter); const code = country ? country.code.toLowerCase() : null; - const countryCode = this.nif.toLowerCase().substring(0, 2); + const countryCode = this.nif?.toLowerCase().substring(0, 2); - if (!this.nif || !validateTin(this.nif, code) || (this.isVies && countryCode == code)) + if (!validateTin(this.nif, code) || (this.isVies && countryCode == code)) err(); done(); } @@ -122,7 +122,7 @@ module.exports = Self => { }); async function hasSupplierSameName(err, done) { - if (!this.name || !this.countryFk) done(); + if (!this.name || !this.countryFk) return done(); const supplier = await Self.app.models.Supplier.findOne( { where: { diff --git a/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js b/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js index 0655c7bba..9ea859f7c 100644 --- a/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js +++ b/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js @@ -1,7 +1,9 @@ const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; module.exports = Self => { - Self.remoteMethodCtx('getItemTypeWorker', { + Self.remoteMethod('getItemTypeWorker', { description: 'Returns the workers that appear in itemType', accessType: 'READ', accepts: [{ @@ -20,38 +22,37 @@ module.exports = Self => { } }); - Self.getItemTypeWorker = async(ctx, filter, options) => { - const myOptions = {}; + Self.getItemTypeWorker = async filter => { const conn = Self.dataSource.connector; - let tx; - - if (typeof options == 'object') - Object.assign(myOptions, options); const query = `SELECT DISTINCT u.id, u.nickname FROM itemType it JOIN worker w ON w.id = it.workerFk JOIN account.user u ON u.id = w.id`; + const stmt = new ParameterizedSQL(query); - let stmt = new ParameterizedSQL(query); - - if (filter.where) { - const value = filter.where.firstName; - const myFilter = { - where: {or: [ + filter.where = buildFilter(filter.where, (param, value) => { + switch (param) { + case 'firstName': + return {or: [ {'w.firstName': {like: `%${value}%`}}, {'w.lastName': {like: `%${value}%`}}, {'u.name': {like: `%${value}%`}}, {'u.nickname': {like: `%${value}%`}} - ]} - }; + ]}; + case 'id': + return {'w.id': value}; + } + }); - stmt.merge(conn.makeSuffix(myFilter)); - } + let myFilter = { + where: {'u.active': true} + }; - if (tx) await tx.commit(); + myFilter = mergeFilters(myFilter, filter); + stmt.merge(conn.makeSuffix(myFilter)); return conn.executeStmt(stmt); }; }; diff --git a/modules/ticket/back/methods/ticket-request/specs/getItemTypeWorkers.spec.js b/modules/ticket/back/methods/ticket-request/specs/getItemTypeWorkers.spec.js index ae5c508b6..c57451c26 100644 --- a/modules/ticket/back/methods/ticket-request/specs/getItemTypeWorkers.spec.js +++ b/modules/ticket/back/methods/ticket-request/specs/getItemTypeWorkers.spec.js @@ -1,12 +1,10 @@ const models = require('vn-loopback/server/server').models; describe('ticket-request getItemTypeWorker()', () => { - const ctx = {req: {accessToken: {userId: 18}}}; - it('should return the buyer as result', async() => { const filter = {where: {firstName: 'buyer'}}; - const result = await models.TicketRequest.getItemTypeWorker(ctx, filter); + const result = await models.TicketRequest.getItemTypeWorker(filter); expect(result.length).toEqual(1); }); @@ -14,7 +12,7 @@ describe('ticket-request getItemTypeWorker()', () => { it('should return the workers at itemType as result', async() => { const filter = {}; - const result = await models.TicketRequest.getItemTypeWorker(ctx, filter); + const result = await models.TicketRequest.getItemTypeWorker(filter); expect(result.length).toBeGreaterThan(1); }); diff --git a/modules/ticket/back/methods/ticket/addSale.js b/modules/ticket/back/methods/ticket/addSale.js index 2cd02a8a4..cbf884273 100644 --- a/modules/ticket/back/methods/ticket/addSale.js +++ b/modules/ticket/back/methods/ticket/addSale.js @@ -1,4 +1,3 @@ - const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { @@ -47,9 +46,7 @@ module.exports = Self => { } try { - const isEditable = await models.Ticket.isEditable(ctx, id, myOptions); - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await models.Ticket.isEditableOrThrow(ctx, id, myOptions); const item = await models.Item.findById(itemId, null, myOptions); const ticket = await models.Ticket.findById(id, { diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index 48aee36d4..b5ff50d59 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -1,4 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); const loggable = require('vn-loopback/util/log'); module.exports = Self => { @@ -116,10 +115,7 @@ module.exports = Self => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; const $t = ctx.req.__; // $translate - const isEditable = await models.Ticket.isEditable(ctx, args.id, myOptions); - - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await models.Ticket.isEditableOrThrow(ctx, args.id, myOptions); const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE'); if (!editZone) { diff --git a/modules/ticket/back/methods/ticket/docuwareDownload.js b/modules/ticket/back/methods/ticket/docuwareDownload.js new file mode 100644 index 000000000..e9b74b1a9 --- /dev/null +++ b/modules/ticket/back/methods/ticket/docuwareDownload.js @@ -0,0 +1,55 @@ +module.exports = Self => { + Self.remoteMethod('docuwareDownload', { + description: 'Download a ticket delivery note document', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'Number', + description: 'The document 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/docuwareDownload`, + verb: 'GET' + } + }); + + Self.docuwareDownload = async id => { + const filter = { + condition: [ + { + DBName: docuwareInfo.findById, + Value: [id] + }, + { + DBName: 'ESTADO', + Value: ['Firmado'] + } + ], + sortOrder: [ + { + Field: 'FILENAME', + Direction: 'Desc' + } + ] + }; + return Self.app.models.Docuware.download(id, 'deliveryNote', filter); + }; +}; diff --git a/modules/ticket/back/methods/ticket/isEditable.js b/modules/ticket/back/methods/ticket/isEditable.js index 13bd4d57f..f657cd5e3 100644 --- a/modules/ticket/back/methods/ticket/isEditable.js +++ b/modules/ticket/back/methods/ticket/isEditable.js @@ -20,41 +20,12 @@ module.exports = Self => { }); Self.isEditable = async(ctx, id, options) => { - const models = Self.app.models; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - const state = await models.TicketState.findOne({ - where: {ticketFk: id} - }, myOptions); - - const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); - - const alertLevel = state ? state.alertLevel : null; - const ticket = await models.Ticket.findById(id, { - fields: ['clientFk'], - include: [{ - relation: 'client', - scope: { - include: { - relation: 'type' - } - } - }] - }, myOptions); - - const isLocked = await models.Ticket.isLocked(id, myOptions); - const isWeekly = await models.TicketWeekly.findOne({where: {ticketFk: id}}, myOptions); - - const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0); - const isNormalClient = ticket && ticket.client().type().code == 'normal'; - const isEditable = !(alertLevelGreaterThanZero && isNormalClient); - - if (ticket && (isEditable || isRoleAdvanced) && !isLocked && !isWeekly) - return true; - - return false; + try { + await Self.isEditableOrThrow(ctx, id, options); + } catch (e) { + if (e.name === 'ForbiddenError') return false; + throw e; + } + return true; }; }; diff --git a/modules/ticket/back/methods/ticket/isEditableOrThrow.js b/modules/ticket/back/methods/ticket/isEditableOrThrow.js new file mode 100644 index 000000000..6a8bafa2c --- /dev/null +++ b/modules/ticket/back/methods/ticket/isEditableOrThrow.js @@ -0,0 +1,49 @@ +const ForbiddenError = require('vn-loopback/util/forbiddenError'); + +module.exports = Self => { + Self.isEditableOrThrow = async(ctx, id, options) => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const state = await models.TicketState.findOne({ + where: {ticketFk: id} + }, myOptions); + + const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); + const canEditWeeklyTicket = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'canEditWeekly', 'WRITE'); + const alertLevel = state ? state.alertLevel : null; + const ticket = await models.Ticket.findById(id, { + fields: ['clientFk'], + include: [{ + relation: 'client', + scope: { + include: { + relation: 'type' + } + } + }] + }, myOptions); + + const isLocked = await models.Ticket.isLocked(id, myOptions); + const isWeekly = await models.TicketWeekly.findOne({where: {ticketFk: id}}, myOptions); + + const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0); + const isNormalClient = ticket && ticket.client().type().code == 'normal'; + const isEditable = !(alertLevelGreaterThanZero && isNormalClient); + + if (!ticket) + throw new ForbiddenError(`The ticket doesn't exist.`); + + if (!isEditable && !isRoleAdvanced) + throw new ForbiddenError(`This ticket is not editable.`); + + if (isLocked) + throw new ForbiddenError(`This ticket is locked.`); + + if (isWeekly && !canEditWeeklyTicket) + throw new ForbiddenError(`You don't have enough privileges.`); + }; +}; diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index 22fe7b3f5..e18e58e0b 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -14,7 +14,7 @@ module.exports = function(Self) { { arg: 'companyFk', description: 'The company id', - type: 'string', + type: 'number', required: true }, { @@ -67,7 +67,7 @@ module.exports = function(Self) { const [firstTicket] = tickets; const clientId = firstTicket.clientFk; - const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, myOptions); + const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, companyFk, myOptions); if (!clientCanBeInvoiced) throw new UserError(`This client can't be invoiced`); diff --git a/modules/ticket/back/methods/ticket/priceDifference.js b/modules/ticket/back/methods/ticket/priceDifference.js index 42a71f1c8..495e9e1aa 100644 --- a/modules/ticket/back/methods/ticket/priceDifference.js +++ b/modules/ticket/back/methods/ticket/priceDifference.js @@ -1,5 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { Self.remoteMethodCtx('priceDifference', { description: 'Returns sales with price difference if the ticket is editable', @@ -72,10 +70,7 @@ module.exports = Self => { } try { - const isEditable = await Self.isEditable(ctx, args.id, myOptions); - - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await Self.isEditableOrThrow(ctx, args.id, myOptions); const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE'); if (!editZone) { diff --git a/modules/ticket/back/methods/ticket/recalculateComponents.js b/modules/ticket/back/methods/ticket/recalculateComponents.js index eb9c8a72e..bdf74f8b6 100644 --- a/modules/ticket/back/methods/ticket/recalculateComponents.js +++ b/modules/ticket/back/methods/ticket/recalculateComponents.js @@ -1,4 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('recalculateComponents', { description: 'Calculates the price of a sale and its components', @@ -33,10 +32,7 @@ module.exports = Self => { } try { - const isEditable = await Self.isEditable(ctx, id, myOptions); - - if (!isEditable) - throw new UserError(`The current ticket can't be modified`); + await Self.isEditableOrThrow(ctx, id, myOptions); const recalculation = await Self.rawSql('CALL vn.ticket_recalcComponents(?, NULL)', [id], myOptions); diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index 878cce056..2f8c402da 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -39,10 +39,7 @@ module.exports = Self => { const ticketToDelete = await models.Ticket.findById(id, {fields: ['isDeleted']}, myOptions); if (ticketToDelete.isDeleted) return false; - const isEditable = await Self.isEditable(ctx, id, myOptions); - - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await Self.isEditableOrThrow(ctx, id, myOptions); // Check if ticket has refunds const ticketRefunds = await models.TicketRefund.find({ diff --git a/modules/ticket/back/methods/ticket/specs/addSale.spec.js b/modules/ticket/back/methods/ticket/specs/addSale.spec.js index 2e568716a..b1ecb133b 100644 --- a/modules/ticket/back/methods/ticket/specs/addSale.spec.js +++ b/modules/ticket/back/methods/ticket/specs/addSale.spec.js @@ -97,6 +97,6 @@ describe('ticket addSale()', () => { error = e; } - expect(error.message).toEqual(`The sales of this ticket can't be modified`); + expect(error.message).toEqual(`This ticket is locked.`); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/filter.spec.js b/modules/ticket/back/methods/ticket/specs/filter.spec.js index 6cc1a3ad2..510446cab 100644 --- a/modules/ticket/back/methods/ticket/specs/filter.spec.js +++ b/modules/ticket/back/methods/ticket/specs/filter.spec.js @@ -141,6 +141,7 @@ describe('ticket filter()', () => { }); it('should return the tickets that are not pending', async() => { + pending('#6010 test intermitente'); const tx = await models.Ticket.beginTransaction({}); try { diff --git a/modules/ticket/back/methods/ticket/specs/isEditable.spec.js b/modules/ticket/back/methods/ticket/specs/isEditable.spec.js index 7337017d6..301745ed3 100644 --- a/modules/ticket/back/methods/ticket/specs/isEditable.spec.js +++ b/modules/ticket/back/methods/ticket/specs/isEditable.spec.js @@ -1,156 +1,37 @@ const models = require('vn-loopback/server/server').models; -describe('ticket isEditable()', () => { - it('should return false if the given ticket does not exist', async() => { +describe('isEditable()', () => { + it('should return false if It is able to edit', async() => { const tx = await models.Ticket.beginTransaction({}); let result; try { const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 9}} - }; - - result = await models.Ticket.isEditable(ctx, 9999, options); - + const ctx = {req: {accessToken: {userId: 35}}}; + result = await models.Ticket.isEditable(ctx, 5, options); await tx.rollback(); - } catch (e) { + } catch (error) { await tx.rollback(); throw e; } - expect(result).toEqual(false); + expect(result).toBeFalse(); }); - it(`should return false if the given ticket isn't invoiced but isDeleted`, async() => { + it('should return true if It is able to edit', async() => { const tx = await models.Ticket.beginTransaction({}); let result; try { const options = {transaction: tx}; - const deletedTicket = await models.Ticket.findOne({ - where: { - invoiceOut: null, - isDeleted: true - }, - fields: ['id'] - }); - - const ctx = { - req: {accessToken: {userId: 9}} - }; - - result = await models.Ticket.isEditable(ctx, deletedTicket.id, options); - + const ctx = {req: {accessToken: {userId: 35}}}; + result = await models.Ticket.isEditable(ctx, 15, options); await tx.rollback(); - } catch (e) { + } catch (error) { await tx.rollback(); throw e; } - expect(result).toEqual(false); - }); - - it('should return true if the given ticket is editable', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 9}} - }; - - result = await models.Ticket.isEditable(ctx, 16, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(result).toEqual(true); - }); - - it('should not be able to edit a deleted or invoiced ticket even for salesAssistant', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 21}} - }; - - result = await models.Ticket.isEditable(ctx, 19, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(result).toEqual(false); - }); - - it('should not be able to edit a deleted or invoiced ticket even for productionBoss', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 50}} - }; - - result = await models.Ticket.isEditable(ctx, 19, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(result).toEqual(false); - }); - - it('should not be able to edit a deleted or invoiced ticket even for salesPerson', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 18}} - }; - - result = await models.Ticket.isEditable(ctx, 19, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(result).toEqual(false); - }); - - it('should not be able to edit if is a ticket weekly', async() => { - const tx = await models.Ticket.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const ctx = {req: {accessToken: {userId: 1}}}; - - const result = await models.Ticket.isEditable(ctx, 15, options); - - expect(result).toEqual(false); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } + expect(result).toBeTrue(); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js b/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js new file mode 100644 index 000000000..6c89bac26 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js @@ -0,0 +1,96 @@ +const models = require('vn-loopback/server/server').models; + +describe('ticket isEditableOrThrow()', () => { + it('should throw an error as the ticket does not exist', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + try { + const options = {transaction: tx}; + const ctx = { + req: {accessToken: {userId: 9}} + }; + + await models.Ticket.isEditableOrThrow(ctx, 9999, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toEqual(`The ticket doesn't exist.`); + }); + + it('should throw an error as this ticket is not editable', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + + try { + const options = {transaction: tx}; + const ctx = { + req: {accessToken: {userId: 1}} + }; + + await models.Ticket.isEditableOrThrow(ctx, 8, options); + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This ticket is not editable.`); + }); + + it('should throw an error as this ticket is locked.', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + try { + const options = {transaction: tx}; + const ctx = { + req: {accessToken: {userId: 18}} + }; + + await models.Ticket.isEditableOrThrow(ctx, 19, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This ticket is locked.`); + }); + + it('should throw an error as you do not have enough privileges.', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: 1}}}; + + await models.Ticket.isEditableOrThrow(ctx, 15, options); + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`You don't have enough privileges.`); + }); + + it('should return undefined if It can be edited', async() => { + const tx = await models.Ticket.beginTransaction({}); + let result; + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: 35}}}; + + result = await models.Ticket.isEditableOrThrow(ctx, 15, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(result).toBeUndefined(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js index cc54353ef..d01f0c1bb 100644 --- a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js +++ b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js @@ -1,5 +1,6 @@ const models = require('vn-loopback/server/server').models; const UserError = require('vn-loopback/util/user-error'); +const ForbiddenError = require('vn-loopback/util/forbiddenError'); describe('sale priceDifference()', () => { it('should return ticket price differences', async() => { @@ -59,7 +60,7 @@ describe('sale priceDifference()', () => { await tx.rollback(); } - expect(error).toEqual(new UserError(`The sales of this ticket can't be modified`)); + expect(error).toEqual(new ForbiddenError(`This ticket is not editable.`)); }); it('should return ticket movable', async() => { diff --git a/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js index ed10d114f..383c2c6d5 100644 --- a/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js +++ b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js @@ -1,4 +1,5 @@ const models = require('vn-loopback/server/server').models; +const ForbiddenError = require('vn-loopback/util/forbiddenError'); describe('ticket recalculateComponents()', () => { const ticketId = 11; @@ -38,6 +39,6 @@ describe('ticket recalculateComponents()', () => { error = e; } - expect(error).toEqual(new Error(`The current ticket can't be modified`)); + expect(error).toEqual(new ForbiddenError(`This ticket is locked.`)); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/transferClient.spec.js b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js new file mode 100644 index 000000000..c09c20083 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js @@ -0,0 +1,49 @@ +const models = require('vn-loopback/server/server').models; + +describe('Ticket transferClient()', () => { + const userId = 9; + const activeCtx = { + accessToken: {userId: userId}, + }; + const ctx = {req: activeCtx}; + + it('should throw an error as the ticket is not editable', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + + try { + const options = {transaction: tx}; + const ticketId = 4; + const clientId = 1; + await models.Ticket.transferClient(ctx, ticketId, clientId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toEqual(`This ticket is locked.`); + }); + + it('should be assigned a different clientFk', async() => { + const tx = await models.Ticket.beginTransaction({}); + let updatedTicket; + const ticketId = 10; + const clientId = 1; + + try { + const options = {transaction: tx}; + + await models.Ticket.transferClient(ctx, ticketId, clientId, options); + updatedTicket = await models.Ticket.findById(ticketId, {fields: ['clientFk']}, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(updatedTicket.clientFk).toEqual(clientId); + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/transferSales.spec.js b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js index 562688917..ef92e88c0 100644 --- a/modules/ticket/back/methods/ticket/specs/transferSales.spec.js +++ b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js @@ -33,7 +33,7 @@ describe('sale transferSales()', () => { error = e; } - expect(error.message).toEqual(`The sales of this ticket can't be modified`); + expect(error.message).toEqual(`This ticket is not editable.`); }); it('should throw an error if the receiving ticket is not editable', async() => { diff --git a/modules/ticket/back/methods/ticket/transferClient.js b/modules/ticket/back/methods/ticket/transferClient.js new file mode 100644 index 000000000..60e70d710 --- /dev/null +++ b/modules/ticket/back/methods/ticket/transferClient.js @@ -0,0 +1,45 @@ +module.exports = Self => { + Self.remoteMethodCtx('transferClient', { + description: 'Transfering ticket to another client', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'the ticket id', + http: {source: 'path'} + }, + { + arg: 'clientFk', + type: 'number', + required: true, + }, + ], + http: { + path: `/:id/transferClient`, + verb: 'PATCH' + } + }); + + Self.transferClient = async(ctx, id, clientFk, options) => { + const models = Self.app.models; + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + await Self.isEditableOrThrow(ctx, id, myOptions); + + const ticket = await models.Ticket.findById( + id, + {fields: ['id', 'shipped', 'clientFk', 'addressFk']}, + myOptions + ); + const client = await models.Client.findById(clientFk, {fields: ['id', 'defaultAddressFk']}, myOptions); + + await ticket.updateAttributes({ + clientFk, + addressFk: client.defaultAddressFk, + }); + }; +}; diff --git a/modules/ticket/back/methods/ticket/transferSales.js b/modules/ticket/back/methods/ticket/transferSales.js index 0eee50d5f..a48c5683c 100644 --- a/modules/ticket/back/methods/ticket/transferSales.js +++ b/modules/ticket/back/methods/ticket/transferSales.js @@ -1,4 +1,4 @@ -let UserError = require('vn-loopback/util/user-error'); +const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('transferSales', { @@ -48,9 +48,7 @@ module.exports = Self => { } try { - const isEditable = await models.Ticket.isEditable(ctx, id, myOptions); - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await models.Ticket.isEditableOrThrow(ctx, id, myOptions); if (ticketId) { const isReceiverEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions); diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index f0c85ecc4..c37337253 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -6,6 +6,7 @@ module.exports = function(Self) { require('../methods/ticket/componentUpdate')(Self); require('../methods/ticket/new')(Self); require('../methods/ticket/isEditable')(Self); + require('../methods/ticket/isEditableOrThrow')(Self); require('../methods/ticket/setDeleted')(Self); require('../methods/ticket/restore')(Self); require('../methods/ticket/getSales')(Self); @@ -19,6 +20,7 @@ module.exports = function(Self) { require('../methods/ticket/uploadFile')(Self); require('../methods/ticket/addSale')(Self); require('../methods/ticket/transferSales')(Self); + require('../methods/ticket/transferClient')(Self); require('../methods/ticket/recalculateComponents')(Self); require('../methods/ticket/sendSms')(Self); require('../methods/ticket/isLocked')(Self); diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index d55b8a462..cb7eeb8ee 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -36,13 +36,12 @@ translate> as PDF without prices - as PDF signed - + diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 0fc8488ca..360d93564 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -96,20 +96,14 @@ class Controller extends Section { } transferClient() { - this.$http.get(`Clients/${this.ticket.client.id}`).then(client => { - const ticket = this.ticket; + const ticket = this.ticket; + const clientFk = ticket.client.id; - const params = - { - clientFk: client.data.id, - addressFk: client.data.defaultAddressFk, - }; - - this.$http.patch(`Tickets/${ticket.id}`, params).then(() => { + this.$http.patch(`Tickets/${ticket.id}/transferClient`, {clientFk}) + .then(() => { this.vnApp.showSuccess(this.$t('Data saved!')); this.reload(); }); - }); } isTicketEditable() { @@ -327,6 +321,10 @@ class Controller extends Section { }); } + docuwareDownload() { + this.vnFile.download(`api/Ticket/${this.ticket.id}/docuwareDownload`); + } + setTicketWeight(weight) { return this.$http.patch(`Tickets/${this.ticket.id}`, {weight}) .then(() => { diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 914fe486c..1bb270165 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -326,17 +326,4 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { expect(controller.vnApp.showSuccess).toHaveBeenCalled(); }); }); - - describe('transferClient()', () => { - it(`should perform two queries, a get to obtain the clientData and a patch to update the ticket`, () => { - const client = - { - clientFk: 1101, - addressFk: 1, - }; - $httpBackend.expect('GET', `Clients/${ticket.client.id}`).respond(client); - $httpBackend.expect('PATCH', `Tickets/${ticket.id}`).respond(); - controller.transferClient(); - }); - }); }); diff --git a/modules/travel/front/search-panel/index.js b/modules/travel/front/search-panel/index.js index 089953127..f53c15ca8 100644 --- a/modules/travel/front/search-panel/index.js +++ b/modules/travel/front/search-panel/index.js @@ -38,10 +38,12 @@ class Controller extends SearchPanel { applyFilters(param) { if (typeof this.filter.scopeDays === 'number') { - const shippedFrom = Date.vnNew(); + const today = Date.vnNew(); + const shippedFrom = new Date(today.getTime()); + shippedFrom.setDate(today.getDate() - 30); shippedFrom.setHours(0, 0, 0, 0); - const shippedTo = new Date(shippedFrom.getTime()); + const shippedTo = new Date(today.getTime()); shippedTo.setDate(shippedTo.getDate() + this.filter.scopeDays); shippedTo.setHours(23, 59, 59, 999); Object.assign(this.filter, {shippedFrom, shippedTo}); diff --git a/modules/worker/back/methods/worker-dms/docuwareDownload.js b/modules/worker/back/methods/worker-dms/docuwareDownload.js new file mode 100644 index 000000000..c64c159ed --- /dev/null +++ b/modules/worker/back/methods/worker-dms/docuwareDownload.js @@ -0,0 +1,45 @@ +module.exports = Self => { + Self.remoteMethod('docuwareDownload', { + description: 'Download a worker document', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'Number', + description: 'The document 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/docuwareDownload`, + verb: 'GET' + } + }); + + Self.docuwareDownload = async id => { + const filter = { + condition: [ + { + DBName: 'FILENAME', + Value: [id] + } + ] + }; + return Self.app.models.Docuware.download(id, 'hr', filter); + }; +}; diff --git a/modules/worker/back/methods/worker-dms/filter.js b/modules/worker/back/methods/worker-dms/filter.js index c16b2bbb1..5f55f1bd7 100644 --- a/modules/worker/back/methods/worker-dms/filter.js +++ b/modules/worker/back/methods/worker-dms/filter.js @@ -5,6 +5,12 @@ module.exports = Self => { description: 'Find all instances of the model matched by filter from the data source.', accessType: 'READ', accepts: [ + { + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, { arg: 'filter', type: 'Object', @@ -17,16 +23,17 @@ module.exports = Self => { root: true }, http: { - path: `/filter`, + path: `/:id/filter`, verb: 'GET' } }); - Self.filter = async(ctx, filter) => { + Self.filter = async(ctx, id, filter) => { const conn = Self.dataSource.connector; const userId = ctx.req.accessToken.userId; + const models = Self.app.models; - const account = await Self.app.models.VnUser.findById(userId); + const account = await models.VnUser.findById(userId); const stmt = new ParameterizedSQL( `SELECT d.id dmsFk, d.reference, d.description, d.file, d.created, d.hardCopyNumber, d.hasFile FROM workerDocument wd @@ -47,7 +54,38 @@ module.exports = Self => { }] }, oldWhere]}; stmt.merge(conn.makeSuffix(filter)); + const workerDms = await conn.executeStmt(stmt); - return await conn.executeStmt(stmt); + // Get docuware info + const docuware = await models.Docuware.findOne({ + fields: ['dmsTypeFk'], + where: {code: 'hr', action: 'find'} + }); + const docuwareDmsType = docuware.dmsTypeFk; + let workerDocuware = []; + if (!docuwareDmsType || (docuwareDmsType && await models.DmsType.hasReadRole(ctx, docuwareDmsType))) { + const worker = await models.Worker.findById(id, {fields: ['fi', 'firstName', 'lastName']}); + const docuwareParse = { + 'Filename': 'dmsFk', + 'Tipo Documento': 'description', + 'Stored on': 'created', + 'Document ID': 'id' + }; + workerDocuware = + await models.Docuware.getById('hr', worker.lastName + worker.firstName, docuwareParse) ?? []; + for (document of workerDocuware) { + const defaultData = { + file: 'dw' + document.id + '.png', + isDocuware: true, + hardCopyNumber: null, + hasFile: false, + reference: worker.fi, + dmsFk: 'DW' + document.id + }; + + document = Object.assign(document, defaultData); + } + } + return workerDms.concat(workerDocuware); }; }; diff --git a/modules/worker/back/models/worker-dms.js b/modules/worker/back/models/worker-dms.js index b9d6f9a77..6343e902e 100644 --- a/modules/worker/back/models/worker-dms.js +++ b/modules/worker/back/models/worker-dms.js @@ -2,6 +2,7 @@ module.exports = Self => { require('../methods/worker-dms/downloadFile')(Self); require('../methods/worker-dms/removeFile')(Self); require('../methods/worker-dms/filter')(Self); + require('../methods/worker-dms/docuwareDownload')(Self); Self.isMine = async function(ctx, dmsId) { const myUserId = ctx.req.accessToken.userId; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index 877add57b..08f63ddf9 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -3,7 +3,7 @@ data="absenceTypes" auto-load="true"> -
+
Autonomous worker @@ -73,41 +73,39 @@ value-field="businessFk" order="businessFk DESC" limit="5"> - -
#{{businessFk}}
-
- {{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}} -
-
- -
-
- - - - - {{absenceType.name}} - -
-
- - - - Festive - - - - - Current day - -
+ +
#{{businessFk}}
+
+ {{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}} +
+
+
+
+ + + + + {{absenceType.name}} + +
+
+ + + + Festive + + + + + Current day + +
+
+ - diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index 87e806cc3..5606ad0ce 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -31,6 +31,8 @@ class Controller extends Section { } set businessId(value) { + if (!this.card.hasWorkCenter) return; + this._businessId = value; if (value) { this.refresh() @@ -64,7 +66,7 @@ class Controller extends Section { set worker(value) { this._worker = value; - if (value && value.hasWorkCenter) { + if (value) { this.getIsSubordinate(); this.getActiveContract(); } @@ -293,5 +295,8 @@ ngModule.vnComponent('vnWorkerCalendar', { controller: Controller, bindings: { worker: '<' + }, + require: { + card: '^vnWorkerCard' } }); diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index 4b78d883b..5d7ae0795 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -20,6 +20,9 @@ describe('Worker', () => { controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'}; controller.$params.id = 1106; controller._worker = {id: 1106}; + controller.card = { + hasWorkCenter: true + }; })); describe('year() getter', () => { @@ -74,7 +77,7 @@ describe('Worker', () => { let yesterday = new Date(today.getTime()); yesterday.setDate(yesterday.getDate() - 1); - controller.worker = {id: 1107, hasWorkCenter: true}; + controller.worker = {id: 1107}; expect(controller.getIsSubordinate).toHaveBeenCalledWith(); expect(controller.getActiveContract).toHaveBeenCalledWith(); diff --git a/modules/worker/front/card/index.js b/modules/worker/front/card/index.js index 35f331764..b8b533c5d 100644 --- a/modules/worker/front/card/index.js +++ b/modules/worker/front/card/index.js @@ -3,7 +3,7 @@ import ModuleCard from 'salix/components/module-card'; class Controller extends ModuleCard { reload() { - let filter = { + const filter = { include: [ { relation: 'user', @@ -32,13 +32,12 @@ class Controller extends ModuleCard { ] }; - this.$http.get(`Workers/${this.$params.id}`, {filter}) - .then(res => this.worker = res.data) - .then(() => - this.$http.get(`Workers/${this.$params.id}/activeContract`) - .then(res => { - if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk; - })); + return Promise.all([ + this.$http.get(`Workers/${this.$params.id}`, {filter}) + .then(res => this.worker = res.data), + this.$http.get(`Workers/${this.$params.id}/activeContract`) + .then(res => this.hasWorkCenter = res.data?.workCenterFk) + ]); } } diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index 58ac3d9e6..ea005e1a2 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -5,8 +5,8 @@
- - Click to exclude the user from getting disabled - - - Click to allow the user to be disabled - + + {{$ctrl.workerExcluded + ? 'Click to allow the user to be disabled' + : 'Click to exclude the user from getting disabled'}} +
@@ -84,7 +77,7 @@ - - \ No newline at end of file + diff --git a/modules/worker/front/descriptor/index.js b/modules/worker/front/descriptor/index.js index ef2f64e85..a53528ef2 100644 --- a/modules/worker/front/descriptor/index.js +++ b/modules/worker/front/descriptor/index.js @@ -18,28 +18,19 @@ class Controller extends Descriptor { this.getIsExcluded(); } - get excluded() { - return this.entity.excluded; - } - - set excluded(value) { - this.entity.excluded = value; - } - getIsExcluded() { - this.$http.get(`workerDisableExcludeds/${this.entity.id}/exists`).then(data => { - this.excluded = data.data.exists; + this.$http.get(`WorkerDisableExcludeds/${this.entity.id}/exists`).then(data => { + this.workerExcluded = data.data.exists; }); } handleExcluded() { - if (this.excluded) { - this.$http.delete(`workerDisableExcludeds/${this.entity.id}`); - this.excluded = false; - } else { - this.$http.post(`workerDisableExcludeds`, {workerFk: this.entity.id, dated: new Date}); - this.excluded = true; - } + if (this.workerExcluded) + this.$http.delete(`WorkerDisableExcludeds/${this.entity.id}`); + else + this.$http.post(`WorkerDisableExcludeds`, {workerFk: this.entity.id, dated: new Date}); + + this.workerExcluded = !this.workerExcluded; } loadData() { diff --git a/modules/worker/front/dms/index/index.html b/modules/worker/front/dms/index/index.html index 1404336a2..aefbbcf34 100644 --- a/modules/worker/front/dms/index/index.html +++ b/modules/worker/front/dms/index/index.html @@ -1,6 +1,6 @@ - + ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)"> {{::document.file}} @@ -63,16 +63,14 @@ + ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)"> - + - - + + + + @@ -91,9 +97,9 @@ fixed-bottom-right> - - \ No newline at end of file + diff --git a/modules/worker/front/dms/index/index.js b/modules/worker/front/dms/index/index.js index 9bb3c896a..489fe1732 100644 --- a/modules/worker/front/dms/index/index.js +++ b/modules/worker/front/dms/index/index.js @@ -17,9 +17,15 @@ class Controller extends Component { }); } - downloadFile(dmsId) { + downloadFile(dmsId, isDocuware) { + if (isDocuware) return this.vnFile.download(`api/workerDms/${dmsId}/docuwareDownload`); this.vnFile.download(`api/workerDms/${dmsId}/downloadFile`); } + + async openDocuware() { + const url = await this.vnApp.getUrl(`WebClient`, 'docuware'); + if (url) window.open(url).focus(); + } } Controller.$inject = ['$element', '$scope', 'vnFile']; diff --git a/modules/worker/front/time-control/index.html b/modules/worker/front/time-control/index.html index 5f0855ee6..760b0dafc 100644 --- a/modules/worker/front/time-control/index.html +++ b/modules/worker/front/time-control/index.html @@ -4,7 +4,7 @@ filter="::$ctrl.filter" data="$ctrl.hours"> -
+
@@ -107,7 +107,7 @@
Autonomous worker @@ -136,6 +136,7 @@
+ { + if (!this.card.hasWorkCenter) return; + this.getWorkedHours(this.started, this.ended); this.getAbsences(); }); @@ -151,7 +153,6 @@ class Controller extends Section { } getAbsences() { - if (!this.worker.hasWorkerCenter) return; const fullYear = this.started.getFullYear(); let params = { workerFk: this.$params.id, @@ -486,5 +487,8 @@ ngModule.vnComponent('vnWorkerTimeControl', { controller: Controller, bindings: { worker: '<' + }, + require: { + card: '^vnWorkerCard' } }); diff --git a/modules/worker/front/time-control/index.spec.js b/modules/worker/front/time-control/index.spec.js index 0132f50fe..6d8510ba8 100644 --- a/modules/worker/front/time-control/index.spec.js +++ b/modules/worker/front/time-control/index.spec.js @@ -16,9 +16,8 @@ describe('Component vnWorkerTimeControl', () => { $scope = $rootScope.$new(); $element = angular.element(''); controller = $componentController('vnWorkerTimeControl', {$element, $scope}); - controller.worker = { - hasWorkerCenter: true - + controller.card = { + hasWorkCenter: true }; })); diff --git a/package-lock.json b/package-lock.json index ee6d4e0fa..5506075b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.30.01", + "version": "23.32.01", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 66e341ad5..37e39d5a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.30.01", + "version": "23.32.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", diff --git a/print/core/smtp.js b/print/core/smtp.js index 276b85401..8c07e7eca 100644 --- a/print/core/smtp.js +++ b/print/core/smtp.js @@ -10,16 +10,17 @@ module.exports = { async send(options) { options.from = `${config.app.senderName} <${config.app.senderEmail}>`; - if (!process.env.NODE_ENV) - options.to = config.app.senderEmail; + const env = process.env.NODE_ENV; + const canSend = env === 'production' || !env || options.force; - if (process.env.NODE_ENV !== 'production' && !options.force) { + if (!canSend || !config.smtp.auth.user) { const notProductionError = {message: 'This not production, this email not sended'}; await this.mailLog(options, notProductionError); + return Promise.resolve(true); } - if (!config.smtp.auth.user) - return Promise.resolve(true); + if (!env) + options.to = config.app.senderEmail; let res; let error; diff --git a/print/templates/reports/invoice/sql/sales.sql b/print/templates/reports/invoice/sql/sales.sql index f5721a594..3833a3700 100644 --- a/print/templates/reports/invoice/sql/sales.sql +++ b/print/templates/reports/invoice/sql/sales.sql @@ -1,31 +1,19 @@ -SELECT +SELECT io.ref, - c.socialName, - sa.iban, - pm.name AS payMethod, - t.clientFk, - t.shipped, - t.nickname, s.ticketFk, - s.itemFk, - s.concept, - s.quantity, - s.price, + ib.ediBotanic botanical, + s.quantity, + s.price, s.discount, - i.tag5, - i.value5, - i.tag6, - i.value6, - i.tag7, - i.value7, - tc.code AS vatType, - ib.ediBotanic botanical + s.itemFk, + s.concept, + tc.code vatType FROM vn.invoiceOut io JOIN vn.ticket t ON t.refFk = io.ref JOIN vn.supplier su ON su.id = io.companyFk - JOIN vn.client c ON c.id = t.clientFk + JOIN vn.client c ON c.id = t.clientFk JOIN vn.payMethod pm ON pm.id = c.payMethodFk - JOIN vn.company co ON co.id = io.companyFk + JOIN vn.company co ON co.id = io.companyFk JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk JOIN vn.sale s ON s.ticketFk = t.id JOIN item i ON i.id = s.itemFk @@ -38,35 +26,23 @@ SELECT AND itc.itemFk = s.itemFk JOIN vn.taxClass tc ON tc.id = itc.taxClassFk WHERE t.refFk = ? - UNION ALL -SELECT + UNION ALL +SELECT io.ref, - c.socialName, - sa.iban, - pm.name AS payMethod, - t.clientFk, - t.shipped, - t.nickname, - t.id AS ticketFk, + t.id ticketFk, + NULL botanical, + ts.quantity, + ts.price, + 0 discount, '', - ts.description concept, - ts.quantity, - ts.price, - 0 discount, - NULL AS tag5, - NULL AS value5, - NULL AS tag6, - NULL AS value6, - NULL AS tag7, - NULL AS value7, - tc.code AS vatType, - NULL AS botanical + ts.description concept, + tc.code vatType FROM vn.invoiceOut io JOIN vn.ticket t ON t.refFk = io.ref JOIN vn.ticketService ts ON ts.ticketFk = t.id - JOIN vn.client c ON c.id = t.clientFk + JOIN vn.client c ON c.id = t.clientFk JOIN vn.payMethod pm ON pm.id = c.payMethodFk - JOIN vn.company co ON co.id = io.companyFk + JOIN vn.company co ON co.id = io.companyFk JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk JOIN vn.taxClass tc ON tc.id = ts.taxClassFk - WHERE t.refFk = ? \ No newline at end of file + WHERE t.refFk = ?