diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js index c0a4e8ef34..19224057cd 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 2053ddf851..74d922236e 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 56d006ee77..a0d72ce017 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 dd11951cce..8460bb561a 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 0000000000..cdf8a3b625 --- /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 fcc1671a6b..bc580a0796 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/models/company.json b/back/models/company.json index f16c5762fc..f8b5641aca 100644 --- a/back/models/company.json +++ b/back/models/company.json @@ -18,6 +18,9 @@ }, "expired": { "type": "date" + }, + "supplierAccountFk": { + "type": "number" } }, "scope": { diff --git a/back/models/docuware.json b/back/models/docuware.json index dec20eedec..b1a6a8bce3 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/db/changes/233001/00-company.sql b/db/changes/233001/00-company.sql new file mode 100644 index 0000000000..a3b61b9ccc --- /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/233201/00-transferClient.sql b/db/changes/233201/00-transferClient.sql new file mode 100644 index 0000000000..8a7ce05438 --- /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-workerDocuware.sql b/db/changes/233201/00-workerDocuware.sql new file mode 100644 index 0000000000..2f2c4a1cd8 --- /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/front/core/services/app.js b/front/core/services/app.js index fb0a087773..6c80fafcca 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 cf8801b52e..e9811e38f0 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/es.json b/loopback/locale/es.json index 08f5bf7657..d95e8d8a41 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -305,6 +305,7 @@ "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}}", + "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" } diff --git a/modules/client/back/methods/client/canBeInvoiced.js b/modules/client/back/methods/client/canBeInvoiced.js index 567d491f1f..843e9549fc 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/specs/canBeInvoiced.spec.js b/modules/client/back/methods/client/specs/canBeInvoiced.spec.js index 2f11d80139..397be3c921 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/ticket/back/methods/ticket/docuwareDownload.js b/modules/ticket/back/methods/ticket/docuwareDownload.js new file mode 100644 index 0000000000..e9b74b1a96 --- /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/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index 22fe7b3f5d..e18e58e0b4 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/specs/transferClient.spec.js b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js new file mode 100644 index 0000000000..ed10e5159a --- /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(`The current ticket can't be modified`); + }); + + 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/transferClient.js b/modules/ticket/back/methods/ticket/transferClient.js new file mode 100644 index 0000000000..04c153bd2e --- /dev/null +++ b/modules/ticket/back/methods/ticket/transferClient.js @@ -0,0 +1,49 @@ +const UserError = require('vn-loopback/util/user-error'); +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); + + const isEditable = await Self.isEditable(ctx, id, myOptions); + + if (!isEditable) + throw new UserError(`The current ticket can't be modified`); + + 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/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index f0c85ecc45..b432c9f6bb 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -19,6 +19,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 d55b8a462a..cb7eeb8eef 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 0fc8488cae..360d935644 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 914fe486c9..1bb2701654 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/worker/back/methods/worker-dms/docuwareDownload.js b/modules/worker/back/methods/worker-dms/docuwareDownload.js new file mode 100644 index 0000000000..c64c159ed1 --- /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 c16b2bbb13..5f55f1bd71 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 b9d6f9a775..6343e902e9 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/dms/index/index.html b/modules/worker/front/dms/index/index.html index 1404336a2a..aefbbcf345 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 9bb3c896a3..489fe17320 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/print/templates/reports/invoice/sql/sales.sql b/print/templates/reports/invoice/sql/sales.sql index f5721a594b..3833a37008 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 = ?