From 940b4310590259f33a1475a68677e4cb26275ba3 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Wed, 5 Oct 2022 14:04:30 +0200 Subject: [PATCH 01/72] feat(model) Created the model for packagingMistake --- modules/ticket/back/model-config.json | 3 +++ .../ticket/back/models/packagingMistake.json | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 modules/ticket/back/models/packagingMistake.json diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 8a6ac0c00..859ecdef8 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -20,6 +20,9 @@ "Packaging": { "dataSource": "vn" }, + "PackagingMistake": { + "dataSource": "vn" + }, "PrintServerQueue": { "dataSource": "vn" }, diff --git a/modules/ticket/back/models/packagingMistake.json b/modules/ticket/back/models/packagingMistake.json new file mode 100644 index 000000000..7bfc0eca2 --- /dev/null +++ b/modules/ticket/back/models/packagingMistake.json @@ -0,0 +1,25 @@ +{ + "name": "PackagingMistake", + "base": "VnModel", + "options": { + "mysql": { + "table": "packagingMistake" + } + }, + "properties": { + "expeditionFk": { + "id": true, + "type": "number" + }, + "workerFk": { + "type": "number" + }, + "typeFk": { + "type": "number" + }, + "created": { + "type": "date" + } + } + } + \ No newline at end of file From 704cb71b61631478b54445b1542dc7a55091b281 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 7 Dec 2022 13:53:59 +0100 Subject: [PATCH 02/72] refs #4866 instance log added and e2e done --- .../collection/spec/getCollection.spec.js | 2 +- e2e/helpers/selectors.js | 5 +- .../05-ticket/01-sale/02_edit_sale.spec.js | 9 ++ front/salix/components/index.js | 1 + .../salix/components/instance-log/index.html | 69 ++++++++++++ front/salix/components/instance-log/index.js | 106 ++++++++++++++++++ .../components/instance-log/locale/es.yml | 15 +++ .../salix/components/instance-log/style.scss | 43 +++++++ modules/ticket/front/sale/index.html | 63 +++++++---- modules/ticket/front/sale/locale/es.yml | 5 +- 10 files changed, 291 insertions(+), 27 deletions(-) create mode 100644 front/salix/components/instance-log/index.html create mode 100644 front/salix/components/instance-log/index.js create mode 100644 front/salix/components/instance-log/locale/es.yml create mode 100644 front/salix/components/instance-log/style.scss diff --git a/back/methods/collection/spec/getCollection.spec.js b/back/methods/collection/spec/getCollection.spec.js index e87efb4a0..edc8e4dfc 100644 --- a/back/methods/collection/spec/getCollection.spec.js +++ b/back/methods/collection/spec/getCollection.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -describe('ticket getCollection()', () => { +fdescribe('ticket getCollection()', () => { it('should return a list of collections', async() => { let ctx = {req: {accessToken: {userId: 1107}}}; let response = await models.Collection.getCollection(ctx); diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index e374e266e..6c2ebf6a9 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -678,7 +678,10 @@ export default { moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]', moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]', stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]', - moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield' + moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield', + firstSaleHistoryButton: 'vn-ticket-sale vn-tr:nth-child(1) vn-icon-button[icon="history"]', + firstSaleHistory: 'form vn-table div > vn-tbody > vn-tr', + closeHistory: 'div.window vn-button[icon="clear"]' }, ticketTracking: { createStateButton: 'vn-float-button' diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js index 67dfd83bf..9d6fddbe6 100644 --- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js @@ -196,6 +196,15 @@ describe('Ticket Edit sale path', () => { expect(result).toContain('22.50'); }); + it('should check in the history that logs has been added', async() => { + await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton); + await page.waitForSelector(selectors.ticketSales.firstSaleHistory); + const result = await page.countElement(selectors.ticketSales.firstSaleHistory); + + expect(result).toBeGreaterThan(0); + await page.waitToClick(selectors.ticketSales.closeHistory); + }); + it('should recalculate price of sales', async() => { await page.waitToClick(selectors.ticketSales.firstSaleCheckbox); await page.waitToClick(selectors.ticketSales.secondSaleCheckbox); diff --git a/front/salix/components/index.js b/front/salix/components/index.js index dbe9fe81a..fccf99521 100644 --- a/front/salix/components/index.js +++ b/front/salix/components/index.js @@ -19,3 +19,4 @@ import './user-popover'; import './upload-photo'; import './bank-entity'; import './log'; +import './instance-log'; diff --git a/front/salix/components/instance-log/index.html b/front/salix/components/instance-log/index.html new file mode 100644 index 000000000..2393bc8e1 --- /dev/null +++ b/front/salix/components/instance-log/index.html @@ -0,0 +1,69 @@ + + + + + + + + + + Date + User + Action + Changes + + + + + + {{::log.creationDate | date:'dd/MM/yyyy HH:mm'}} + + + {{::log.user.name || 'System' | translate}} + + + + {{::$ctrl.actionsText[log.action]}} + + + + + + + + + + + + + + + + + +
FieldBeforeAfter
{{prop.name}}{{::$ctrl.formatValue(prop.old)}}{{::$ctrl.formatValue(prop.new)}}
+ +
+ {{::log.description}} +
+
+
+
+
+
+ +
+
+
+ + diff --git a/front/salix/components/instance-log/index.js b/front/salix/components/instance-log/index.js new file mode 100644 index 000000000..4c17cde0b --- /dev/null +++ b/front/salix/components/instance-log/index.js @@ -0,0 +1,106 @@ +import ngModule from '../../module'; +import './style.scss'; +import Section from '../section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.actionsText = { + 'insert': 'Creates', + 'update': 'Updates', + 'delete': 'Deletes', + 'select': 'Views' + }; ``; + } + + open() { + this.filter = { + where: + {changedModel: this.changedModel, + changedModelId: this.changedModelId}, + include: [{ + relation: 'user', + scope: { + fields: ['name'], + include: { + relation: 'worker', + scope: { + fields: ['id'] + } + } + }, + }], + }; + this.$.instanceLog.show(); + } + + get logs() { + return this._logs; + } + + set logs(value) { + this._logs = value; + if (!this.logs) return; + const validations = window.validations; + for (const log of value) { + const locale = validations[log.changedModel] && validations[log.changedModel].locale + ? validations[log.changedModel].locale : {}; + log.oldProperties = this.getInstance(log.oldInstance, locale); + log.newProperties = this.getInstance(log.newInstance, locale); + let props = [].concat(log.oldProperties.map(p => p.key), log.newProperties.map(p => p.key)); + props = [...new Set(props)]; + log.props = []; + for (const prop of props) { + const matchOldProp = log.oldProperties.find(p => p.key === prop); + const matchNewProp = log.newProperties.find(p => p.key === prop); + log.props.push({ + name: prop, + old: matchOldProp ? matchOldProp.value : null, + new: matchNewProp ? matchNewProp.value : null, + }); + } + } + } + + formatValue(value) { + switch (typeof value) { + case 'boolean': + return value ? '✓' : '✗'; + default: + return value; + } + } + + getInstance(instance, locale) { + const properties = []; + let validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/; + + if (typeof instance == 'object' && instance != null) { + Object.keys(instance).forEach(property => { + if (validDate.test(instance[property])) + instance[property] = new Date(instance[property]).toLocaleString('es-ES'); + const key = locale[property] || property; + properties.push({key, value: instance[property]}); + }); + return properties; + } + return null; + } + + showWorkerDescriptor(event, workerId) { + if (!workerId) return; + this.$.workerDescriptor.show(event.target, workerId); + } +} + +ngModule.vnComponent('vnInstanceLog', { + controller: Controller, + template: require('./index.html'), + bindings: { + model: '<', + originId: '<', + changedModel: '<', + changedModelId: '<', + url: '@' + } +}); diff --git a/front/salix/components/instance-log/locale/es.yml b/front/salix/components/instance-log/locale/es.yml new file mode 100644 index 000000000..d341095d8 --- /dev/null +++ b/front/salix/components/instance-log/locale/es.yml @@ -0,0 +1,15 @@ +Date: Fecha +Model: Modelo +Action: Acción +Author: Autor +Before: Antes +After: Despues +History: Historial +Name: Nombre +Creates: Crea +Updates: Actualiza +Deletes: Elimina +Views: Visualiza +System: Sistema +note: nota +Changes: Cambios diff --git a/front/salix/components/instance-log/style.scss b/front/salix/components/instance-log/style.scss new file mode 100644 index 000000000..d62f1ac06 --- /dev/null +++ b/front/salix/components/instance-log/style.scss @@ -0,0 +1,43 @@ +@import "variables"; + +vn-instance-log { + vn-td { + vertical-align: initial !important; + } + .changes { + display: none; + } + .label { + color: $color-font-secondary; + } + .value { + color: $color-font; + } + + @media screen and (max-width: 1570px) { + vn-table .expendable { + display: none; + } + .changes { + padding-top: 10px; + display: block; + } + } + .attributes { + width: 100%; + white-space: inherit !important; + + tr { + height: 10px; + + & > td { + padding: 2px; + width: 33%; + } + & > td.field, + & > th.field { + color: gray; + } + } + } +} diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index c624b1a95..c2f45b552 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -30,7 +30,7 @@ ng-click="moreOptions.show($event)" ng-show="$ctrl.hasSelectedSales()"> - - @@ -68,6 +68,7 @@ Disc Amount Packaging + @@ -84,13 +85,13 @@ vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}"> - - @@ -108,21 +109,21 @@ - - {{::sale.visible}} - {{::sale.available}} @@ -195,7 +196,7 @@ translate-attr="{title: !$ctrl.isLocked ? 'Edit discount' : ''}" ng-click="$ctrl.showEditDiscountPopover($event, sale)" ng-if="sale.id"> - {{(sale.discount / 100) | percentage}} + {{(sale.discount / 100) | percentage}} @@ -204,6 +205,22 @@ {{::sale.item.itemPackingTypeFk | dashIfEmpty}} + + + + + + + @@ -383,8 +400,8 @@ - {{::ticket.id}} @@ -392,22 +409,22 @@ {{::ticket.agencyName}} {{::ticket.address}} - {{::ticket.nickname}} - {{::ticket.name}} - {{::ticket.street}} - {{::ticket.postalCode}} + {{::ticket.nickname}} + {{::ticket.name}} + {{::ticket.street}} + {{::ticket.postalCode}} {{::ticket.city}} @@ -502,4 +519,4 @@ vn-acl-action="remove"> Refund - \ No newline at end of file + diff --git a/modules/ticket/front/sale/locale/es.yml b/modules/ticket/front/sale/locale/es.yml index 072e57534..2668b7811 100644 --- a/modules/ticket/front/sale/locale/es.yml +++ b/modules/ticket/front/sale/locale/es.yml @@ -13,9 +13,9 @@ New ticket: Nuevo ticket Edit price: Editar precio You are going to delete lines of the ticket: Vas a eliminar lineas del ticket This ticket will be removed from current route! Continue anyway?: ¡Se eliminará el ticket de la ruta actual! ¿Continuar de todas formas? -You have to allow pop-ups in your web browser to use this functionality: +You have to allow pop-ups in your web browser to use this functionality: Debes permitir los pop-pups en tu navegador para que esta herramienta funcione correctamente -Disc: Dto +Disc: Dto Available: Disponible What is the day of receipt of the ticket?: ¿Cual es el día de preparación del pedido? Add claim: Crear reclamación @@ -39,3 +39,4 @@ Packaging: Encajado Refund: Abono Promotion mana: Maná promoción Claim mana: Maná reclamación +History: Historial From 0da90fdb4e36dbf99c69a44b0a9ea940336097e1 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 7 Dec 2022 13:56:24 +0100 Subject: [PATCH 03/72] refs #4866 fdescribe removed --- back/methods/collection/spec/getCollection.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/collection/spec/getCollection.spec.js b/back/methods/collection/spec/getCollection.spec.js index edc8e4dfc..e87efb4a0 100644 --- a/back/methods/collection/spec/getCollection.spec.js +++ b/back/methods/collection/spec/getCollection.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -fdescribe('ticket getCollection()', () => { +describe('ticket getCollection()', () => { it('should return a list of collections', async() => { let ctx = {req: {accessToken: {userId: 1107}}}; let response = await models.Collection.getCollection(ctx); From 4bafcc905ad7e8b97aae9293e68c904c6f78709f Mon Sep 17 00:00:00 2001 From: alexandre Date: Fri, 9 Dec 2022 11:41:08 +0100 Subject: [PATCH 04/72] refs #4866 fix css --- .../salix/components/instance-log/index.html | 8 ++--- .../salix/components/instance-log/style.scss | 30 ++----------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/front/salix/components/instance-log/index.html b/front/salix/components/instance-log/index.html index 2393bc8e1..56c0ab773 100644 --- a/front/salix/components/instance-log/index.html +++ b/front/salix/components/instance-log/index.html @@ -16,8 +16,8 @@ Date - User - Action + User + Action Changes @@ -47,8 +47,8 @@ {{prop.name}} - {{::$ctrl.formatValue(prop.old)}} - {{::$ctrl.formatValue(prop.new)}} + {{::$ctrl.formatValue(prop.old)}} + {{::$ctrl.formatValue(prop.new)}} diff --git a/front/salix/components/instance-log/style.scss b/front/salix/components/instance-log/style.scss index d62f1ac06..26a3a1bb4 100644 --- a/front/salix/components/instance-log/style.scss +++ b/front/salix/components/instance-log/style.scss @@ -1,37 +1,13 @@ -@import "variables"; - -vn-instance-log { +.vn-dialog .window { + width: max-content; vn-td { - vertical-align: initial !important; - } - .changes { - display: none; - } - .label { - color: $color-font-secondary; - } - .value { - color: $color-font; - } - - @media screen and (max-width: 1570px) { - vn-table .expendable { - display: none; - } - .changes { - padding-top: 10px; - display: block; - } + vertical-align: initial; } .attributes { width: 100%; - white-space: inherit !important; - tr { height: 10px; - & > td { - padding: 2px; width: 33%; } & > td.field, From f7a97a8139fdc5ed72c6b53b9800e66cf904b156 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 14 Dec 2022 13:31:56 +0100 Subject: [PATCH 05/72] =?UTF-8?q?fix:=20siempre=20muestra=20el=20bot=C3=B3?= =?UTF-8?q?n=20si=20hay=20datos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/travel/front/extra-community/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index 5174f8da2..3e5d5f224 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -27,7 +27,7 @@
From e6168356002e27c0104428ff60e1934056673af8 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 14 Dec 2022 13:40:28 +0100 Subject: [PATCH 06/72] delete: hasDateRange() --- modules/travel/front/extra-community/index.js | 10 ---------- modules/travel/front/extra-community/index.spec.js | 11 ----------- 2 files changed, 21 deletions(-) diff --git a/modules/travel/front/extra-community/index.js b/modules/travel/front/extra-community/index.js index a4ac487e6..2389570b9 100644 --- a/modules/travel/front/extra-community/index.js +++ b/modules/travel/front/extra-community/index.js @@ -43,16 +43,6 @@ class Controller extends Section { this.smartTableOptions = {}; } - get hasDateRange() { - const userParams = this.$.model.userParams; - const hasLanded = userParams.landedTo; - const hasShipped = userParams.shippedFrom; - const hasContinent = userParams.continent; - const hasWarehouseOut = userParams.warehouseOutFk; - - return hasLanded || hasShipped || hasContinent || hasWarehouseOut; - } - onDragInterval() { if (this.dragClientY > 0 && this.dragClientY < 75) this.$window.scrollTo(0, this.$window.scrollY - 10); diff --git a/modules/travel/front/extra-community/index.spec.js b/modules/travel/front/extra-community/index.spec.js index ae48b9ca1..18ddee665 100644 --- a/modules/travel/front/extra-community/index.spec.js +++ b/modules/travel/front/extra-community/index.spec.js @@ -14,17 +14,6 @@ describe('Travel Component vnTravelExtraCommunity', () => { controller.$.model.refresh = jest.fn(); })); - describe('hasDateRange()', () => { - it('should return truthy when shippedFrom or landedTo are set as userParams', () => { - const now = new Date(); - controller.$.model.userParams = {shippedFrom: now, landedTo: now}; - - const result = controller.hasDateRange; - - expect(result).toBeTruthy(); - }); - }); - describe('findDraggable()', () => { it('should find the draggable element', () => { const draggable = document.createElement('tr'); From a46c2ced7c3ddfc015719f3966849e7bc57274b2 Mon Sep 17 00:00:00 2001 From: guillermo Date: Mon, 19 Dec 2022 07:26:07 +0100 Subject: [PATCH 07/72] refs #4975: Added last mdbVersion/last method --- loopback/locale/es.json | 9 +++-- modules/mdb/back/methods/mdbVersion/last.js | 40 +++++++++++++++++++ modules/mdb/back/methods/mdbVersion/upload.js | 6 +-- modules/mdb/back/models/mdbVersion.js | 1 + 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 modules/mdb/back/methods/mdbVersion/last.js diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 932dfe98f..478b0da15 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -247,7 +247,8 @@ "Empty data source": "Origen de datos vacio", "Email verify": "Correo de verificación", "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment", - "Receipt's bank was not found": "No se encontró el banco del recibo", - "This receipt was not compensated": "Este recibo no ha sido compensado", - "Client's email was not found": "No se encontró el email del cliente" -} + "Receipt's bank was not found": "No se encontró el banco del recibo", + "This receipt was not compensated": "Este recibo no ha sido compensado", + "Client's email was not found": "No se encontró el email del cliente", + "Not exist this app name": "No existe este nombre de aplicación" +} \ No newline at end of file diff --git a/modules/mdb/back/methods/mdbVersion/last.js b/modules/mdb/back/methods/mdbVersion/last.js new file mode 100644 index 000000000..91329be4e --- /dev/null +++ b/modules/mdb/back/methods/mdbVersion/last.js @@ -0,0 +1,40 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('last', { + description: 'Upload and attach a access file', + accepts: [ + { + arg: 'appName', + type: 'string', + required: true, + description: 'The app name' + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:appName/last`, + verb: 'GET' + } + }); + + Self.last = async(ctx, appName) => { + const models = Self.app.models; + const versions = await models.MdbVersion.find({ + where: {app: appName} + }); + + if (!versions.length) + throw new UserError('Not exist this app name'); + + let maxNumber = 0; + for (let mdb of versions) { + if (mdb.version > maxNumber) + maxNumber = mdb.version; + } + return maxNumber; + }; +}; diff --git a/modules/mdb/back/methods/mdbVersion/upload.js b/modules/mdb/back/methods/mdbVersion/upload.js index 57df35ce7..d165a7bb5 100644 --- a/modules/mdb/back/methods/mdbVersion/upload.js +++ b/modules/mdb/back/methods/mdbVersion/upload.js @@ -11,14 +11,12 @@ module.exports = Self => { type: 'string', required: true, description: 'The app name' - }, - { + }, { arg: 'newVersion', type: 'number', required: true, description: `The new version number` - }, - { + }, { arg: 'branch', type: 'string', required: true, diff --git a/modules/mdb/back/models/mdbVersion.js b/modules/mdb/back/models/mdbVersion.js index b36ee2a60..3a7a0c6f3 100644 --- a/modules/mdb/back/models/mdbVersion.js +++ b/modules/mdb/back/models/mdbVersion.js @@ -1,3 +1,4 @@ module.exports = Self => { require('../methods/mdbVersion/upload')(Self); + require('../methods/mdbVersion/last')(Self); }; From dc82e610b7f58739221cd7c56505af83a52af5f2 Mon Sep 17 00:00:00 2001 From: guillermo Date: Mon, 19 Dec 2022 11:24:18 +0100 Subject: [PATCH 08/72] refs #4975 Minor changes --- loopback/locale/es.json | 2 +- modules/mdb/back/methods/mdbVersion/last.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 478b0da15..c2dad5e03 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -250,5 +250,5 @@ "Receipt's bank was not found": "No se encontró el banco del recibo", "This receipt was not compensated": "Este recibo no ha sido compensado", "Client's email was not found": "No se encontró el email del cliente", - "Not exist this app name": "No existe este nombre de aplicación" + "App name does not exist": "El nombre de aplicación no es válido" } \ No newline at end of file diff --git a/modules/mdb/back/methods/mdbVersion/last.js b/modules/mdb/back/methods/mdbVersion/last.js index 91329be4e..7e4a18ac3 100644 --- a/modules/mdb/back/methods/mdbVersion/last.js +++ b/modules/mdb/back/methods/mdbVersion/last.js @@ -12,7 +12,7 @@ module.exports = Self => { } ], returns: { - type: ['object'], + type: 'number', root: true }, http: { @@ -24,11 +24,12 @@ module.exports = Self => { Self.last = async(ctx, appName) => { const models = Self.app.models; const versions = await models.MdbVersion.find({ - where: {app: appName} + where: {app: appName}, + fields: ['version'] }); if (!versions.length) - throw new UserError('Not exist this app name'); + throw new UserError('App name does not exist'); let maxNumber = 0; for (let mdb of versions) { From f82870bec674bd3e9c1573a8014a5f71575dc33b Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 19 Dec 2022 14:39:38 +0100 Subject: [PATCH 09/72] feat(item_fixed-price): recalculate rate2 and when change item keep minPrice of item --- db/changes/225001/00-priceFixed_getRate2.sql | 23 ++++++++++ .../item/back/methods/fixed-price/getRate2.js | 34 ++++++++++++++ modules/item/back/models/fixed-price.js | 1 + modules/item/front/fixed-price/index.html | 46 ++++++++++++------- modules/item/front/fixed-price/index.js | 23 +++++++++- modules/item/front/fixed-price/locale/es.yml | 2 - 6 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 db/changes/225001/00-priceFixed_getRate2.sql create mode 100644 modules/item/back/methods/fixed-price/getRate2.js diff --git a/db/changes/225001/00-priceFixed_getRate2.sql b/db/changes/225001/00-priceFixed_getRate2.sql new file mode 100644 index 000000000..cf36efb57 --- /dev/null +++ b/db/changes/225001/00-priceFixed_getRate2.sql @@ -0,0 +1,23 @@ +DROP FUNCTION IF EXISTS `vn`.`priceFixed_getRate2`; + +DELIMITER $$ +$$ +CREATE FUNCTION `vn`.`priceFixed_getRate2`(vFixedPriceFk INT, vRate3 DOUBLE) +RETURNS DOUBLE +BEGIN + + DECLARE vWarehouse INT; + DECLARE vRate2 DOUBLE; + + SELECT round(vRate3 * (1 + ((r.rate2 - r.rate3)/100)), 2) INTO vRate2 + FROM vn.rate r + JOIN vn.priceFixed p ON p.id = vFixedPriceFk + WHERE r.dated <= p.started + AND r.warehouseFk = p.warehouseFk + ORDER BY r.dated DESC + LIMIT 1; + + RETURN vRate2; + +END$$ +DELIMITER ; diff --git a/modules/item/back/methods/fixed-price/getRate2.js b/modules/item/back/methods/fixed-price/getRate2.js new file mode 100644 index 000000000..b8811f425 --- /dev/null +++ b/modules/item/back/methods/fixed-price/getRate2.js @@ -0,0 +1,34 @@ +module.exports = Self => { + Self.remoteMethod('getRate2', { + description: 'Return the rate2.', + accessType: 'READ', + accepts: [ + { + arg: 'fixedPriceId', + type: 'integer', + description: 'The fixedPrice Id', + required: true + }, + { + arg: 'rate3', + type: 'number', + description: `The price rate 3`, + required: true + } + ], + returns: { + type: 'number', + root: true + }, + http: { + path: `/getRate2`, + verb: 'GET' + } + }); + + Self.getRate2 = async(fixedPriceId, rate3) => { + const [result] = await Self.rawSql(`SELECT vn.priceFixed_getRate2(?, ?) as rate2`, + [fixedPriceId, rate3]); + return result.rate2; + }; +}; diff --git a/modules/item/back/models/fixed-price.js b/modules/item/back/models/fixed-price.js index 9c78c586f..91010805f 100644 --- a/modules/item/back/models/fixed-price.js +++ b/modules/item/back/models/fixed-price.js @@ -1,4 +1,5 @@ module.exports = Self => { require('../methods/fixed-price/filter')(Self); require('../methods/fixed-price/upsertFixedPrice')(Self); + require('../methods/fixed-price/getRate2')(Self); }; diff --git a/modules/item/front/fixed-price/index.html b/modules/item/front/fixed-price/index.html index 9498bf96f..f9d177562 100644 --- a/modules/item/front/fixed-price/index.html +++ b/modules/item/front/fixed-price/index.html @@ -41,14 +41,12 @@ Warehouse - P.P.U. + field="rate2"> + Grouping price - P.P.P. + field="rate3"> + Packing price Min price @@ -72,7 +70,7 @@ show-field="name" value-field="id" search-function="$ctrl.itemSearchFunc($search)" - on-change="$ctrl.upsertPrice(price)" + on-change="$ctrl.upsertPrice(price, true)" order="id DESC" tabindex="1"> @@ -112,18 +110,32 @@ - - + + {{price.rate2 | currency: 'EUR':2}} + + + + + - - + + {{price.rate3 | currency: 'EUR':2}} + + + + + { + const rate2 = res.data; + if (rate2) { + price.rate2 = rate2; + this.upsertPrice(price); + } + }); + } } ngModule.vnComponent('vnFixedPrice', { diff --git a/modules/item/front/fixed-price/locale/es.yml b/modules/item/front/fixed-price/locale/es.yml index 3f400336d..6bdfcb678 100644 --- a/modules/item/front/fixed-price/locale/es.yml +++ b/modules/item/front/fixed-price/locale/es.yml @@ -3,5 +3,3 @@ Search prices by item ID or code: Buscar por ID de artículo o código Search fixed prices: Buscar precios fijados Add fixed price: Añadir precio fijado This row will be removed: Esta linea se eliminará -Price By Unit: Precio Por Unidad -Price By Package: Precio Por Paquete \ No newline at end of file From 5d2b69d063de91b8b2fa72bb54eb93dc023835e3 Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 19 Dec 2022 15:06:59 +0100 Subject: [PATCH 10/72] test(item_fixedPrice): front recalculateRate2 --- .../item/back/methods/fixed-price/getRate2.js | 4 ++-- modules/item/front/fixed-price/index.js | 2 +- modules/item/front/fixed-price/index.spec.js | 20 +++++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/modules/item/back/methods/fixed-price/getRate2.js b/modules/item/back/methods/fixed-price/getRate2.js index b8811f425..e1a399be6 100644 --- a/modules/item/back/methods/fixed-price/getRate2.js +++ b/modules/item/back/methods/fixed-price/getRate2.js @@ -11,7 +11,7 @@ module.exports = Self => { }, { arg: 'rate3', - type: 'number', + type: 'object', description: `The price rate 3`, required: true } @@ -29,6 +29,6 @@ module.exports = Self => { Self.getRate2 = async(fixedPriceId, rate3) => { const [result] = await Self.rawSql(`SELECT vn.priceFixed_getRate2(?, ?) as rate2`, [fixedPriceId, rate3]); - return result.rate2; + return result; }; }; diff --git a/modules/item/front/fixed-price/index.js b/modules/item/front/fixed-price/index.js index 5a57a123e..f03d73f08 100644 --- a/modules/item/front/fixed-price/index.js +++ b/modules/item/front/fixed-price/index.js @@ -124,7 +124,7 @@ export default class Controller extends Section { }; this.$http.get(query, {params}) .then(res => { - const rate2 = res.data; + const rate2 = res.data.rate2; if (rate2) { price.rate2 = rate2; this.upsertPrice(price); diff --git a/modules/item/front/fixed-price/index.spec.js b/modules/item/front/fixed-price/index.spec.js index 94621e352..db9579444 100644 --- a/modules/item/front/fixed-price/index.spec.js +++ b/modules/item/front/fixed-price/index.spec.js @@ -85,5 +85,25 @@ describe('fixed price', () => { expect(controller.$.model.remove).toHaveBeenCalled(); }); }); + + describe('recalculateRate2()', () => { + it(`should rate2 recalculate`, () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + const price = { + id: 1, + itemFk: 1, + rate2: 2, + rate3: 2 + }; + const response = {rate2: 1}; + controller.recalculateRate2(price); + + const query = `FixedPrices/getRate2?fixedPriceId=${price.id}&rate3=${price.rate3}`; + $httpBackend.expectGET(query).respond(response); + $httpBackend.flush(); + + expect(price.rate2).toEqual(response.rate2); + }); + }); }); }); From cb9ad5d01284522c4a477202109fbd0b8150ac71 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 20 Dec 2022 08:45:14 +0100 Subject: [PATCH 11/72] refactor: show ticketId error --- back/methods/osticket/closeTicket.js | 94 +++++++++++++++------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index 33fe5958b..e5cbc58f4 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -25,36 +25,36 @@ module.exports = Self => { return false; const con = mysql.createConnection({ - host: `${config.hostDb}`, - user: `${config.userDb}`, - password: `${config.passwordDb}`, - port: `${config.portDb}` + host: config.hostDb, + user: config.userDb, + password: config.passwordDb, + port: config.portDb }); const sql = `SELECT ot.ticket_id, ot.number FROM osticket.ost_ticket ot - JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id + JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T' JOIN ( SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated FROM osticket.ost_thread_entry ote - WHERE ote.staff_id != 0 AND ote.type = 'R' + WHERE ote.staff_id AND ote.type = 'R' GROUP BY ote.thread_id - ) sub ON sub.thread_id = ot2.id - WHERE ot.isanswered = 1 - AND ots.state = '${config.oldStatus}' - AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`; + ) sub ON sub.thread_id = ot2.id + WHERE ot.isanswered + AND ots.state = ? + AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`; - let ticketsId = []; + const ticketsId = []; con.connect(err => { if (err) throw err; - con.query(sql, (err, results) => { - if (err) throw err; - for (const result of results) - ticketsId.push(result.ticket_id); - }); + con.query(sql, [config.oldStatus, config.day], + (err, results) => { + if (err) throw err; + for (const result of results) + ticketsId.push(result.ticket_id); + }); }); - await getRequestToken(); async function getRequestToken() { @@ -94,6 +94,39 @@ module.exports = Self => { await close(token, secondCookie); } + async function close(token, secondCookie) { + for (const ticketId of ticketsId) { + try { + const lockCode = await getLockCode(token, secondCookie, ticketId); + let form = new FormData(); + form.append('__CSRFToken__', token); + form.append('id', ticketId); + form.append('a', config.responseType); + form.append('lockCode', lockCode); + form.append('from_email_id', config.fromEmailId); + form.append('reply-to', config.replyTo); + form.append('cannedResp', 0); + form.append('response', config.comment); + form.append('signature', 'none'); + form.append('reply_status_id', config.newStatusId); + + const ostUri = `${config.host}/tickets.php?id=${ticketId}`; + const params = { + method: 'POST', + body: form, + headers: { + 'Cookie': secondCookie + } + }; + await fetch(ostUri, params); + } catch (e) { + const err = new Error(`${ticketId} Ticket close failed: ${e.message}`); + err.stack += e.stack; + throw err; + } + } + } + async function getLockCode(token, secondCookie, ticketId) { const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`; const params = { @@ -109,32 +142,5 @@ module.exports = Self => { return json.code; } - - async function close(token, secondCookie) { - for (const ticketId of ticketsId) { - const lockCode = await getLockCode(token, secondCookie, ticketId); - let form = new FormData(); - form.append('__CSRFToken__', token); - form.append('id', ticketId); - form.append('a', config.responseType); - form.append('lockCode', lockCode); - form.append('from_email_id', config.fromEmailId); - form.append('reply-to', config.replyTo); - form.append('cannedResp', 0); - form.append('response', config.comment); - form.append('signature', 'none'); - form.append('reply_status_id', config.newStatusId); - - const ostUri = `${config.host}/tickets.php?id=${ticketId}`; - const params = { - method: 'POST', - body: form, - headers: { - 'Cookie': secondCookie - } - }; - return fetch(ostUri, params); - } - } }; }; From 90b36bf1abb1be5c28ded97d618196a1f37ab3d3 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Tue, 20 Dec 2022 14:21:01 +0100 Subject: [PATCH 12/72] Fix missing report for the invoice electronic notification --- modules/ticket/front/descriptor-menu/index.js | 9 +++++++- .../invoice-electronic.html | 13 ++++++++++++ .../invoice-electronic/invoice-electronic.js | 21 +++++++++++++++++++ .../email/invoice-electronic/locale/en.yml | 4 ++++ .../email/invoice-electronic/locale/es.yml | 4 ++++ 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 print/templates/email/invoice-electronic/invoice-electronic.html create mode 100644 print/templates/email/invoice-electronic/invoice-electronic.js create mode 100644 print/templates/email/invoice-electronic/locale/en.yml create mode 100644 print/templates/email/invoice-electronic/locale/es.yml diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index f10e059ad..477eab9a3 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -254,8 +254,15 @@ class Controller extends Section { if (client.hasElectronicInvoice) { this.$http.post(`NotificationQueues`, { - notificationFk: 'invoiceElectronic', + notificationFk: 'invoice-electronic', authorFk: client.id, + params: JSON.stringify( + { + 'name': client.name, + 'email': client.email, + 'ticketId': this.id, + 'url': window.location.href + }) }).then(a => { this.vnApp.showSuccess(this.$t('Invoice sent')); }); diff --git a/print/templates/email/invoice-electronic/invoice-electronic.html b/print/templates/email/invoice-electronic/invoice-electronic.html new file mode 100644 index 000000000..fc96e0970 --- /dev/null +++ b/print/templates/email/invoice-electronic/invoice-electronic.html @@ -0,0 +1,13 @@ + + + + + + {{ $t('subject') }} + + +

{{ $t('title') }} {{name}}

+

{{ $t('clientMail') }} {{email}}

+

{{ $t('ticketId') }} {{ticketId}} + + diff --git a/print/templates/email/invoice-electronic/invoice-electronic.js b/print/templates/email/invoice-electronic/invoice-electronic.js new file mode 100644 index 000000000..2e1e739ac --- /dev/null +++ b/print/templates/email/invoice-electronic/invoice-electronic.js @@ -0,0 +1,21 @@ +module.exports = { + name: 'invoice-electronic', + props: { + name: { + type: [String], + required: true + }, + email: { + type: [String], + required: true + }, + ticketId: { + type: [Number], + required: true + }, + url: { + type: [String], + required: true + } + }, +}; diff --git a/print/templates/email/invoice-electronic/locale/en.yml b/print/templates/email/invoice-electronic/locale/en.yml new file mode 100644 index 000000000..5523a2fa3 --- /dev/null +++ b/print/templates/email/invoice-electronic/locale/en.yml @@ -0,0 +1,4 @@ +subject: A electronic invoice has been created +title: A new electronic invoice has been created for the client +clientMail: The client's email is +ticketId: The invoice's ticket is \ No newline at end of file diff --git a/print/templates/email/invoice-electronic/locale/es.yml b/print/templates/email/invoice-electronic/locale/es.yml new file mode 100644 index 000000000..2cbcfbb36 --- /dev/null +++ b/print/templates/email/invoice-electronic/locale/es.yml @@ -0,0 +1,4 @@ +subject: Se ha creado una factura electrónica +title: Se ha creado una nueva factura electrónica para el cliente +clientMail: El correo del cliente es +ticketId: El ticket de la factura es \ No newline at end of file From 7261415e16ce99d8a85bbaf95fc888919f2df5b5 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 21 Dec 2022 09:36:31 +0100 Subject: [PATCH 13/72] =?UTF-8?q?fix:=20a=C3=B1adido=20length?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/travel/front/extra-community/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index 3e5d5f224..ee8dcdf98 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -27,7 +27,7 @@

From 502a6925213db8b67db48cda2b29363fa4b61153 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 21 Dec 2022 12:55:11 +0100 Subject: [PATCH 14/72] test(item_fixedPrice_rate2): backTest and e2e --- .../00-priceFixed_getRate2.sql | 0 e2e/helpers/selectors.js | 4 +- e2e/paths/04-item/13_fixedPrice.spec.js | 6 +-- .../item/back/methods/fixed-price/getRate2.js | 15 ++++--- .../fixed-price/specs/getRate2.spec.js | 39 +++++++++++++++++++ 5 files changed, 54 insertions(+), 10 deletions(-) rename db/changes/{225001 => 225201}/00-priceFixed_getRate2.sql (100%) create mode 100644 modules/item/back/methods/fixed-price/specs/getRate2.spec.js diff --git a/db/changes/225001/00-priceFixed_getRate2.sql b/db/changes/225201/00-priceFixed_getRate2.sql similarity index 100% rename from db/changes/225001/00-priceFixed_getRate2.sql rename to db/changes/225201/00-priceFixed_getRate2.sql diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 9dab10673..f3fb496a0 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -415,8 +415,8 @@ export default { fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)', fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]', fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]', - fourthPPU: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)', - fourthPPP: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)', + fourthGroupingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)', + fourthPackingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)', fourthHasMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-check[ng-model="price.hasMinPrice"]', fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]', fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]', diff --git a/e2e/paths/04-item/13_fixedPrice.spec.js b/e2e/paths/04-item/13_fixedPrice.spec.js index fc7aac3d0..673705eff 100644 --- a/e2e/paths/04-item/13_fixedPrice.spec.js +++ b/e2e/paths/04-item/13_fixedPrice.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -describe('Item fixed prices path', () => { +fdescribe('Item fixed prices path', () => { let browser; let page; beforeAll(async() => { @@ -24,8 +24,8 @@ describe('Item fixed prices path', () => { it('should fill the fixed price data', async() => { const now = new Date(); await page.autocompleteSearch(selectors.itemFixedPrice.fourthWarehouse, 'Warehouse one'); - await page.write(selectors.itemFixedPrice.fourthPPU, '1'); - await page.write(selectors.itemFixedPrice.fourthPPP, '1'); + await page.writeOnEditableTD(selectors.itemFixedPrice.fourthGroupingPrice, '1'); + await page.writeOnEditableTD(selectors.itemFixedPrice.fourthPackingPrice, '1'); await page.write(selectors.itemFixedPrice.fourthMinPrice, '1'); await page.pickDate(selectors.itemFixedPrice.fourthStarted, now); await page.pickDate(selectors.itemFixedPrice.fourthEnded, now); diff --git a/modules/item/back/methods/fixed-price/getRate2.js b/modules/item/back/methods/fixed-price/getRate2.js index e1a399be6..c90a380e3 100644 --- a/modules/item/back/methods/fixed-price/getRate2.js +++ b/modules/item/back/methods/fixed-price/getRate2.js @@ -1,6 +1,6 @@ module.exports = Self => { Self.remoteMethod('getRate2', { - description: 'Return the rate2.', + description: 'Return the rate2', accessType: 'READ', accepts: [ { @@ -11,13 +11,13 @@ module.exports = Self => { }, { arg: 'rate3', - type: 'object', + type: 'number', description: `The price rate 3`, required: true } ], returns: { - type: 'number', + type: 'object', root: true }, http: { @@ -26,9 +26,14 @@ module.exports = Self => { } }); - Self.getRate2 = async(fixedPriceId, rate3) => { + Self.getRate2 = async(fixedPriceId, rate3, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + const [result] = await Self.rawSql(`SELECT vn.priceFixed_getRate2(?, ?) as rate2`, - [fixedPriceId, rate3]); + [fixedPriceId, rate3], myOptions); return result; }; }; diff --git a/modules/item/back/methods/fixed-price/specs/getRate2.spec.js b/modules/item/back/methods/fixed-price/specs/getRate2.spec.js new file mode 100644 index 000000000..2f5dd93cd --- /dev/null +++ b/modules/item/back/methods/fixed-price/specs/getRate2.spec.js @@ -0,0 +1,39 @@ +const models = require('vn-loopback/server/server').models; + +describe('getRate2()', () => { + it(`should return new rate2 if exists rate`, async() => { + const tx = await models.FixedPrice.beginTransaction({}); + + try { + const options = {transaction: tx}; + const fixedPriceId = 1; + const rate3 = 2; + const result = await models.FixedPrice.getRate2(fixedPriceId, rate3, options); + + expect(result.rate2).toEqual(1.9); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it(`should return null if not exists rate`, async() => { + const tx = await models.FixedPrice.beginTransaction({}); + + try { + const options = {transaction: tx}; + const fixedPriceId = 13; + const rate3 = 2; + const result = await models.FixedPrice.getRate2(fixedPriceId, rate3, options); + + expect(result.rate2).toEqual(null); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); From d18e360f735f4c85659efce2d25ec65b9e5e157c Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 21 Dec 2022 13:47:43 +0100 Subject: [PATCH 15/72] unfocus e2e --- e2e/paths/04-item/13_fixedPrice.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/paths/04-item/13_fixedPrice.spec.js b/e2e/paths/04-item/13_fixedPrice.spec.js index 673705eff..40ccc009e 100644 --- a/e2e/paths/04-item/13_fixedPrice.spec.js +++ b/e2e/paths/04-item/13_fixedPrice.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -fdescribe('Item fixed prices path', () => { +describe('Item fixed prices path', () => { let browser; let page; beforeAll(async() => { From 27288d011d33ca100c03579e5d9f0399fd3c8653 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 21 Dec 2022 15:01:05 +0100 Subject: [PATCH 16/72] fix(searchBar): applyParams --- ..._smartTable_searchBar_integrations.spec.js | 32 +++++++++++++++++-- front/core/components/searchbar/searchbar.js | 6 ++-- .../components/searchbar/searchbar.spec.js | 4 +-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js index 4fc280209..f5897da2c 100644 --- a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js +++ b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -describe('SmartTable SearchBar integration', () => { +fdescribe('SmartTable SearchBar integration', () => { let browser; let page; beforeAll(async() => { @@ -15,7 +15,7 @@ describe('SmartTable SearchBar integration', () => { await browser.close(); }); - describe('as filters', () => { + describe('as filters in smart-table section', () => { it('should search by type in searchBar', async() => { await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton); await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium'); @@ -47,6 +47,34 @@ describe('SmartTable SearchBar integration', () => { }); }); + describe('as filters in section without smart-table', () => { + it('go to zone section', async() => { + await page.loginAndModule('salesPerson', 'zone'); + await page.waitToClick(selectors.globalItems.searchButton); + }); + + it('should search in searchBar first time', async() => { + await page.doSearch('A'); + const count = await page.countElement(selectors.zoneIndex.searchResult); + + expect(count).toEqual(7); + }); + + it('should search in searchBar second time', async() => { + await page.doSearch('A'); + const count = await page.countElement(selectors.zoneIndex.searchResult); + + expect(count).toEqual(7); + }); + + it('should search in searchBar third time', async() => { + await page.doSearch('A'); + const count = await page.countElement(selectors.zoneIndex.searchResult); + + expect(count).toEqual(7); + }); + }); + describe('as orders', () => { it('should order by first id', async() => { await page.loginAndModule('developer', 'item'); diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index f2855d711..aefa89b5b 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -308,7 +308,7 @@ export default class Searchbar extends Component { this.tableQ = null; - const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length; + const hasParams = this.$params.q && JSON.parse(this.$params.q).tableQ; if (hasParams) { const stateFilter = JSON.parse(this.$params.q); for (let param in stateFilter) { @@ -325,8 +325,8 @@ export default class Searchbar extends Component { for (let param in stateFilter.tableQ) params[param] = stateFilter.tableQ[param]; - Object.assign(stateFilter, params); - return this.model.applyParams(params) + const newParams = Object.assign(stateFilter, params); + return this.model.applyParams(newParams) .then(() => this.model.data); } diff --git a/front/core/components/searchbar/searchbar.spec.js b/front/core/components/searchbar/searchbar.spec.js index ed8fd9d07..9998e7a7c 100644 --- a/front/core/components/searchbar/searchbar.spec.js +++ b/front/core/components/searchbar/searchbar.spec.js @@ -174,14 +174,12 @@ describe('Component vnSearchbar', () => { jest.spyOn(controller, 'doSearch'); controller.model = { refresh: jest.fn(), + applyFilter: jest.fn().mockReturnValue(Promise.resolve()), userParams: { id: 1 } }; - controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve()); - jest.spyOn(controller.model, 'applyParams'); - controller.filter = filter; controller.removeParam(0); From d97a2738a4ba9bdd46f3e7eef953a324208cf201 Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 22 Dec 2022 07:12:09 +0100 Subject: [PATCH 17/72] refs #4975 Upload method modified and addedd model --- modules/mdb/back/methods/mdbVersion/upload.js | 53 +++++++++++++------ modules/mdb/back/models/mdbVersionTree.json | 35 ++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 modules/mdb/back/models/mdbVersionTree.json diff --git a/modules/mdb/back/methods/mdbVersion/upload.js b/modules/mdb/back/methods/mdbVersion/upload.js index d165a7bb5..8c77281b1 100644 --- a/modules/mdb/back/methods/mdbVersion/upload.js +++ b/modules/mdb/back/methods/mdbVersion/upload.js @@ -12,7 +12,7 @@ module.exports = Self => { required: true, description: 'The app name' }, { - arg: 'newVersion', + arg: 'toVersion', type: 'number', required: true, description: `The new version number` @@ -21,6 +21,11 @@ module.exports = Self => { type: 'string', required: true, description: `The branch name` + }, { + arg: 'fromVersion', + type: 'string', + required: true, + description: `The old version number` } ], returns: { @@ -33,14 +38,12 @@ module.exports = Self => { } }); - Self.upload = async(ctx, appName, newVersion, branch, options) => { + Self.upload = async(ctx, appName, toVersion, branch, fromVersion, options) => { const models = Self.app.models; const myOptions = {}; - const TempContainer = models.TempContainer; const AccessContainer = models.AccessContainer; const fileOptions = {}; - let tx; if (typeof options == 'object') @@ -53,6 +56,29 @@ module.exports = Self => { let srcFile; try { + const existBranch = await models.MdbBranch.findOne({ + where: {name: branch} + }, myOptions); + + if (!existBranch) + throw new UserError('Not exist this branch'); + + let lastVersion = await models.MdbBranch.findOne({ + where: {appName} + }, myOptions); + + if (lastVersion++ != toVersion) + throw new UserError('Try again'); + + const userId = ctx.req.accessToken.userId; + models.MdbVersionTree.create({ + app: appName, + version: toVersion, + branchFk: branch, + fromVersion, + userFk: userId + }); + const tempContainer = await TempContainer.container('access'); const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); const files = Object.values(uploaded.files).map(file => { @@ -65,7 +91,7 @@ module.exports = Self => { const accessContainer = await AccessContainer.container('.archive'); const destinationFile = path.join( - accessContainer.client.root, accessContainer.name, appName, `${newVersion}.7z`); + accessContainer.client.root, accessContainer.name, appName, `${toVersion}.7z`); if (process.env.NODE_ENV == 'test') await fs.unlink(srcFile); @@ -75,18 +101,11 @@ module.exports = Self => { }); await fs.chmod(destinationFile, 0o644); - const existBranch = await models.MdbBranch.findOne({ - where: {name: branch} - }); - - if (!existBranch) - throw new UserError('Not exist this branch'); - const branchPath = path.join(accessContainer.client.root, 'branches', branch); await fs.mkdir(branchPath, {recursive: true}); const destinationBranch = path.join(branchPath, `${appName}.7z`); - const destinationRelative = `../../.archive/${appName}/${newVersion}.7z`; + const destinationRelative = `../../.archive/${appName}/${toVersion}.7z`; try { await fs.unlink(destinationBranch); } catch (e) {} @@ -94,7 +113,7 @@ module.exports = Self => { if (branch == 'master') { const destinationRoot = path.join(accessContainer.client.root, `${appName}.7z`); - const rootRelative = `./.archive/${appName}/${newVersion}.7z`; + const rootRelative = `./.archive/${appName}/${toVersion}.7z`; try { await fs.unlink(destinationRoot); } catch (e) {} @@ -105,15 +124,15 @@ module.exports = Self => { await models.MdbVersion.upsert({ app: appName, branchFk: branch, - version: newVersion - }); + version: toVersion + }, myOptions); if (tx) await tx.commit(); } catch (e) { if (tx) await tx.rollback(); if (fs.existsSync(srcFile)) - await fs.unlink(srcFile); + fs.unlink(srcFile); throw e; } diff --git a/modules/mdb/back/models/mdbVersionTree.json b/modules/mdb/back/models/mdbVersionTree.json new file mode 100644 index 000000000..8c0260e54 --- /dev/null +++ b/modules/mdb/back/models/mdbVersionTree.json @@ -0,0 +1,35 @@ +{ + "name": "MdbVersionTree", + "base": "VnModel", + "options": { + "mysql": { + "table": "mdbVersionTree" + } + }, + "properties": { + "app": { + "type": "string", + "description": "The app name", + "id": true + }, + "version": { + "type": "number" + }, + "branchFk": { + "type": "string" + }, + "fromVersion": { + "type": "number" + }, + "userFk": { + "type": "number" + } + }, + "relations": { + "branch": { + "type": "belongsTo", + "model": "MdbBranch", + "foreignKey": "branchFk" + } + } +} \ No newline at end of file From 5a25b0ef4c85b2ec0a5d80e1513fb7d927cc78f9 Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 22 Dec 2022 07:12:44 +0100 Subject: [PATCH 18/72] refs #4975 Added model MdbVersionTree --- modules/mdb/back/model-config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/mdb/back/model-config.json b/modules/mdb/back/model-config.json index d5be8de87..1384aaf76 100644 --- a/modules/mdb/back/model-config.json +++ b/modules/mdb/back/model-config.json @@ -5,6 +5,9 @@ "MdbVersion": { "dataSource": "vn" }, + "MdbVersionTree": { + "dataSource": "vn" + }, "AccessContainer": { "dataSource": "accessStorage" } From 05f4db7924d85b753d8e33ae3d56b922a06b9544 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 22 Dec 2022 07:16:22 +0100 Subject: [PATCH 19/72] Add requested changes to the fixtures --- db/changes/224903/00-insert_notification_invoiceE.sql | 1 - db/changes/225001/.gitkeep | 0 db/changes/225001/00-insert_notification_invoiceE.sql | 1 + 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 db/changes/224903/00-insert_notification_invoiceE.sql delete mode 100644 db/changes/225001/.gitkeep create mode 100644 db/changes/225001/00-insert_notification_invoiceE.sql diff --git a/db/changes/224903/00-insert_notification_invoiceE.sql b/db/changes/224903/00-insert_notification_invoiceE.sql deleted file mode 100644 index 1d416c196..000000000 --- a/db/changes/224903/00-insert_notification_invoiceE.sql +++ /dev/null @@ -1 +0,0 @@ -insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoiceElectronic', 'A electronic invoice has been generated'); \ No newline at end of file diff --git a/db/changes/225001/.gitkeep b/db/changes/225001/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/db/changes/225001/00-insert_notification_invoiceE.sql b/db/changes/225001/00-insert_notification_invoiceE.sql new file mode 100644 index 000000000..bbce9dd01 --- /dev/null +++ b/db/changes/225001/00-insert_notification_invoiceE.sql @@ -0,0 +1 @@ +insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoice-electronic', 'A electronic invoice has been generated'); \ No newline at end of file From 186a64f7fa8676ee6f1942ac39d29ee44c4596f0 Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 22 Dec 2022 07:46:06 +0100 Subject: [PATCH 20/72] unfocus --- e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js index f5897da2c..ad558ace2 100644 --- a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js +++ b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -fdescribe('SmartTable SearchBar integration', () => { +describe('SmartTable SearchBar integration', () => { let browser; let page; beforeAll(async() => { From fce035298685d4388648f9cdf9e8cc3f5c3dcd67 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 22 Dec 2022 10:41:07 +0100 Subject: [PATCH 21/72] add fixtures --- db/dump/fixtures.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 92b84be43..2fd4174ea 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2685,6 +2685,7 @@ INSERT INTO `util`.`notificationConfig` INSERT INTO `util`.`notification` (`id`, `name`, `description`) VALUES (1, 'print-email', 'notification fixture one'); + (2, 'invoice-electronic', 'A electronic invoice has been generated') INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) VALUES From 93c98e05fc3bc4c0931519777d6b97caa1861045 Mon Sep 17 00:00:00 2001 From: joan Date: Fri, 23 Dec 2022 08:27:03 +0100 Subject: [PATCH 22/72] fix(discount): handle error for a worker without business contract --- modules/ticket/back/methods/sale/usesMana.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ticket/back/methods/sale/usesMana.js b/modules/ticket/back/methods/sale/usesMana.js index 093057dca..3f55293bf 100644 --- a/modules/ticket/back/methods/sale/usesMana.js +++ b/modules/ticket/back/methods/sale/usesMana.js @@ -24,6 +24,8 @@ module.exports = Self => { const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); const workerDepartment = await models.WorkerDepartment.findById(userId, null, myOptions); + if (!workerDepartment) return false; + const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); return usesMana ? true : false; From c98b5a969d82a7f8a761734fbc89a6bddadb3f07 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 23 Dec 2022 09:16:26 +0100 Subject: [PATCH 23/72] feat: actualizada consulta intrastat --- db/changes/224903/00-invoiceOut_new.sql | 225 ++++++++++++++++++ print/templates/reports/invoice/invoice.js | 2 +- .../reports/invoice/sql/intrastat.sql | 59 ++--- 3 files changed, 249 insertions(+), 37 deletions(-) create mode 100644 db/changes/224903/00-invoiceOut_new.sql diff --git a/db/changes/224903/00-invoiceOut_new.sql b/db/changes/224903/00-invoiceOut_new.sql new file mode 100644 index 000000000..10a42d40d --- /dev/null +++ b/db/changes/224903/00-invoiceOut_new.sql @@ -0,0 +1,225 @@ +DROP PROCEDURE IF EXISTS `vn`.`invoiceOut_new`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`( + vSerial VARCHAR(255), + vInvoiceDate DATETIME, + vTaxArea VARCHAR(25), + OUT vNewInvoiceId INT) +BEGIN +/** + * Creación de facturas emitidas. + * requiere previamente tabla ticketToInvoice(id). + * + * @param vSerial serie a la cual se hace la factura + * @param vInvoiceDate fecha de la factura + * @param vTaxArea tipo de iva en relacion a la empresa y al cliente + * @param vNewInvoiceId id de la factura que se acaba de generar + * @return vNewInvoiceId + */ + DECLARE vSpainCountryCode INT DEFAULT 1; + DECLARE vIsAnySaleToInvoice BOOL; + DECLARE vIsAnyServiceToInvoice BOOL; + DECLARE vNewRef VARCHAR(255); + DECLARE vWorker INT DEFAULT account.myUser_getId(); + DECLARE vCompany INT; + DECLARE vSupplier INT; + DECLARE vClient INT; + DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1; + DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6; + DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2; + DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R'; + DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S'; + DECLARE vNewInvoiceInId INT; + DECLARE vIsInterCompany BOOL; + + SET vInvoiceDate = IFNULL(vInvoiceDate,CURDATE()); + + SELECT t.clientFk, t.companyFk + INTO vClient, vCompany + FROM ticketToInvoice tt + JOIN ticket t ON t.id = tt.id + LIMIT 1; + + -- Eliminem de ticketToInvoice els tickets que no han de ser facturats + DELETE ti.* + FROM ticketToInvoice ti + JOIN ticket t ON t.id = ti.id + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN supplier su ON su.id = t.companyFk + JOIN client c ON c.id = t.clientFk + LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk + WHERE YEAR(t.shipped) < 2001 + OR c.isTaxDataChecked = FALSE + OR t.isDeleted + OR c.hasToInvoice = FALSE + OR itc.id IS NULL; + + SELECT SUM(s.quantity * s.price * (100 - s.discount)/100), ts.id + INTO vIsAnySaleToInvoice, vIsAnyServiceToInvoice + FROM ticketToInvoice t + LEFT JOIN sale s ON s.ticketFk = t.id + LEFT JOIN ticketService ts ON ts.ticketFk = t.id; + + IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice) + AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase()) + THEN + + -- el trigger añade el siguiente Id_Factura correspondiente a la vSerial + INSERT INTO invoiceOut + ( + ref, + serial, + issued, + clientFk, + dued, + companyFk, + cplusInvoiceType477Fk + ) + SELECT + 1, + vSerial, + vInvoiceDate, + vClient, + getDueDate(vInvoiceDate, dueDay), + vCompany, + IF(vSerial = vCorrectingSerial, + vCplusCorrectingInvoiceTypeFk, + IF(vSerial = vSimplifiedSerial, + vCplusSimplifiedInvoiceTypeFk, + vCplusStandardInvoiceTypeFk)) + FROM client + WHERE id = vClient; + + + SET vNewInvoiceId = LAST_INSERT_ID(); + + SELECT `ref` + INTO vNewRef + FROM invoiceOut + WHERE id = vNewInvoiceId; + + UPDATE ticket t + JOIN ticketToInvoice ti ON ti.id = t.id + SET t.refFk = vNewRef; + + DROP TEMPORARY TABLE IF EXISTS tmp.updateInter; + CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY + SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador + FROM ticketToInvoice ti + LEFT JOIN ticketState ts ON ti.id = ts.ticket + JOIN state s + WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id); + + INSERT INTO vncontrol.inter(state_id,Id_Ticket,Id_Trabajador) + SELECT * FROM tmp.updateInter; + + INSERT INTO ticketLog (action, userFk, originFk, description) + SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef) + FROM ticketToInvoice ti; + + CALL invoiceExpenceMake(vNewInvoiceId); + CALL invoiceTaxMake(vNewInvoiceId,vTaxArea); + + UPDATE invoiceOut io + JOIN ( + SELECT SUM(amount) AS total + FROM invoiceOutExpence + WHERE invoiceOutFk = vNewInvoiceId + ) base + JOIN ( + SELECT SUM(vat) AS total + FROM invoiceOutTax + WHERE invoiceOutFk = vNewInvoiceId + ) vat + SET io.amount = base.total + vat.total + WHERE io.id = vNewInvoiceId; + + DROP TEMPORARY TABLE tmp.updateInter; + + SELECT ios.isCEE INTO vIsInterCompany + FROM vn.ticket t + JOIN vn.invoiceOut io ON io.`ref` = t.refFk + JOIN vn.invoiceOutSerial ios ON ios.code = io.serial + WHERE t.refFk = vNewRef + LIMIT 1; + + IF (vIsInterCompany) THEN + + SELECT vCompany INTO vSupplier; + SELECT id INTO vCompany FROM company WHERE clientFk = vClient; + + INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk) + SELECT vSupplier, vNewRef, vInvoiceDate, vCompany; + + SET vNewInvoiceInId = LAST_INSERT_ID(); + + DROP TEMPORARY TABLE IF EXISTS tmp.ticket; + CREATE TEMPORARY TABLE tmp.ticket + (KEY (ticketFk)) + ENGINE = MEMORY + SELECT id ticketFk + FROM ticketToInvoice; + + CALL `ticket_getTax`('NATIONAL'); + + SET @vTaxableBaseServices := 0.00; + SET @vTaxCodeGeneral := NULL; + + INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) + SELECT vNewInvoiceInId, @vTaxableBaseServices, sub.expenceFk, sub.taxTypeSageFk , sub.transactionTypeSageFk + FROM ( + SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk, @vTaxCodeGeneral := i.taxClassCodeFk + FROM tmp.ticketServiceTax tst + JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code + WHERE i.isService + HAVING taxableBase + ) sub; + + INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) + SELECT vNewInvoiceInId, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, @vTaxableBaseServices, 0) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk + FROM tmp.ticketTax tt + JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code + WHERE !i.isService + GROUP BY tt.pgcFk + HAVING taxableBase + ORDER BY tt.priority; + + CALL invoiceInDueDay_calculate(vNewInvoiceInId); + + INSERT INTO invoiceInIntrastat ( + invoiceInFk, + intrastatFk, + amount, + stems, + countryFk, + net) + SELECT + vNewInvoiceInId invoiceInFk, + i.intrastatFk, + CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal, + CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, + su.countryFk, + CAST(SUM(IFNULL(i.stems, 1) + * s.quantity + * IF(ic.grams, ic.grams, i.weightByPiece) / 1000) AS DECIMAL(10,2)) netKg + FROM sale s + JOIN ticket t ON s.ticketFk = t.id + JOIN supplier su ON su.id = t.companyFk + JOIN item i ON i.id = s.itemFk + JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk + JOIN intrastat ir ON ir.id = i.intrastatFk + WHERE t.refFk = vNewRef; + + DROP TEMPORARY TABLE tmp.ticket; + DROP TEMPORARY TABLE tmp.ticketAmount; + DROP TEMPORARY TABLE tmp.ticketTax; + DROP TEMPORARY TABLE tmp.ticketServiceTax; + + END IF; + + END IF; + + DROP TEMPORARY TABLE `ticketToInvoice`; +END$$ +DELIMITER ; diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index 48848c079..f7011ad81 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -82,7 +82,7 @@ module.exports = { return this.rawSqlFromDef(`taxes`, [reference]); }, fetchIntrastat(reference) { - return this.rawSqlFromDef(`intrastat`, [reference, reference, reference, reference, reference]); + return this.rawSqlFromDef(`intrastat`, [reference, reference, reference]); }, fetchRectified(reference) { return this.rawSqlFromDef(`rectified`, [reference]); diff --git a/print/templates/reports/invoice/sql/intrastat.sql b/print/templates/reports/invoice/sql/intrastat.sql index 7f5fbdf39..f986a9564 100644 --- a/print/templates/reports/invoice/sql/intrastat.sql +++ b/print/templates/reports/invoice/sql/intrastat.sql @@ -1,39 +1,26 @@ SELECT * FROM invoiceOut io JOIN invoiceOutSerial ios ON io.serial = ios.code - JOIN - (SELECT - t.refFk, - ir.id code, - ir.description description, - CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, - CAST(SUM(CAST(IFNULL(i.stems, 1) * s.quantity * IF(ic.grams, ic.grams, i.density * ic.cm3delivery / 1000) / 1000 AS DECIMAL(10,2)) * - IF(sub.weight, sub.weight / vn.invoiceOut_getWeight(?), 1)) AS DECIMAL(10,2)) netKg, - CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal - FROM vn.ticket t - JOIN vn.sale s ON s.ticketFk = t.id - JOIN vn.item i ON i.id = s.itemFk - JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk - JOIN vn.intrastat ir ON ir.id = i.intrastatFk - LEFT JOIN ( - SELECT t2.weight - FROM vn.ticket t2 - WHERE refFk = ? AND weight - LIMIT 1 - ) sub ON TRUE - WHERE t.refFk = ? - AND i.intrastatFk - GROUP BY i.intrastatFk - UNION ALL - SELECT - NULL AS refFk, - NULL AS code, - NULL AS description, - 0 AS stems, - 0 AS netKg, - IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) AS subtotal - FROM vn.ticketService ts - JOIN vn.ticket t ON ts.ticketFk = t.id - WHERE t.refFk = ?) sub - WHERE io.`ref` = ? AND ios.isCEE - ORDER BY sub.code; + JOIN( + SELECT ir.id code, + ir.description, + iii.stems, + iii.net netKg, + iii.amount subtotal + FROM vn.invoiceInIntrastat iii + LEFT JOIN vn.invoiceIn ii ON ii.id = iii.invoiceInFk + LEFT JOIN vn.invoiceOut io ON io.ref = ii.supplierRef + LEFT JOIN vn.intrastat ir ON ir.id = iii.intrastatFk + WHERE io.`ref` = ? + UNION ALL + SELECT NULL code, + 'Servicios' description, + 0 stems, + 0 netKg, + IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) subtotal + FROM vn.ticketService ts + JOIN vn.ticket t ON ts.ticketFk = t.id + WHERE t.refFk = ? + ) sub + WHERE io.ref = ? AND ios.isCEE + ORDER BY sub.code; From 9468f604fff620a36d43279b74a5b02e6c88cccb Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 23 Dec 2022 10:02:21 +0100 Subject: [PATCH 24/72] move sql changes --- db/changes/{224903 => 225201}/00-invoiceOut_new.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename db/changes/{224903 => 225201}/00-invoiceOut_new.sql (100%) diff --git a/db/changes/224903/00-invoiceOut_new.sql b/db/changes/225201/00-invoiceOut_new.sql similarity index 100% rename from db/changes/224903/00-invoiceOut_new.sql rename to db/changes/225201/00-invoiceOut_new.sql From 29e66157ea92c9e3187e2e6d5af5e15b0670bdff Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 23 Dec 2022 12:29:28 +0100 Subject: [PATCH 25/72] fix: se excluyen los test de back que fallan intermitente --- .../back/methods/invoiceOut/specs/downloadZip.spec.js | 2 ++ .../back/methods/worker-time-control/specs/timeEntry.spec.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js index 4d1833635..3ebcf8344 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js @@ -31,6 +31,8 @@ describe('InvoiceOut downloadZip()', () => { }); it('should return an error if the size of the files is too large', async() => { + pending('https://redmine.verdnatura.es/issues/5035'); + const tx = await models.InvoiceOut.beginTransaction({}); let error; diff --git a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js index 7f652519b..2fc5c60d4 100644 --- a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js @@ -309,6 +309,8 @@ describe('workerTimeControl add/delete timeEntry()', () => { }); it('Should throw an error when trying "out" before closing a "middle" couple', async() => { + pending('https://redmine.verdnatura.es/issues/5035'); + activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; From 762e39f7be6321171dd83d3610fd5c1498c7bb2b Mon Sep 17 00:00:00 2001 From: alexandre Date: Fri, 23 Dec 2022 15:10:35 +0100 Subject: [PATCH 26/72] refs #4866 refactor component instance-log --- .../salix/components/instance-log/index.html | 67 ++------------ front/salix/components/instance-log/index.js | 89 +------------------ .../components/instance-log/locale/es.yml | 15 ---- .../salix/components/instance-log/style.scss | 24 ++--- 4 files changed, 16 insertions(+), 179 deletions(-) delete mode 100644 front/salix/components/instance-log/locale/es.yml diff --git a/front/salix/components/instance-log/index.html b/front/salix/components/instance-log/index.html index 56c0ab773..354e81080 100644 --- a/front/salix/components/instance-log/index.html +++ b/front/salix/components/instance-log/index.html @@ -2,68 +2,11 @@ - - - - - - - Date - User - Action - Changes - - - - - - {{::log.creationDate | date:'dd/MM/yyyy HH:mm'}} - - - {{::log.user.name || 'System' | translate}} - - - - {{::$ctrl.actionsText[log.action]}} - - - - - - - - - - - - - - - - - -
FieldBeforeAfter
{{prop.name}}{{::$ctrl.formatValue(prop.old)}}{{::$ctrl.formatValue(prop.new)}}
- -
- {{::log.description}} -
-
-
-
-
-
- -
+ origin-id="$ctrl.originId" + changed-model="$ctrl.changedModel" + changed-model-id="$ctrl.changedModelId"> +
- - diff --git a/front/salix/components/instance-log/index.js b/front/salix/components/instance-log/index.js index 4c17cde0b..6d8497c2d 100644 --- a/front/salix/components/instance-log/index.js +++ b/front/salix/components/instance-log/index.js @@ -1,96 +1,11 @@ import ngModule from '../../module'; -import './style.scss'; import Section from '../section'; +import './style.scss'; export default class Controller extends Section { - constructor($element, $) { - super($element, $); - this.actionsText = { - 'insert': 'Creates', - 'update': 'Updates', - 'delete': 'Deletes', - 'select': 'Views' - }; ``; - } - open() { - this.filter = { - where: - {changedModel: this.changedModel, - changedModelId: this.changedModelId}, - include: [{ - relation: 'user', - scope: { - fields: ['name'], - include: { - relation: 'worker', - scope: { - fields: ['id'] - } - } - }, - }], - }; this.$.instanceLog.show(); } - - get logs() { - return this._logs; - } - - set logs(value) { - this._logs = value; - if (!this.logs) return; - const validations = window.validations; - for (const log of value) { - const locale = validations[log.changedModel] && validations[log.changedModel].locale - ? validations[log.changedModel].locale : {}; - log.oldProperties = this.getInstance(log.oldInstance, locale); - log.newProperties = this.getInstance(log.newInstance, locale); - let props = [].concat(log.oldProperties.map(p => p.key), log.newProperties.map(p => p.key)); - props = [...new Set(props)]; - log.props = []; - for (const prop of props) { - const matchOldProp = log.oldProperties.find(p => p.key === prop); - const matchNewProp = log.newProperties.find(p => p.key === prop); - log.props.push({ - name: prop, - old: matchOldProp ? matchOldProp.value : null, - new: matchNewProp ? matchNewProp.value : null, - }); - } - } - } - - formatValue(value) { - switch (typeof value) { - case 'boolean': - return value ? '✓' : '✗'; - default: - return value; - } - } - - getInstance(instance, locale) { - const properties = []; - let validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/; - - if (typeof instance == 'object' && instance != null) { - Object.keys(instance).forEach(property => { - if (validDate.test(instance[property])) - instance[property] = new Date(instance[property]).toLocaleString('es-ES'); - const key = locale[property] || property; - properties.push({key, value: instance[property]}); - }); - return properties; - } - return null; - } - - showWorkerDescriptor(event, workerId) { - if (!workerId) return; - this.$.workerDescriptor.show(event.target, workerId); - } } ngModule.vnComponent('vnInstanceLog', { @@ -99,8 +14,8 @@ ngModule.vnComponent('vnInstanceLog', { bindings: { model: '<', originId: '<', - changedModel: '<', changedModelId: '<', + changedModel: '@', url: '@' } }); diff --git a/front/salix/components/instance-log/locale/es.yml b/front/salix/components/instance-log/locale/es.yml deleted file mode 100644 index d341095d8..000000000 --- a/front/salix/components/instance-log/locale/es.yml +++ /dev/null @@ -1,15 +0,0 @@ -Date: Fecha -Model: Modelo -Action: Acción -Author: Autor -Before: Antes -After: Despues -History: Historial -Name: Nombre -Creates: Crea -Updates: Actualiza -Deletes: Elimina -Views: Visualiza -System: Sistema -note: nota -Changes: Cambios diff --git a/front/salix/components/instance-log/style.scss b/front/salix/components/instance-log/style.scss index 26a3a1bb4..70cbc52dd 100644 --- a/front/salix/components/instance-log/style.scss +++ b/front/salix/components/instance-log/style.scss @@ -1,18 +1,12 @@ -.vn-dialog .window { - width: max-content; - vn-td { - vertical-align: initial; - } - .attributes { - width: 100%; - tr { - height: 10px; - & > td { - width: 33%; - } - & > td.field, - & > th.field { - color: gray; +.vn-dialog { + & > .window:not(:has(.empty-rows)) { + width:60%; + vn-log { + vn-card { + visibility: hidden; + & > * { + visibility: visible; + } } } } From 8696d554d66a9f989c9ea79034ebc3bfda1fdfb2 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Mon, 26 Dec 2022 07:22:59 +0100 Subject: [PATCH 27/72] Change SQL folder --- db/changes/{225001 => 225002}/00-insert_notification_invoiceE.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename db/changes/{225001 => 225002}/00-insert_notification_invoiceE.sql (100%) diff --git a/db/changes/225001/00-insert_notification_invoiceE.sql b/db/changes/225002/00-insert_notification_invoiceE.sql similarity index 100% rename from db/changes/225001/00-insert_notification_invoiceE.sql rename to db/changes/225002/00-insert_notification_invoiceE.sql From 7aeeddcf1813a65eca3b0cfc21e324bd1e923e4f Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Tue, 27 Dec 2022 07:23:15 +0100 Subject: [PATCH 28/72] removed the sql --- db/changes/225002/00-insert_notification_invoiceE.sql | 1 - 1 file changed, 1 deletion(-) delete mode 100644 db/changes/225002/00-insert_notification_invoiceE.sql diff --git a/db/changes/225002/00-insert_notification_invoiceE.sql b/db/changes/225002/00-insert_notification_invoiceE.sql deleted file mode 100644 index bbce9dd01..000000000 --- a/db/changes/225002/00-insert_notification_invoiceE.sql +++ /dev/null @@ -1 +0,0 @@ -insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoice-electronic', 'A electronic invoice has been generated'); \ No newline at end of file From deb730aae7d6e41dd1cd8ff973bb9e54ac709d43 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Tue, 27 Dec 2022 09:21:02 +0100 Subject: [PATCH 29/72] solve fixture error --- db/dump/fixtures.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 2fd4174ea..2b4dd4b09 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2684,8 +2684,8 @@ INSERT INTO `util`.`notificationConfig` INSERT INTO `util`.`notification` (`id`, `name`, `description`) VALUES - (1, 'print-email', 'notification fixture one'); - (2, 'invoice-electronic', 'A electronic invoice has been generated') + (1, 'print-email', 'notification fixture one'), + (2, 'invoice-electronic', 'A electronic invoice has been generated'); INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) VALUES From 3b717e54fabbc5225edd4bddcf553c9934d9bf8f Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 28 Dec 2022 13:06:00 +0100 Subject: [PATCH 30/72] refs #3571 refactor e2e worker --- e2e/paths/03-worker/01_summary.spec.js | 64 ++++----------- e2e/paths/03-worker/02_basicData.spec.js | 38 ++++----- e2e/paths/03-worker/03_pbx.spec.js | 15 ++-- e2e/paths/03-worker/04_time_control.spec.js | 85 +++++++------------- e2e/paths/03-worker/05_calendar.spec.js | 86 ++++++++------------- 5 files changed, 97 insertions(+), 191 deletions(-) diff --git a/e2e/paths/03-worker/01_summary.spec.js b/e2e/paths/03-worker/01_summary.spec.js index 4ea87481a..4e5b0cfa9 100644 --- a/e2e/paths/03-worker/01_summary.spec.js +++ b/e2e/paths/03-worker/01_summary.spec.js @@ -2,68 +2,32 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; describe('Worker summary path', () => { + const workerId = 3; let browser; let page; beforeAll(async() => { browser = await getBrowser(); page = browser.page; await page.loginAndModule('employee', 'worker'); + const httpDataResponse = page.waitForResponse(response => { + return response.status() === 200 && response.url().includes(`Workers/${workerId}`); + }); await page.accessToSearchResult('agencyNick'); + await httpDataResponse; }); afterAll(async() => { await browser.close(); }); - it('should reach the employee summary section', async() => { - await page.waitForState('worker.card.summary'); - }); - - it('should check the summary contains the name and userName on the header', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.header, 'innerText'); - - expect(result).toEqual('agency agency'); - }); - - it('should check the summary contains the basic data id', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.id, 'innerText'); - - expect(result).toEqual('3'); - }); - - it('should check the summary contains the basic data email', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.email, 'innerText'); - - expect(result).toEqual('agency@verdnatura.es'); - }); - - it('should check the summary contains the basic data department', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.department, 'innerText'); - - expect(result).toEqual('CAMARA'); - }); - - it('should check the summary contains the user data id', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.userId, 'innerText'); - - expect(result).toEqual('3'); - }); - - it('should check the summary contains the user data name', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.userName, 'innerText'); - - expect(result).toEqual('agency'); - }); - - it('should check the summary contains the user data role', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.role, 'innerText'); - - expect(result).toEqual('agency'); - }); - - it('should check the summary contains the user data extension', async() => { - const result = await page.waitToGetProperty(selectors.workerSummary.extension, 'innerText'); - - expect(result).toEqual('1101'); + it('should reach the employee summary section and check all properties', async() => { + expect(await page.getProperty(selectors.workerSummary.header, 'innerText')).toEqual('agency agency'); + expect(await page.getProperty(selectors.workerSummary.id, 'innerText')).toEqual('3'); + expect(await page.getProperty(selectors.workerSummary.email, 'innerText')).toEqual('agency@verdnatura.es'); + expect(await page.getProperty(selectors.workerSummary.department, 'innerText')).toEqual('CAMARA'); + expect(await page.getProperty(selectors.workerSummary.userId, 'innerText')).toEqual('3'); + expect(await page.getProperty(selectors.workerSummary.userName, 'innerText')).toEqual('agency'); + expect(await page.getProperty(selectors.workerSummary.role, 'innerText')).toEqual('agency'); + expect(await page.getProperty(selectors.workerSummary.extension, 'innerText')).toEqual('1101'); }); }); diff --git a/e2e/paths/03-worker/02_basicData.spec.js b/e2e/paths/03-worker/02_basicData.spec.js index c367c8706..66a597dd1 100644 --- a/e2e/paths/03-worker/02_basicData.spec.js +++ b/e2e/paths/03-worker/02_basicData.spec.js @@ -2,13 +2,18 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; describe('Worker basic data path', () => { + const workerId = 1106; let browser; let page; beforeAll(async() => { browser = await getBrowser(); page = browser.page; await page.loginAndModule('hr', 'worker'); + const httpDataResponse = page.waitForResponse(response => { + return response.status() === 200 && response.url().includes(`Workers/${workerId}`); + }); await page.accessToSearchResult('David Charles Haller'); + await httpDataResponse; await page.accessToSection('worker.card.basicData'); }); @@ -16,35 +21,20 @@ describe('Worker basic data path', () => { await browser.close(); }); - it('should edit the form', async() => { - await page.clearInput(selectors.workerBasicData.name); - await page.write(selectors.workerBasicData.name, 'David C.'); - await page.clearInput(selectors.workerBasicData.surname); - await page.write(selectors.workerBasicData.surname, 'H.'); - await page.clearInput(selectors.workerBasicData.phone); - await page.write(selectors.workerBasicData.phone, '444332211'); - await page.waitToClick(selectors.workerBasicData.saveButton); + it('should edit the form and then reload the section and check the data was edited', async() => { + await page.overwrite(selectors.workerBasicData.name, 'David C.'); + await page.overwrite(selectors.workerBasicData.surname, 'H.'); + await page.overwrite(selectors.workerBasicData.phone, '444332211'); + await page.click(selectors.workerBasicData.saveButton); + const message = await page.waitForSnackbar(); expect(message.text).toContain('Data saved!'); - }); - it('should reload the section then check the name was edited', async() => { await page.reloadSection('worker.card.basicData'); - const result = await page.waitToGetProperty(selectors.workerBasicData.name, 'value'); - expect(result).toEqual('David C.'); - }); - - it('should the surname was edited', async() => { - const result = await page.waitToGetProperty(selectors.workerBasicData.surname, 'value'); - - expect(result).toEqual('H.'); - }); - - it('should the phone was edited', async() => { - const result = await page.waitToGetProperty(selectors.workerBasicData.phone, 'value'); - - expect(result).toEqual('444332211'); + expect(await page.waitToGetProperty(selectors.workerBasicData.name, 'value')).toEqual('David C.'); + expect(await page.waitToGetProperty(selectors.workerBasicData.surname, 'value')).toEqual('H.'); + expect(await page.waitToGetProperty(selectors.workerBasicData.phone, 'value')).toEqual('444332211'); }); }); diff --git a/e2e/paths/03-worker/03_pbx.spec.js b/e2e/paths/03-worker/03_pbx.spec.js index f5d2711d1..0e8003c47 100644 --- a/e2e/paths/03-worker/03_pbx.spec.js +++ b/e2e/paths/03-worker/03_pbx.spec.js @@ -16,19 +16,16 @@ describe('Worker pbx path', () => { await browser.close(); }); - it('should receive an error when the extension exceeds 4 characters', async() => { + it('should receive an error when the extension exceeds 4 characters and then sucessfully save the changes', async() => { await page.write(selectors.workerPbx.extension, '55555'); - await page.waitToClick(selectors.workerPbx.saveButton); - const message = await page.waitForSnackbar(); + await page.click(selectors.workerPbx.saveButton); + let message = await page.waitForSnackbar(); expect(message.text).toContain('Extension format is invalid'); - }); - it('should sucessfully save the changes', async() => { - await page.clearInput(selectors.workerPbx.extension); - await page.write(selectors.workerPbx.extension, '4444'); - await page.waitToClick(selectors.workerPbx.saveButton); - const message = await page.waitForSnackbar(); + await page.overwrite(selectors.workerPbx.extension, '4444'); + await page.click(selectors.workerPbx.saveButton); + message = await page.waitForSnackbar(); expect(message.text).toContain('Data saved! User must access web'); }); diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js index be8df3cf0..1222e7482 100644 --- a/e2e/paths/03-worker/04_time_control.spec.js +++ b/e2e/paths/03-worker/04_time_control.spec.js @@ -2,7 +2,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -describe('Worker time control path', () => { +fdescribe('Worker time control path', () => { let browser; let page; beforeAll(async() => { @@ -21,95 +21,70 @@ describe('Worker time control path', () => { const fourPm = '16:00'; const hankPymId = 1107; - it('should go to the next month', async() => { - const date = new Date(); + it(`should go to the next month, go to current month, go 1 month in the past, return error when insert 'out' of first entry, then insert 'in' monday, then insert 'out' monday, then check if Hank Pym worked 8:20 hours, remove first entry of monday, be the 'out' the first entry of monday and change week of month`, async() => { + let date = new Date(); date.setMonth(date.getMonth() + 1); - const month = date.toLocaleString('default', {month: 'long'}); + let month = date.toLocaleString('default', {month: 'long'}); - await page.waitToClick(selectors.workerTimeControl.nextMonthButton); - const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText'); + await page.click(selectors.workerTimeControl.nextMonthButton); - expect(result).toContain(month); - }); + expect(await page.getProperty(selectors.workerTimeControl.monthName, 'innerText')).toContain(month); - it('should go to current month', async() => { - const date = new Date(); - const month = date.toLocaleString('default', {month: 'long'}); + date = new Date(); + month = date.toLocaleString('default', {month: 'long'}); - await page.waitToClick(selectors.workerTimeControl.previousMonthButton); - const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText'); + await page.click(selectors.workerTimeControl.previousMonthButton); - expect(result).toContain(month); - }); + expect(await page.getProperty(selectors.workerTimeControl.monthName, 'innerText')).toContain(month); - it('should go 1 month in the past', async() => { - const date = new Date(); + date = new Date(); date.setMonth(date.getMonth() - 1); + month = date.toLocaleString('default', {month: 'long'}); const timestamp = Math.round(date.getTime() / 1000); - const month = date.toLocaleString('default', {month: 'long'}); - await page.loginAndModule('salesBoss', 'worker'); await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`); - await page.waitToClick(selectors.workerTimeControl.secondWeekDay); + await page.click(selectors.workerTimeControl.secondWeekDay); - const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText'); + expect(await page.getProperty(selectors.workerTimeControl.monthName, 'innerText')).toContain(month); - expect(result).toContain(month); - }); - - it(`should return error when insert 'out' of first entry`, async() => { - await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); + await page.click(selectors.workerTimeControl.mondayAddTimeButton); await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm); await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out'); await page.respondToDialog('accept'); - const message = await page.waitForSnackbar(); + let message = await page.waitForSnackbar(); expect(message.text).toBeDefined(); - }); - it(`should insert 'in' monday`, async() => { - await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); + await page.click(selectors.workerTimeControl.mondayAddTimeButton); await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm); await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in'); await page.respondToDialog('accept'); - const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); - expect(result).toEqual(eightAm); - }); + expect(await page.getProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText')).toEqual(eightAm); - it(`should insert 'out' monday`, async() => { - await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); + await page.click(selectors.workerTimeControl.mondayAddTimeButton); await page.pickTime(selectors.workerTimeControl.dialogTimeInput, fourPm); await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out'); await page.respondToDialog('accept'); - const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText'); - expect(result).toEqual(fourPm); - }); + expect(await page.getProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText')).toEqual(fourPm); - it(`should check Hank Pym worked 8:20 hours`, async() => { - await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '08:20 h.'); - await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '08:20 h.'); - }); - - it('should remove first entry of monday', async() => { - await page.waitForTextInElement(selectors.workerTimeControl.firstEntryOfMonday, eightAm); - await page.waitForTextInElement(selectors.workerTimeControl.secondEntryOfMonday, fourPm); - await page.waitToClick(selectors.workerTimeControl.firstEntryOfMondayDelete); + expect(await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText')).toBe('08:20 h.'); + expect(await page.getProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText')).toBe('08:20 h.'); + expect(await page.getProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText')).toBe(eightAm); + expect(await page.getProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText')).toBe(fourPm); + await page.click(selectors.workerTimeControl.firstEntryOfMondayDelete); await page.respondToDialog('accept'); - const message = await page.waitForSnackbar(); + message = await page.waitForSnackbar(); expect(message.text).toContain('Entry removed'); - }); - it(`should be the 'out' the first entry of monday`, async() => { - const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); + const result = await page.getProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); expect(result).toEqual(fourPm); - }); - it('should change week of month', async() => { - await page.waitToClick(selectors.workerTimeControl.thrirdWeekDay); - await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '00:00 h.'); + await page.click(selectors.workerTimeControl.thrirdWeekDay); + + expect(await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText')).toBe('00:00 h.'); }); }); diff --git a/e2e/paths/03-worker/05_calendar.spec.js b/e2e/paths/03-worker/05_calendar.spec.js index e97b7fe7c..c310baf5a 100644 --- a/e2e/paths/03-worker/05_calendar.spec.js +++ b/e2e/paths/03-worker/05_calendar.spec.js @@ -1,16 +1,25 @@ +/* eslint-disable max-len */ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; describe('Worker calendar path', () => { - let reasonableTimeBetweenClicks = 400; + const reasonableTimeBetweenClicks = 300; + const date = new Date(); + const lastYear = (date.getFullYear() - 1).toString(); + let browser; let page; + + async function accessAs(user) { + await page.loginAndModule(user, 'worker'); + await page.accessToSearchResult('Charles Xavier'); + await page.accessToSection('worker.card.calendar'); + } + beforeAll(async() => { browser = await getBrowser(); page = browser.page; - await page.loginAndModule('hr', 'worker'); - await page.accessToSearchResult('Charles Xavier'); - await page.accessToSection('worker.card.calendar'); + accessAs('hr'); }); afterAll(async() => { @@ -21,48 +30,40 @@ describe('Worker calendar path', () => { it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => { await page.waitToClick(selectors.workerCalendar.holidays); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); + await page.click(selectors.workerCalendar.penultimateMondayOfJanuary); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.absence); + await page.click(selectors.workerCalendar.absence); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch); + await page.click(selectors.workerCalendar.lastMondayOfMarch); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.halfHoliday); + await page.click(selectors.workerCalendar.halfHoliday); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.fistMondayOfMay); + await page.click(selectors.workerCalendar.fistMondayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.furlough); + await page.click(selectors.workerCalendar.furlough); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay); + await page.click(selectors.workerCalendar.secondTuesdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay); + await page.click(selectors.workerCalendar.secondWednesdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay); + await page.click(selectors.workerCalendar.secondThursdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.halfFurlough); - await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.secondFridayOfJun); + await page.click(selectors.workerCalendar.halfFurlough); await page.waitForTimeout(reasonableTimeBetweenClicks); + await page.click(selectors.workerCalendar.secondFridayOfJun); - const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); - - expect(result).toContain(' 1.5 '); + expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 1.5 '); }); }); describe(`as salesBoss`, () => { - it(`should log in and get to Charles Xavier's calendar`, async() => { - await page.loginAndModule('salesBoss', 'worker'); - await page.accessToSearchResult('Charles Xavier'); - await page.accessToSection('worker.card.calendar'); - }); + it(`should log in, get to Charles Xavier's calendar, undo what was done here, and check the total holidays used are back to what it was`, async() => { + accessAs('salesBoss'); - it('should undo what was done here', async() => { - await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.holidays); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); @@ -90,45 +91,24 @@ describe('Worker calendar path', () => { await page.waitToClick(selectors.workerCalendar.halfFurlough); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.secondFridayOfJun); - }); - it('should check the total holidays used are back to what it was', async() => { - const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); - - expect(result).toContain(' 0 '); + expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 '); }); }); describe(`as Charles Xavier`, () => { - it(`should log in and get to his calendar`, async() => { - await page.loginAndModule('CharlesXavier', 'worker'); - await page.accessToSearchResult('Charles Xavier'); - await page.accessToSection('worker.card.calendar'); - }); - - it('should make a futile attempt to add holidays', async() => { - await page.waitForTimeout(reasonableTimeBetweenClicks); + it('should log in and get to his calendar, make a futile attempt to add holidays, check the total holidays used are now the initial ones and use the year selector to go to the previous year', async() => { + accessAs('CharlesXavier'); await page.waitToClick(selectors.workerCalendar.holidays); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); - }); - it('should check the total holidays used are now the initial ones', async() => { - const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); + await page.click(selectors.workerCalendar.penultimateMondayOfJanuary); - expect(result).toContain(' 0 '); - }); - - it('should use the year selector to go to the previous year', async() => { - const date = new Date(); - const lastYear = (date.getFullYear() - 1).toString(); + expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 '); await page.autocompleteSearch(selectors.workerCalendar.year, lastYear); - await page.waitForTimeout(reasonableTimeBetweenClicks); - const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); - - expect(result).toContain(' 0 '); + expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 '); }); }); }); From b2af21ba013f25c6b855636b8185e8d59988174a Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 28 Dec 2022 13:17:47 +0100 Subject: [PATCH 31/72] refs #3571 fdescribed removed --- e2e/paths/03-worker/04_time_control.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js index 1222e7482..a8aea8fd4 100644 --- a/e2e/paths/03-worker/04_time_control.spec.js +++ b/e2e/paths/03-worker/04_time_control.spec.js @@ -2,7 +2,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -fdescribe('Worker time control path', () => { +describe('Worker time control path', () => { let browser; let page; beforeAll(async() => { From edcc55df26080a3c5774cf28b8ef5f45b63f613d Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 29 Dec 2022 10:01:38 +0100 Subject: [PATCH 32/72] refs #4875 @3h disable random to be able to test with some kind of reliability --- back/tests.js | 3 +++ .../back/methods/invoiceOut/specs/createPdf.spec.js | 2 +- .../back/methods/invoiceOut/specs/downloadZip.spec.js | 2 +- .../invoiceOut/back/methods/invoiceOut/specs/filter.spec.js | 6 ++++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/back/tests.js b/back/tests.js index ab6893791..7779bb2e7 100644 --- a/back/tests.js +++ b/back/tests.js @@ -35,6 +35,9 @@ async function test() { const Jasmine = require('jasmine'); const jasmine = new Jasmine(); + // jasmine.seed('68436'); + jasmine.randomizeTests(false); + const SpecReporter = require('jasmine-spec-reporter').SpecReporter; jasmine.addReporter(new SpecReporter({ spec: { diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js index ee3310368..87fcefcb0 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js @@ -11,7 +11,7 @@ describe('InvoiceOut createPdf()', () => { const ctx = {req: activeCtx}; it('should create a new PDF file and set true the hasPdf property', async() => { - pending('https://redmine.verdnatura.es/issues/4875'); + // pending('https://redmine.verdnatura.es/issues/4875'); const invoiceId = 1; spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js index 4d1833635..9dc2d4f15 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js @@ -13,7 +13,7 @@ describe('InvoiceOut downloadZip()', () => { }; it('should return part of link to dowloand the zip', async() => { - pending('https://redmine.verdnatura.es/issues/4875'); + // pending('https://redmine.verdnatura.es/issues/4875'); const tx = await models.InvoiceOut.beginTransaction({}); try { diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js index 7b5886236..00aa0c331 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js @@ -51,7 +51,6 @@ describe('InvoiceOut filter()', () => { }); it('should return the invoice out matching hasPdf', async() => { - pending('https://redmine.verdnatura.es/issues/4875'); const tx = await models.InvoiceOut.beginTransaction({}); const options = {transaction: tx}; @@ -67,7 +66,10 @@ describe('InvoiceOut filter()', () => { const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options); - expect(result.length).toEqual(1); + // expect(result.length).toEqual(1); + expect(result.length).toBeGreaterThanOrEqual(1); + // Debido a que parece que esta fallando debido a un test anterior que no hace rollback + // y deja la base de datos en un estado inconsistente, se cambia el expect a que sea mayor o igual a 1. await tx.rollback(); } catch (e) { From 3514c0ac810a39dd6b6a7f9882b5deebd9f8c775 Mon Sep 17 00:00:00 2001 From: alexandre Date: Tue, 3 Jan 2023 09:14:25 +0100 Subject: [PATCH 33/72] refs #3571 pending tests --- e2e/paths/03-worker/04_time_control.spec.js | 79 ++++++++++++++------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js index a8aea8fd4..bba7ced89 100644 --- a/e2e/paths/03-worker/04_time_control.spec.js +++ b/e2e/paths/03-worker/04_time_control.spec.js @@ -21,70 +21,99 @@ describe('Worker time control path', () => { const fourPm = '16:00'; const hankPymId = 1107; - it(`should go to the next month, go to current month, go 1 month in the past, return error when insert 'out' of first entry, then insert 'in' monday, then insert 'out' monday, then check if Hank Pym worked 8:20 hours, remove first entry of monday, be the 'out' the first entry of monday and change week of month`, async() => { + it('should go to the next month, go to current month and go 1 month in the past', async() => { let date = new Date(); date.setMonth(date.getMonth() + 1); let month = date.toLocaleString('default', {month: 'long'}); await page.click(selectors.workerTimeControl.nextMonthButton); + let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText'); - expect(await page.getProperty(selectors.workerTimeControl.monthName, 'innerText')).toContain(month); + expect(result).toContain(month); date = new Date(); month = date.toLocaleString('default', {month: 'long'}); await page.click(selectors.workerTimeControl.previousMonthButton); + result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText'); - expect(await page.getProperty(selectors.workerTimeControl.monthName, 'innerText')).toContain(month); + expect(result).toContain(month); date = new Date(); date.setMonth(date.getMonth() - 1); - month = date.toLocaleString('default', {month: 'long'}); const timestamp = Math.round(date.getTime() / 1000); + month = date.toLocaleString('default', {month: 'long'}); + await page.loginAndModule('salesBoss', 'worker'); await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`); await page.click(selectors.workerTimeControl.secondWeekDay); - expect(await page.getProperty(selectors.workerTimeControl.monthName, 'innerText')).toContain(month); + result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText'); - await page.click(selectors.workerTimeControl.mondayAddTimeButton); + expect(result).toContain(month); + }); + + it(`should return error when insert 'out' of first entry`, async() => { + pending('https://redmine.verdnatura.es/issues/4707'); + await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm); await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out'); await page.respondToDialog('accept'); - let message = await page.waitForSnackbar(); + const message = await page.waitForSnackbar(); expect(message.text).toBeDefined(); + }); - await page.click(selectors.workerTimeControl.mondayAddTimeButton); + it(`should insert 'in' monday`, async() => { + pending('https://redmine.verdnatura.es/issues/4707'); + await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm); await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in'); await page.respondToDialog('accept'); + const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); - expect(await page.getProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText')).toEqual(eightAm); + expect(result).toEqual(eightAm); + }); - await page.click(selectors.workerTimeControl.mondayAddTimeButton); + it(`should insert 'out' monday`, async() => { + pending('https://redmine.verdnatura.es/issues/4707'); + await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); await page.pickTime(selectors.workerTimeControl.dialogTimeInput, fourPm); await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out'); await page.respondToDialog('accept'); - - expect(await page.getProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText')).toEqual(fourPm); - - expect(await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText')).toBe('08:20 h.'); - expect(await page.getProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText')).toBe('08:20 h.'); - expect(await page.getProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText')).toBe(eightAm); - expect(await page.getProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText')).toBe(fourPm); - await page.click(selectors.workerTimeControl.firstEntryOfMondayDelete); - await page.respondToDialog('accept'); - message = await page.waitForSnackbar(); - - expect(message.text).toContain('Entry removed'); - - const result = await page.getProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); + const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText'); expect(result).toEqual(fourPm); + }); + it(`should check Hank Pym worked 8:20 hours`, async() => { + pending('https://redmine.verdnatura.es/issues/4707'); + await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '08:20 h.'); + await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '08:20 h.'); + }); + + it('should remove first entry of monday', async() => { + pending('https://redmine.verdnatura.es/issues/4707'); + await page.waitForTextInElement(selectors.workerTimeControl.firstEntryOfMonday, eightAm); + await page.waitForTextInElement(selectors.workerTimeControl.secondEntryOfMonday, fourPm); + await page.waitToClick(selectors.workerTimeControl.firstEntryOfMondayDelete); + await page.respondToDialog('accept'); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Entry removed'); + }); + + it(`should be the 'out' the first entry of monday`, async() => { + pending('https://redmine.verdnatura.es/issues/4707'); + const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); + + expect(result).toEqual(fourPm); + }); + + it('should change week of month', async() => { await page.click(selectors.workerTimeControl.thrirdWeekDay); + const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText'); - expect(await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText')).toBe('00:00 h.'); + expect(result).toEqual('00:00 h.'); }); }); From 56354ba771fdcdc08e08e08483083b8af26cfca4 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Tue, 3 Jan 2023 13:24:44 +0100 Subject: [PATCH 34/72] reactivate test --- .../back/methods/invoiceOut/specs/createPdf.spec.js | 1 - .../invoiceOut/back/methods/invoiceOut/specs/filter.spec.js | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js index 87fcefcb0..803338ef3 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js @@ -11,7 +11,6 @@ describe('InvoiceOut createPdf()', () => { const ctx = {req: activeCtx}; it('should create a new PDF file and set true the hasPdf property', async() => { - // pending('https://redmine.verdnatura.es/issues/4875'); const invoiceId = 1; spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js index 00aa0c331..02f982011 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js @@ -66,10 +66,7 @@ describe('InvoiceOut filter()', () => { const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options); - // expect(result.length).toEqual(1); - expect(result.length).toBeGreaterThanOrEqual(1); - // Debido a que parece que esta fallando debido a un test anterior que no hace rollback - // y deja la base de datos en un estado inconsistente, se cambia el expect a que sea mayor o igual a 1. + expect(result.length).toEqual(1); await tx.rollback(); } catch (e) { From f499311a163d979ad616dd46c9b825da0b109d17 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 4 Jan 2023 08:36:55 +0100 Subject: [PATCH 35/72] refs #4951 changed triggers and added checkLength --- db/changes/230201/00-triggersXDiario.sql | 83 ++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 db/changes/230201/00-triggersXDiario.sql diff --git a/db/changes/230201/00-triggersXDiario.sql b/db/changes/230201/00-triggersXDiario.sql new file mode 100644 index 000000000..3efed0f76 --- /dev/null +++ b/db/changes/230201/00-triggersXDiario.sql @@ -0,0 +1,83 @@ +DROP TRIGGER IF EXISTS vn.XDiario_beforeUpdate; +USE vn; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeUpdate` + BEFORE UPDATE ON `XDiario` + FOR EACH ROW +BEGIN + IF NOT NEW.SUBCTA <=> OLD.SUBCTA THEN + IF NOT util.checkLength(NEW.SUBCTA, 10) THEN + CALL util.throw('INVALID_LENGTH'); + END IF; + END IF; + IF NOT NEW.CONTRA <=> OLD.CONTRA THEN + IF NOT util.checkLength(NEW.CONTRA, 10) THEN + CALL util.throw('INVALID_LENGTH'); + END IF; + END IF; + IF NOT NEW.FECHA <=> OLD.FECHA THEN + CALL XDiario_checkDate(NEW.FECHA); + END IF; + IF NOT NEW.FECHA_EX <=> OLD.FECHA_EX THEN + CALL XDiario_checkDate(NEW.FECHA_EX); + END IF; + IF NOT NEW.FECHA_OP <=> OLD.FECHA_OP THEN + CALL XDiario_checkDate(NEW.FECHA_OP); + END IF; + IF NOT NEW.FECHA_RT <=> OLD.FECHA_RT THEN + CALL XDiario_checkDate(NEW.FECHA_RT); + END IF; + IF NOT NEW.FECREGCON <=> OLD.FECREGCON THEN + CALL XDiario_checkDate(NEW.FECREGCON); + END IF; +END$$ +DELIMITER ; + + +DROP TRIGGER IF EXISTS vn.XDiario_beforeInsert; +USE vn; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeInsert` + BEFORE INSERT ON `XDiario` + FOR EACH ROW +BEGIN + IF NOT util.checkLength(NEW.SUBCTA, 10) THEN + CALL util.throw('INVALID_LENGTH'); + END IF; + IF NOT util.checkLength(NEW.CONTRA, 10) THEN + CALL util.throw('INVALID_LENGTH'); + END IF; + CALL XDiario_checkDate(NEW.FECHA); + CALL XDiario_checkDate(NEW.FECHA_EX); + CALL XDiario_checkDate(NEW.FECHA_OP); + CALL XDiario_checkDate(NEW.FECHA_RT); + CALL XDiario_checkDate(NEW.FECREGCON); +END$$ +DELIMITER ; + + +DROP FUNCTION IF EXISTS `util`.`checkLength`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`checkLength`(vString VARCHAR(10), vLength INT(11)) RETURNS tinyint(1) + DETERMINISTIC +BEGIN +/** + * Comprueba la longitud de un string + * + * @param vString String a comprobar + * @param vLength Longitud que debe tener + * @return Devuelve TRUE/FALSE en caso de que tenga la longitud correcta o no + */ + IF LENGTH(vString) <=> vLength THEN + RETURN TRUE; + END IF; + RETURN FALSE; +END$$ +DELIMITER ; + From 8184749626775d0bd8c37d154f96aa94383542e0 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 4 Jan 2023 09:40:27 +0100 Subject: [PATCH 36/72] change db version folder --- db/changes/{225201 => 230201}/00-priceFixed_getRate2.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename db/changes/{225201 => 230201}/00-priceFixed_getRate2.sql (100%) diff --git a/db/changes/225201/00-priceFixed_getRate2.sql b/db/changes/230201/00-priceFixed_getRate2.sql similarity index 100% rename from db/changes/225201/00-priceFixed_getRate2.sql rename to db/changes/230201/00-priceFixed_getRate2.sql From a4e591c5f5dc8d5e76606670072691980b72017a Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 4 Jan 2023 10:37:59 +0100 Subject: [PATCH 37/72] refs #4951 triggers modified --- db/changes/230201/00-triggersXDiario.sql | 37 +++++++++++++++--------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/db/changes/230201/00-triggersXDiario.sql b/db/changes/230201/00-triggersXDiario.sql index 3efed0f76..b31157a3f 100644 --- a/db/changes/230201/00-triggersXDiario.sql +++ b/db/changes/230201/00-triggersXDiario.sql @@ -8,13 +8,19 @@ CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeUpdate` FOR EACH ROW BEGIN IF NOT NEW.SUBCTA <=> OLD.SUBCTA THEN - IF NOT util.checkLength(NEW.SUBCTA, 10) THEN - CALL util.throw('INVALID_LENGTH'); + IF NEW.SUBCTA <=> '' THEN + SET NEW.SUBCTA = NULL; + END IF; + IF NEW.SUBCTA IS NOT NULL AND NOT util.checkStringLength(NEW.SUBCTA, 10) THEN + CALL util.throw('INVALID_STRING_LENGTH'); END IF; END IF; IF NOT NEW.CONTRA <=> OLD.CONTRA THEN - IF NOT util.checkLength(NEW.CONTRA, 10) THEN - CALL util.throw('INVALID_LENGTH'); + IF NEW.CONTRA <=> '' THEN + SET NEW.CONTRA = NULL; + END IF; + IF NEW.CONTRA IS NOT NULL AND NOT util.checkStringLength(NEW.CONTRA, 10) THEN + CALL util.throw('INVALID_STRING_LENGTH'); END IF; END IF; IF NOT NEW.FECHA <=> OLD.FECHA THEN @@ -45,11 +51,17 @@ CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeInsert` BEFORE INSERT ON `XDiario` FOR EACH ROW BEGIN - IF NOT util.checkLength(NEW.SUBCTA, 10) THEN - CALL util.throw('INVALID_LENGTH'); + IF NEW.SUBCTA <=> '' THEN + SET NEW.SUBCTA = NULL; END IF; - IF NOT util.checkLength(NEW.CONTRA, 10) THEN - CALL util.throw('INVALID_LENGTH'); + IF NEW.SUBCTA IS NOT NULL AND NOT util.checkStringLength(NEW.SUBCTA, 10) THEN + CALL util.throw('INVALID_STRING_LENGTH'); + END IF; + IF NEW.CONTRA <=> '' THEN + SET NEW.CONTRA = NULL; + END IF; + IF NEW.CONTRA IS NOT NULL AND NOT util.checkStringLength(NEW.CONTRA, 10) THEN + CALL util.throw('INVALID_STRING_LENGTH'); END IF; CALL XDiario_checkDate(NEW.FECHA); CALL XDiario_checkDate(NEW.FECHA_EX); @@ -60,11 +72,11 @@ END$$ DELIMITER ; -DROP FUNCTION IF EXISTS `util`.`checkLength`; +DROP FUNCTION IF EXISTS `util`.`checkStringLength`; DELIMITER $$ $$ -CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`checkLength`(vString VARCHAR(10), vLength INT(11)) RETURNS tinyint(1) +CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`checkStringLength`(vString VARCHAR(255), vLength INT(3)) RETURNS tinyint(1) DETERMINISTIC BEGIN /** @@ -74,10 +86,7 @@ BEGIN * @param vLength Longitud que debe tener * @return Devuelve TRUE/FALSE en caso de que tenga la longitud correcta o no */ - IF LENGTH(vString) <=> vLength THEN - RETURN TRUE; - END IF; - RETURN FALSE; + RETURN LENGTH(vString) <=> vLength; END$$ DELIMITER ; From 030c474e06470ee7922c33fe179295e8c7e713d7 Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 4 Jan 2023 13:18:55 +0100 Subject: [PATCH 38/72] Fixed update module --- db/changes/225201/01-modules.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/changes/225201/01-modules.sql b/db/changes/225201/01-modules.sql index 82861a5e2..243d2d016 100644 --- a/db/changes/225201/01-modules.sql +++ b/db/changes/225201/01-modules.sql @@ -43,7 +43,7 @@ SET t.code = 'claim' WHERE t.code LIKE 'Claims' ESCAPE '#'; UPDATE salix.module t -SET t.code = 'user' +SET t.code = 'account' WHERE t.code LIKE 'Users' ESCAPE '#'; UPDATE salix.module t From 46f36e1de1ecdf4b0e3fa2ffc9c48bc83a558110 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 4 Jan 2023 15:03:45 +0100 Subject: [PATCH 39/72] try --- back/methods/docuware/10.pdf | Bin 0 -> 127664 bytes back/methods/docuware/download.js | 47 +++++++++++++++++- .../ticket/front/descriptor-menu/index.html | 9 +++- modules/ticket/front/descriptor-menu/index.js | 1 - .../front/descriptor-menu/locale/es.yml | 1 + 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 back/methods/docuware/10.pdf diff --git a/back/methods/docuware/10.pdf b/back/methods/docuware/10.pdf new file mode 100644 index 0000000000000000000000000000000000000000..98a8d2c15aeb278ea073521d0fb324bd81ee755c GIT binary patch literal 127664 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 literal 0 HcmV?d00001 diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js index 489a07e34..717decfcc 100644 --- a/back/methods/docuware/download.js +++ b/back/methods/docuware/download.js @@ -1,6 +1,9 @@ /* eslint max-len: ["error", { "code": 180 }]*/ const got = require('got'); +const axios = require('axios'); const UserError = require('vn-loopback/util/user-error'); +const FormData = require('form-data'); +const fs = require('fs-extra'); module.exports = Self => { Self.remoteMethodCtx('download', { @@ -89,6 +92,7 @@ module.exports = Self => { // get dialog const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); + console.log(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`); const dialogJson = JSON.parse(dialogResponse.body).Dialog; const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id; @@ -107,9 +111,48 @@ module.exports = Self => { } }; - const stream = got.stream(downloadUri, downloadOptions); + // const stream = got.stream(downloadUri, downloadOptions); - return [stream, contentType, fileName]; + const uploadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents?storeDialogId=1f665772-c936-4e13-aa2a-f209b1a7070e`; + const form = new FormData(); + const file = await fs.readFile('back/methods/docuware/10.pdf'); + form.append('file', file, '10.pdf'); + const uploadOptions = { + 'headers': { + 'Cookie': cookie, + } + }; + + const fileData = { + formData: { + 'document': { + value: JSON.stringify({Fields: {}}), + options: { + filename: 'document.json', + contentType: 'application/json', + }, + }, + 'file[]': { + value: fs.createReadStream('back/methods/docuware/10.pdf'), + options: { + filename: '10.pdf', + contentType: null + } + } + } + }; + + Object.assign(uploadOptions, fileData); + try { + const upload = await axios.post(uploadUri, file, uploadOptions); + console.log('UPLOAD FINISHED'); + } catch (e) { + console.log('ERROR CATCHED:', e); + console.log('ERROR CATCHED'); + return; + } + // return [stream, contentType, fileName]; + return; } catch (error) { if (error.code === 'ENOENT') throw new UserError('The DOCUWARE PDF document does not exists'); diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index 805e0b391..1707ff727 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -21,7 +21,7 @@ Add turn Show Delivery Note... @@ -37,7 +37,7 @@ href='api/Docuwares/{{$ctrl.ticket.id}}/download/deliveryClient/findTicket?access_token={{$ctrl.vnToken.token}}' target="_blank" translate> - as PDF + as PDF Docuware Send PDF + + Send PDF Docuware + diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 168002d07..7b7278a80 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -85,7 +85,6 @@ class Controller extends Section { .then(res => this.ticket = res.data) .then(() => { this.isTicketEditable(); - this.hasDocuware(); }); } diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index a2725f485..3f06d4378 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -1,6 +1,7 @@ Show Delivery Note...: Ver albarán... Send Delivery Note...: Enviar albarán... as PDF: como PDF +as PDF Docuware: como PDF Docuware as CSV: como CSV as PDF without prices: como PDF sin precios Send PDF: Enviar PDF From f0761a92a21be53460ddb6f1ea5625f0172a77e2 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 5 Jan 2023 09:26:11 +0100 Subject: [PATCH 40/72] reactivate working tests --- .../back/methods/invoiceOut/specs/downloadZip.spec.js | 1 - modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js index 9dc2d4f15..41ea45487 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js @@ -13,7 +13,6 @@ describe('InvoiceOut downloadZip()', () => { }; it('should return part of link to dowloand the zip', async() => { - // pending('https://redmine.verdnatura.es/issues/4875'); const tx = await models.InvoiceOut.beginTransaction({}); try { diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js index 02f982011..e5cf5fda0 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js @@ -66,7 +66,7 @@ describe('InvoiceOut filter()', () => { const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options); - expect(result.length).toEqual(1); + expect(result.length).toBeGreaterThanOrEqual(1); await tx.rollback(); } catch (e) { From 0e426db296787f88dfcd0f5a7cb8a63d14d881f3 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 5 Jan 2023 09:32:31 +0100 Subject: [PATCH 42/72] remove seed and randomize false on the test --- back/tests.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/back/tests.js b/back/tests.js index 7779bb2e7..ab6893791 100644 --- a/back/tests.js +++ b/back/tests.js @@ -35,9 +35,6 @@ async function test() { const Jasmine = require('jasmine'); const jasmine = new Jasmine(); - // jasmine.seed('68436'); - jasmine.randomizeTests(false); - const SpecReporter = require('jasmine-spec-reporter').SpecReporter; jasmine.addReporter(new SpecReporter({ spec: { From 2803de94456bb90f87647bf3f1a93eb822c984d5 Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 5 Jan 2023 14:20:47 +0100 Subject: [PATCH 43/72] refs #4975 Return JSON {version:xxx} --- modules/mdb/back/methods/mdbVersion/last.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/mdb/back/methods/mdbVersion/last.js b/modules/mdb/back/methods/mdbVersion/last.js index 7e4a18ac3..802899f76 100644 --- a/modules/mdb/back/methods/mdbVersion/last.js +++ b/modules/mdb/back/methods/mdbVersion/last.js @@ -36,6 +36,11 @@ module.exports = Self => { if (mdb.version > maxNumber) maxNumber = mdb.version; } - return maxNumber; + + let response = { + version: maxNumber + }; + + return response; }; }; From 8f81aa2eeb61db173ec0a8e585452ae891284c5a Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 5 Jan 2023 14:28:35 +0100 Subject: [PATCH 44/72] fix: salta los tickets que estan bloqueados --- back/methods/osticket/closeTicket.js | 93 +++++++++++++--------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index e5cbc58f4..cd0a2b3a2 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -25,10 +25,10 @@ module.exports = Self => { return false; const con = mysql.createConnection({ - host: config.hostDb, - user: config.userDb, - password: config.passwordDb, - port: config.portDb + host: `${config.hostDb}`, + user: `${config.userDb}`, + password: `${config.passwordDb}`, + port: `${config.portDb}` }); const sql = `SELECT ot.ticket_id, ot.number @@ -38,23 +38,23 @@ module.exports = Self => { JOIN ( SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated FROM osticket.ost_thread_entry ote - WHERE ote.staff_id AND ote.type = 'R' + WHERE ote.staff_id != 0 AND ote.type = 'R' GROUP BY ote.thread_id ) sub ON sub.thread_id = ot2.id - WHERE ot.isanswered - AND ots.state = ? - AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`; + WHERE ot.isanswered = 1 + AND ots.state = '${config.oldStatus}' + AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`; - const ticketsId = []; + let ticketsId = []; con.connect(err => { if (err) throw err; - con.query(sql, [config.oldStatus, config.day], - (err, results) => { - if (err) throw err; - for (const result of results) - ticketsId.push(result.ticket_id); - }); + con.query(sql, (err, results) => { + if (err) throw err; + for (const result of results) + ticketsId.push(result.ticket_id); + }); }); + await getRequestToken(); async function getRequestToken() { @@ -94,39 +94,6 @@ module.exports = Self => { await close(token, secondCookie); } - async function close(token, secondCookie) { - for (const ticketId of ticketsId) { - try { - const lockCode = await getLockCode(token, secondCookie, ticketId); - let form = new FormData(); - form.append('__CSRFToken__', token); - form.append('id', ticketId); - form.append('a', config.responseType); - form.append('lockCode', lockCode); - form.append('from_email_id', config.fromEmailId); - form.append('reply-to', config.replyTo); - form.append('cannedResp', 0); - form.append('response', config.comment); - form.append('signature', 'none'); - form.append('reply_status_id', config.newStatusId); - - const ostUri = `${config.host}/tickets.php?id=${ticketId}`; - const params = { - method: 'POST', - body: form, - headers: { - 'Cookie': secondCookie - } - }; - await fetch(ostUri, params); - } catch (e) { - const err = new Error(`${ticketId} Ticket close failed: ${e.message}`); - err.stack += e.stack; - throw err; - } - } - } - async function getLockCode(token, secondCookie, ticketId) { const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`; const params = { @@ -140,7 +107,35 @@ module.exports = Self => { const body = await response.text(); const json = JSON.parse(body); - return json.code; + return json.code || json.retry; + } + + async function close(token, secondCookie) { + for (const ticketId of ticketsId) { + const lockCode = await getLockCode(token, secondCookie, ticketId); + if (lockCode == false) continue; + let form = new FormData(); + form.append('__CSRFToken__', token); + form.append('id', ticketId); + form.append('a', config.responseType); + form.append('lockCode', lockCode); + form.append('from_email_id', config.fromEmailId); + form.append('reply-to', config.replyTo); + form.append('cannedResp', 0); + form.append('response', config.comment); + form.append('signature', 'none'); + form.append('reply_status_id', config.newStatusId); + + const ostUri = `${config.host}/tickets.php?id=${ticketId}`; + const params = { + method: 'POST', + body: form, + headers: { + 'Cookie': secondCookie + } + }; + return fetch(ostUri, params); + } } }; }; From 1a74e84359cf7acc99d7927c467b4f622e347d04 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 5 Jan 2023 14:32:14 +0100 Subject: [PATCH 45/72] feat: salta los tickets bloqueados --- back/methods/osticket/closeTicket.js | 92 +++++++++++++++------------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index cd0a2b3a2..ad2af75b7 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -25,10 +25,10 @@ module.exports = Self => { return false; const con = mysql.createConnection({ - host: `${config.hostDb}`, - user: `${config.userDb}`, - password: `${config.passwordDb}`, - port: `${config.portDb}` + host: config.hostDb, + user: config.userDb, + password: config.passwordDb, + port: config.portDb }); const sql = `SELECT ot.ticket_id, ot.number @@ -38,23 +38,23 @@ module.exports = Self => { JOIN ( SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated FROM osticket.ost_thread_entry ote - WHERE ote.staff_id != 0 AND ote.type = 'R' + WHERE ote.staff_id AND ote.type = 'R' GROUP BY ote.thread_id ) sub ON sub.thread_id = ot2.id - WHERE ot.isanswered = 1 - AND ots.state = '${config.oldStatus}' - AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`; + WHERE ot.isanswered + AND ots.state = ? + AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`; - let ticketsId = []; + const ticketsId = []; con.connect(err => { if (err) throw err; - con.query(sql, (err, results) => { - if (err) throw err; - for (const result of results) - ticketsId.push(result.ticket_id); - }); + con.query(sql, [config.oldStatus, config.day], + (err, results) => { + if (err) throw err; + for (const result of results) + ticketsId.push(result.ticket_id); + }); }); - await getRequestToken(); async function getRequestToken() { @@ -94,6 +94,40 @@ module.exports = Self => { await close(token, secondCookie); } + async function close(token, secondCookie) { + for (const ticketId of ticketsId) { + try { + const lockCode = await getLockCode(token, secondCookie, ticketId); + if (lockCode == false) continue; + let form = new FormData(); + form.append('__CSRFToken__', token); + form.append('id', ticketId); + form.append('a', config.responseType); + form.append('lockCode', lockCode); + form.append('from_email_id', config.fromEmailId); + form.append('reply-to', config.replyTo); + form.append('cannedResp', 0); + form.append('response', config.comment); + form.append('signature', 'none'); + form.append('reply_status_id', config.newStatusId); + + const ostUri = `${config.host}/tickets.php?id=${ticketId}`; + const params = { + method: 'POST', + body: form, + headers: { + 'Cookie': secondCookie + } + }; + await fetch(ostUri, params); + } catch (e) { + const err = new Error(`${ticketId} Ticket close failed: ${e.message}`); + err.stack += e.stack; + throw err; + } + } + } + async function getLockCode(token, secondCookie, ticketId) { const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`; const params = { @@ -109,33 +143,5 @@ module.exports = Self => { return json.code || json.retry; } - - async function close(token, secondCookie) { - for (const ticketId of ticketsId) { - const lockCode = await getLockCode(token, secondCookie, ticketId); - if (lockCode == false) continue; - let form = new FormData(); - form.append('__CSRFToken__', token); - form.append('id', ticketId); - form.append('a', config.responseType); - form.append('lockCode', lockCode); - form.append('from_email_id', config.fromEmailId); - form.append('reply-to', config.replyTo); - form.append('cannedResp', 0); - form.append('response', config.comment); - form.append('signature', 'none'); - form.append('reply_status_id', config.newStatusId); - - const ostUri = `${config.host}/tickets.php?id=${ticketId}`; - const params = { - method: 'POST', - body: form, - headers: { - 'Cookie': secondCookie - } - }; - return fetch(ostUri, params); - } - } }; }; From f356188491e5f4261199510465b93a0d38cc8763 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 5 Jan 2023 14:54:43 +0100 Subject: [PATCH 46/72] feat: salta los tickets bloqueados --- back/methods/osticket/closeTicket.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index ad2af75b7..c2f6aab1e 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -98,7 +98,7 @@ module.exports = Self => { for (const ticketId of ticketsId) { try { const lockCode = await getLockCode(token, secondCookie, ticketId); - if (lockCode == false) continue; + if (!lockCode) throw new Error(`Can't get lock code`); let form = new FormData(); form.append('__CSRFToken__', token); form.append('id', ticketId); @@ -123,7 +123,7 @@ module.exports = Self => { } catch (e) { const err = new Error(`${ticketId} Ticket close failed: ${e.message}`); err.stack += e.stack; - throw err; + console.error(err); } } } @@ -141,7 +141,7 @@ module.exports = Self => { const body = await response.text(); const json = JSON.parse(body); - return json.code || json.retry; + return json.code || null; } }; }; From a68fe26952f5e2286232fcc2178431fc61303b1c Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 5 Jan 2023 15:01:20 +0100 Subject: [PATCH 47/72] refactor: machar stack --- back/methods/osticket/closeTicket.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index c2f6aab1e..601470a02 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -121,8 +121,8 @@ module.exports = Self => { }; await fetch(ostUri, params); } catch (e) { - const err = new Error(`${ticketId} Ticket close failed: ${e.message}`); - err.stack += e.stack; + const err = new Error(`$s{ticketId} Ticket close failed: ${e.message}`); + err.stack = e.stack; console.error(err); } } From 0c6d335c4ac5fd5cdaf6b0689047c8dceefb5914 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 5 Jan 2023 15:01:29 +0100 Subject: [PATCH 48/72] refactor: machacar stack --- back/methods/osticket/closeTicket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index 601470a02..98ef0aeef 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -121,7 +121,7 @@ module.exports = Self => { }; await fetch(ostUri, params); } catch (e) { - const err = new Error(`$s{ticketId} Ticket close failed: ${e.message}`); + const err = new Error(`${ticketId} Ticket close failed: ${e.message}`); err.stack = e.stack; console.error(err); } From 201987fb992ac35220eef9d366ece37ff71228c2 Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 9 Jan 2023 11:42:16 +0100 Subject: [PATCH 49/72] feat: muestra el error --- back/methods/osticket/closeTicket.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index 98ef0aeef..40c0d860c 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -97,13 +97,17 @@ module.exports = Self => { async function close(token, secondCookie) { for (const ticketId of ticketsId) { try { - const lockCode = await getLockCode(token, secondCookie, ticketId); - if (!lockCode) throw new Error(`Can't get lock code`); + const lock = await getLockCode(token, secondCookie, ticketId); + if (!lock.code) { + let error = `Can't get lock code`; + if (lock.msg) error += `: ${lock.msg}`; + throw new Error(error); + } let form = new FormData(); form.append('__CSRFToken__', token); form.append('id', ticketId); form.append('a', config.responseType); - form.append('lockCode', lockCode); + form.append('lockCode', lock.code); form.append('from_email_id', config.fromEmailId); form.append('reply-to', config.replyTo); form.append('cannedResp', 0); @@ -141,7 +145,7 @@ module.exports = Self => { const body = await response.text(); const json = JSON.parse(body); - return json.code || null; + return json; } }; }; From 382aad2622524ef254ed1c5d28c35c131ec1ae2b Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 9 Jan 2023 15:11:53 +0100 Subject: [PATCH 50/72] feat(docuware): upload and send pdf --- CHANGELOG.md | 2 + back/methods/docuware/10.pdf | Bin 127664 -> 0 bytes back/methods/docuware/checkFile.js | 6 + back/methods/docuware/deliveryNoteEmail.js | 72 +++ back/methods/docuware/download.js | 53 +- back/methods/docuware/upload.js | 470 ++++++++++++++++++ back/models/docuware.js | 2 + back/models/docuware.json | 13 +- db/changes/230201/00-docuwareStore.sql | 10 + .../ticket/front/descriptor-menu/index.html | 50 +- modules/ticket/front/descriptor-menu/index.js | 34 +- 11 files changed, 629 insertions(+), 83 deletions(-) delete mode 100644 back/methods/docuware/10.pdf create mode 100644 back/methods/docuware/deliveryNoteEmail.js create mode 100644 back/methods/docuware/upload.js create mode 100644 db/changes/230201/00-docuwareStore.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index bafedc760..589faf2fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [General](Inicio) Permite recuperar la contraseña +- [Ticket](Opciones) Subir albarán a Docuware +- [Ticket](Opciones) Enviar correo con PDF de Docuware ### Changed diff --git a/back/methods/docuware/10.pdf b/back/methods/docuware/10.pdf deleted file mode 100644 index 98a8d2c15aeb278ea073521d0fb324bd81ee755c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127664 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 diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js index c6712bb65..38bc35183 100644 --- a/back/methods/docuware/checkFile.js +++ b/back/methods/docuware/checkFile.js @@ -85,6 +85,12 @@ module.exports = Self => { `${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options); JSON.parse(response.body).Items[0].Id; + // const file = JSON.parse(response.body).Items[0]; + // const state = file.Fields.find(field => field.FieldName == 'ESTADO'); + + // if (state != 'Firmado') + // return false; + return true; } catch (error) { return false; diff --git a/back/methods/docuware/deliveryNoteEmail.js b/back/methods/docuware/deliveryNoteEmail.js new file mode 100644 index 000000000..afb8bf980 --- /dev/null +++ b/back/methods/docuware/deliveryNoteEmail.js @@ -0,0 +1,72 @@ +const {Email} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('deliveryNoteEmail', { + description: 'Sends the delivery note email with an docuware attached PDF', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'string', + required: true, + description: 'The ticket id', + http: {source: 'path'} + }, + { + arg: 'recipient', + type: 'string', + description: 'The recipient email', + required: true, + }, + { + arg: 'recipientId', + type: 'number', + description: 'The client id', + required: false + } + ], + 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/delivery-note-email', + verb: 'POST' + } + }); + + Self.deliveryNoteEmail = async(ctx, id) => { + const args = Object.assign({}, ctx.args); + const params = { + recipient: args.recipient, + lang: ctx.req.getLocale() + }; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const email = new Email('delivery-note', params); + + const docuwareFile = await Self.app.models.Docuware.download(ctx, id, 'deliveryClient', 'findTicket'); + + return email.send({ + overrideAttachments: true, + attachments: [{ + filename: `${id}.pdf`, + content: docuwareFile[0] + }] + }); + }; +}; diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js index 717decfcc..29b2aefa2 100644 --- a/back/methods/docuware/download.js +++ b/back/methods/docuware/download.js @@ -1,9 +1,6 @@ /* eslint max-len: ["error", { "code": 180 }]*/ const got = require('got'); -const axios = require('axios'); const UserError = require('vn-loopback/util/user-error'); -const FormData = require('form-data'); -const fs = require('fs-extra'); module.exports = Self => { Self.remoteMethodCtx('download', { @@ -13,19 +10,19 @@ module.exports = Self => { { arg: 'id', type: 'number', - description: 'The id', + description: 'The ticket id', http: {source: 'path'} }, { arg: 'fileCabinet', type: 'string', - description: 'The id', + description: 'The file cabinet', http: {source: 'path'} }, { arg: 'dialog', type: 'string', - description: 'The id', + description: 'The dialog', http: {source: 'path'} } ], @@ -92,7 +89,6 @@ module.exports = Self => { // get dialog const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); - console.log(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`); const dialogJson = JSON.parse(dialogResponse.body).Dialog; const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id; @@ -111,48 +107,9 @@ module.exports = Self => { } }; - // const stream = got.stream(downloadUri, downloadOptions); + const stream = got.stream(downloadUri, downloadOptions); - const uploadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents?storeDialogId=1f665772-c936-4e13-aa2a-f209b1a7070e`; - const form = new FormData(); - const file = await fs.readFile('back/methods/docuware/10.pdf'); - form.append('file', file, '10.pdf'); - const uploadOptions = { - 'headers': { - 'Cookie': cookie, - } - }; - - const fileData = { - formData: { - 'document': { - value: JSON.stringify({Fields: {}}), - options: { - filename: 'document.json', - contentType: 'application/json', - }, - }, - 'file[]': { - value: fs.createReadStream('back/methods/docuware/10.pdf'), - options: { - filename: '10.pdf', - contentType: null - } - } - } - }; - - Object.assign(uploadOptions, fileData); - try { - const upload = await axios.post(uploadUri, file, uploadOptions); - console.log('UPLOAD FINISHED'); - } catch (e) { - console.log('ERROR CATCHED:', e); - console.log('ERROR CATCHED'); - return; - } - // return [stream, contentType, fileName]; - return; + return [stream, contentType, fileName]; } catch (error) { if (error.code === 'ENOENT') throw new UserError('The DOCUWARE PDF document does not exists'); diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js new file mode 100644 index 000000000..393952a6f --- /dev/null +++ b/back/methods/docuware/upload.js @@ -0,0 +1,470 @@ +const got = require('got'); +const UserError = require('vn-loopback/util/user-error'); +const request = require('request'); + +module.exports = Self => { + Self.remoteMethodCtx('upload', { + description: 'Upload an docuware PDF', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The ticket id', + http: {source: 'path'} + }, + { + arg: 'fileCabinet', + type: 'string', + description: 'The file cabinet' + }, + { + arg: 'dialog', + type: 'string', + description: 'The dialog' + } + ], + 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/upload`, + verb: 'POST' + } + }); + + Self.upload = async function(ctx, id, fileCabinet, dialog) { + const myUserId = ctx.req.accessToken.userId; + if (!myUserId) + throw new UserError(`You don't have enough privileges`); + + const models = Self.app.models; + const docuwareConfig = await models.DocuwareConfig.findOne(); + const docuwareInfo = await models.Docuware.findOne({ + where: { + code: fileCabinet, + dialogName: dialog + } + }); + + const docuwareUrl = docuwareConfig.url; + const cookie = docuwareConfig.token; + const fileCabinetName = docuwareInfo.fileCabinetName; + const options = { + 'headers': { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Cookie': cookie + } + }; + + try { + // get fileCabinetId + const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); + const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; + const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; + + // get dialog + const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); + const dialogJson = JSON.parse(dialogResponse.body).Dialog; + const storeDialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'Archivar').Id; + + const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { + id, + type: 'deliveryNote' + }); + + // get ticket data + const ticket = await models.Ticket.findById(id, { + include: [{ + relation: 'client', + scope: { + fields: ['id', 'socialName', 'fi'] + } + }] + }); + const [taxes] = await models.Ticket.rawSql('CALL vn.ticketGetTaxAdd(?)', [id]); + + // upload file + const templateJson = { + 'Fields': [ + { + 'FieldName': 'N__ALBAR_N', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 2531, + 'Top': 3645, + 'Width': 257, + 'Height': 230, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': id, + 'FieldValue': id + }, + { + 'FieldName': 'CIF_PROVEEDOR', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 6176, + 'Top': 4624, + 'Width': 839, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.client().fi, + 'FieldValue': ticket.client().fi + }, + { + 'FieldName': 'CODIGO_PROVEEDOR', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 2531, + 'Top': 3240, + 'Width': 514, + 'Height': 230, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.client().id, + 'FieldValue': ticket.client().id + }, + { + 'FieldName': 'NOMBRE_PROVEEDOR', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 6175, + 'Top': 4264, + 'Width': 858, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.client().socialName, + 'FieldValue': ticket.client().socialName + }, + { + 'FieldName': 'FECHA_FACTURA', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'date', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 2531, + 'Top': 4050, + 'Width': 1181, + 'Height': 230, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.shipped, + 'FieldValue': ticket.shipped + }, + { + 'FieldName': 'TIPO_IVA__1_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 9537, + 'Top': 10057, + 'Width': 615, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': taxes[0].rate, + 'FieldValue': taxes[0].rate + }, + { + 'FieldName': 'BASE_IMPONIBLE__1_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 8907, + 'Top': 10567, + 'Width': 419, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.totalWithoutVat, + 'FieldValue': ticket.totalWithoutVat + }, + { + 'FieldName': 'IMPORTE_IVA__1_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 10423, + 'Top': 10057, + 'Width': 419, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': taxes[0].tax, + 'FieldValue': taxes[0].tax + }, + { + 'FieldName': 'TIPO_IVA__2_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'BASE_IMPONIBLE__2_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'IMPORTE_IVA__2_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'TIPO_IVA__3_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'BASE_IMPONIBLE__3_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'IMPORTE_IVA__3_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'IRPF', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'TOTAL_FACTURA', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 10423, + 'Top': 10958, + 'Width': 419, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.totalWithVat, + 'FieldValue': ticket.totalWithVat + }, + { + 'FieldName': 'ESTADO', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': 'Pendiente procesar', + 'FieldValue': 'Pendiente procesar' + }, + { + 'FieldName': 'URL', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': '' + }, + { + 'FieldName': 'FIRMA_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': 'Si', + 'FieldValue': 'Si' + }, + { + 'FieldName': 'FILTRO_TABLET', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': 'Tablet1', + 'FieldValue': 'Tablet1' + } + ] + }; + + const uploadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${storeDialogId}`; + const optionsUpload = { + 'method': 'POST', + 'url': uploadUri, + 'headers': { + 'Content-Type': 'multipart/form-data', + 'X-File-ModifiedDate': new Date(), + 'Cookie': cookie + }, + 'formData': { + 'document': { + 'value': JSON.stringify(templateJson), + 'options': { + 'filename': 'store.json', + 'contentType': null + } + }, + 'file[]': { + 'value': deliveryNote[0], + 'options': { + 'filename': 'deliveryNote.pdf', + 'contentType': deliveryNote[1] + } + } + } + }; + + try { + return new Promise((resolve, reject) => { + request.post(optionsUpload, function(error) { + if (error) return reject(error); + resolve(); + }); + }); + } catch (error) { + console.log(error); + return; + } + } catch (error) { + if (error.code === 'ENOENT') + throw new UserError('The DOCUWARE PDF document does not exists'); + + throw error; + } + }; +}; diff --git a/back/models/docuware.js b/back/models/docuware.js index 8fd8065ed..0b29494eb 100644 --- a/back/models/docuware.js +++ b/back/models/docuware.js @@ -1,4 +1,6 @@ module.exports = Self => { require('../methods/docuware/download')(Self); + require('../methods/docuware/upload')(Self); require('../methods/docuware/checkFile')(Self); + require('../methods/docuware/deliveryNoteEmail')(Self); }; diff --git a/back/models/docuware.json b/back/models/docuware.json index fb2ed919e..b7e1c57e5 100644 --- a/back/models/docuware.json +++ b/back/models/docuware.json @@ -25,14 +25,5 @@ "find": { "type": "string" } - }, - "acls": [ - { - "property": "*", - "accessType": "*", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - } - ] -} \ No newline at end of file + } +} diff --git a/db/changes/230201/00-docuwareStore.sql b/db/changes/230201/00-docuwareStore.sql new file mode 100644 index 000000000..a9fa01c76 --- /dev/null +++ b/db/changes/230201/00-docuwareStore.sql @@ -0,0 +1,10 @@ +INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName`, `find`) + VALUES + ('deliveryClient', 'Albaranes cliente', 'storeTicket', 'N__ALBAR_N'); + +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) + VALUES + ('Docuware','checkFile','READ','ALLOW','employee'), + ('Docuware','download','READ','ALLOW','salesPerson'), + ('Docuware','upload','WRITE','ALLOW','productionAssi'), + ('Docuware','deliveryNoteEmail','WRITE','ALLOW','salesPerson'); diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index 1707ff727..6b7fcd838 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -21,26 +21,17 @@ Add turn Show Delivery Note... as PDF - - as PDF Docuware - as PDF without prices @@ -64,11 +55,6 @@ translate> Send PDF - - Send PDF Docuware - @@ -77,6 +63,40 @@ + + Docuware options... + + +
+ + Loading Docuware info +
+
+ + + Show PDF + + + Upload Salix PDF + + + Send PDF + + +
+
diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 7b7278a80..11d07d3bf 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -133,15 +133,6 @@ class Controller extends Section { }); } - hasDocuware() { - const params = { - fileCabinet: 'deliveryClient', - dialog: 'findTicket' - }; - this.$http.post(`Docuwares/${this.id}/checkFile`, params) - .then(res => this.hasDocuwareFile = res.data); - } - showPdfDeliveryNote(type) { this.vnReport.show(`tickets/${this.id}/delivery-note-pdf`, { recipientId: this.ticket.client.id, @@ -311,6 +302,31 @@ class Controller extends Section { return this.$http.post(`Tickets/${this.id}/sendSms`, sms) .then(() => this.vnApp.showSuccess(this.$t('SMS sent'))); } + + hasDocuware() { + const params = { + fileCabinet: 'deliveryClient', + dialog: 'findTicket' + }; + this.isLoadingDocuware = true; + this.$http.post(`Docuwares/${this.id}/checkFile`, params) + .then(res => { + this.hasDocuwareFile = res.data; + this.isLoadingDocuware = false; + }); + } + + uploadDocuware() { + return this.$http.post(`Docuwares/${this.id}/upload`, {fileCabinet: 'deliveryClient', dialog: 'storeTicket'}) + .then(() => this.vnApp.showSuccess(this.$t('PDF uploaded!'))); + } + + sendDocuwarePdfDeliveryNote($data) { + return this.vnEmail.send(`Docuwares/${this.id}/delivery-note-email`, { + recipientId: this.ticket.client.id, + recipient: $data.email + }); + } } Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail']; From f23eb9f4ad85bde2d4afe2152807597db50ab24e Mon Sep 17 00:00:00 2001 From: guillermo Date: Mon, 9 Jan 2023 18:28:09 +0100 Subject: [PATCH 51/72] refs #4975 Added call to last method in upload --- modules/mdb/back/methods/mdbVersion/upload.js | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/modules/mdb/back/methods/mdbVersion/upload.js b/modules/mdb/back/methods/mdbVersion/upload.js index 8c77281b1..fa196b58d 100644 --- a/modules/mdb/back/methods/mdbVersion/upload.js +++ b/modules/mdb/back/methods/mdbVersion/upload.js @@ -63,22 +63,13 @@ module.exports = Self => { if (!existBranch) throw new UserError('Not exist this branch'); - let lastVersion = await models.MdbBranch.findOne({ - where: {appName} - }, myOptions); + let lastMethod = await Self.last(ctx, appName, myOptions); + lastMethod.version++; - if (lastVersion++ != toVersion) + if (lastMethod.version != toVersion) throw new UserError('Try again'); const userId = ctx.req.accessToken.userId; - models.MdbVersionTree.create({ - app: appName, - version: toVersion, - branchFk: branch, - fromVersion, - userFk: userId - }); - const tempContainer = await TempContainer.container('access'); const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); const files = Object.values(uploaded.files).map(file => { @@ -121,6 +112,14 @@ module.exports = Self => { } } + await models.MdbVersionTree.create({ + app: appName, + version: toVersion, + branchFk: branch, + fromVersion, + userFk: userId + }, myOptions); + await models.MdbVersion.upsert({ app: appName, branchFk: branch, From e0e34595f4d49b41c4de75cbf43b484c4a94a2e6 Mon Sep 17 00:00:00 2001 From: guillermo Date: Mon, 9 Jan 2023 18:46:19 +0100 Subject: [PATCH 52/72] refs #4975 Added translations --- loopback/locale/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index c2dad5e03..bc23e3c0f 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -250,5 +250,6 @@ "Receipt's bank was not found": "No se encontró el banco del recibo", "This receipt was not compensated": "Este recibo no ha sido compensado", "Client's email was not found": "No se encontró el email del cliente", - "App name does not exist": "El nombre de aplicación no es válido" + "App name does not exist": "El nombre de aplicación no es válido", + "Try again": "Vuelve a intentarlo" } \ No newline at end of file From f103931e503f69a834171d35c9af096e740eea98 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 10 Jan 2023 08:14:13 +0100 Subject: [PATCH 53/72] fix: se concatenan pq sino no muestra el id del ticket --- back/methods/osticket/closeTicket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index 40c0d860c..178b09601 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -126,7 +126,7 @@ module.exports = Self => { await fetch(ostUri, params); } catch (e) { const err = new Error(`${ticketId} Ticket close failed: ${e.message}`); - err.stack = e.stack; + err.stack += e.stack; console.error(err); } } From d540e278eb659c537d73a3aad5171ee04dcae1d4 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Tue, 10 Jan 2023 13:52:30 +0100 Subject: [PATCH 54/72] refs #4605 @30min --- modules/ticket/back/model-config.json | 2 +- .../models/{packagingMistake.json => expeditionMistake.json} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename modules/ticket/back/models/{packagingMistake.json => expeditionMistake.json} (82%) diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 859ecdef8..4187973d0 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -20,7 +20,7 @@ "Packaging": { "dataSource": "vn" }, - "PackagingMistake": { + "ExpeditionMistake": { "dataSource": "vn" }, "PrintServerQueue": { diff --git a/modules/ticket/back/models/packagingMistake.json b/modules/ticket/back/models/expeditionMistake.json similarity index 82% rename from modules/ticket/back/models/packagingMistake.json rename to modules/ticket/back/models/expeditionMistake.json index 7bfc0eca2..e01f2f4be 100644 --- a/modules/ticket/back/models/packagingMistake.json +++ b/modules/ticket/back/models/expeditionMistake.json @@ -1,9 +1,9 @@ { - "name": "PackagingMistake", + "name": "ExpeditionMistake", "base": "VnModel", "options": { "mysql": { - "table": "packagingMistake" + "table": "expeditionMistake" } }, "properties": { From b8fe919b2ad0e455a9b8731e6ca072489d7e37e4 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 10 Jan 2023 15:03:23 +0100 Subject: [PATCH 55/72] feat(docuware): upload deliveryNote file --- back/methods/docuware/upload.js | 773 +++++++++--------- .../ticket/front/descriptor-menu/index.html | 63 +- 2 files changed, 393 insertions(+), 443 deletions(-) diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js index 393952a6f..687c82e2b 100644 --- a/back/methods/docuware/upload.js +++ b/back/methods/docuware/upload.js @@ -1,6 +1,6 @@ const got = require('got'); const UserError = require('vn-loopback/util/user-error'); -const request = require('request'); +const axios = require('axios'); module.exports = Self => { Self.remoteMethodCtx('upload', { @@ -24,21 +24,7 @@ module.exports = Self => { description: 'The dialog' } ], - returns: [ - { - arg: 'body', - type: 'file', - root: true - }, { - arg: 'Content-Type', - type: 'string', - http: {target: 'header'} - }, { - arg: 'Content-Disposition', - type: 'string', - http: {target: 'header'} - } - ], + returns: [], http: { path: `/:id/upload`, verb: 'POST' @@ -70,401 +56,376 @@ module.exports = Self => { } }; - try { - // get fileCabinetId - const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); - const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; - const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; + // get fileCabinetId + const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); + const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; + const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; - // get dialog - const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); - const dialogJson = JSON.parse(dialogResponse.body).Dialog; - const storeDialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'Archivar').Id; + // get dialog + const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); + const dialogJson = JSON.parse(dialogResponse.body).Dialog; + const storeDialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'Archivar').Id; - const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { - id, - type: 'deliveryNote' - }); + const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { + id, + type: 'deliveryNote' + }); - // get ticket data - const ticket = await models.Ticket.findById(id, { - include: [{ - relation: 'client', - scope: { - fields: ['id', 'socialName', 'fi'] - } - }] - }); - const [taxes] = await models.Ticket.rawSql('CALL vn.ticketGetTaxAdd(?)', [id]); - - // upload file - const templateJson = { - 'Fields': [ - { - 'FieldName': 'N__ALBAR_N', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 2531, - 'Top': 3645, - 'Width': 257, - 'Height': 230, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': id, - 'FieldValue': id - }, - { - 'FieldName': 'CIF_PROVEEDOR', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 6176, - 'Top': 4624, - 'Width': 839, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': ticket.client().fi, - 'FieldValue': ticket.client().fi - }, - { - 'FieldName': 'CODIGO_PROVEEDOR', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 2531, - 'Top': 3240, - 'Width': 514, - 'Height': 230, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': ticket.client().id, - 'FieldValue': ticket.client().id - }, - { - 'FieldName': 'NOMBRE_PROVEEDOR', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 6175, - 'Top': 4264, - 'Width': 858, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': ticket.client().socialName, - 'FieldValue': ticket.client().socialName - }, - { - 'FieldName': 'FECHA_FACTURA', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'date', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 2531, - 'Top': 4050, - 'Width': 1181, - 'Height': 230, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': ticket.shipped, - 'FieldValue': ticket.shipped - }, - { - 'FieldName': 'TIPO_IVA__1_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 9537, - 'Top': 10057, - 'Width': 615, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': taxes[0].rate, - 'FieldValue': taxes[0].rate - }, - { - 'FieldName': 'BASE_IMPONIBLE__1_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 8907, - 'Top': 10567, - 'Width': 419, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': ticket.totalWithoutVat, - 'FieldValue': ticket.totalWithoutVat - }, - { - 'FieldName': 'IMPORTE_IVA__1_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 10423, - 'Top': 10057, - 'Width': 419, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': taxes[0].tax, - 'FieldValue': taxes[0].tax - }, - { - 'FieldName': 'TIPO_IVA__2_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'BASE_IMPONIBLE__2_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'IMPORTE_IVA__2_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'TIPO_IVA__3_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'BASE_IMPONIBLE__3_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'IMPORTE_IVA__3_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'IRPF', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'TOTAL_FACTURA', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 10423, - 'Top': 10958, - 'Width': 419, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': ticket.totalWithVat, - 'FieldValue': ticket.totalWithVat - }, - { - 'FieldName': 'ESTADO', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': 'Pendiente procesar', - 'FieldValue': 'Pendiente procesar' - }, - { - 'FieldName': 'URL', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': '' - }, - { - 'FieldName': 'FIRMA_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': 'Si', - 'FieldValue': 'Si' - }, - { - 'FieldName': 'FILTRO_TABLET', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': 'Tablet1', - 'FieldValue': 'Tablet1' - } - ] - }; - - const uploadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${storeDialogId}`; - const optionsUpload = { - 'method': 'POST', - 'url': uploadUri, - 'headers': { - 'Content-Type': 'multipart/form-data', - 'X-File-ModifiedDate': new Date(), - 'Cookie': cookie - }, - 'formData': { - 'document': { - 'value': JSON.stringify(templateJson), - 'options': { - 'filename': 'store.json', - 'contentType': null - } - }, - 'file[]': { - 'value': deliveryNote[0], - 'options': { - 'filename': 'deliveryNote.pdf', - 'contentType': deliveryNote[1] - } - } + // get ticket data + const ticket = await models.Ticket.findById(id, { + include: [{ + relation: 'client', + scope: { + fields: ['id', 'socialName', 'fi'] } - }; + }] + }); + const [taxes] = await models.Ticket.rawSql('CALL vn.ticketGetTaxAdd(?)', [id]); - try { - return new Promise((resolve, reject) => { - request.post(optionsUpload, function(error) { - if (error) return reject(error); - resolve(); - }); - }); - } catch (error) { - console.log(error); - return; - } - } catch (error) { - if (error.code === 'ENOENT') - throw new UserError('The DOCUWARE PDF document does not exists'); + // upload file + const templateJson = { + 'Fields': [ + { + 'FieldName': 'N__ALBAR_N', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 2531, + 'Top': 3645, + 'Width': 257, + 'Height': 230, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': id, + 'FieldValue': id + }, + { + 'FieldName': 'CIF_PROVEEDOR', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 6176, + 'Top': 4624, + 'Width': 839, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.client().fi, + 'FieldValue': ticket.client().fi + }, + { + 'FieldName': 'CODIGO_PROVEEDOR', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 2531, + 'Top': 3240, + 'Width': 514, + 'Height': 230, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.client().id, + 'FieldValue': ticket.client().id + }, + { + 'FieldName': 'NOMBRE_PROVEEDOR', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 6175, + 'Top': 4264, + 'Width': 858, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.client().socialName, + 'FieldValue': ticket.client().socialName + }, + { + 'FieldName': 'FECHA_FACTURA', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'date', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 2531, + 'Top': 4050, + 'Width': 1181, + 'Height': 230, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.shipped, + 'FieldValue': ticket.shipped + }, + { + 'FieldName': 'TIPO_IVA__1_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 9537, + 'Top': 10057, + 'Width': 615, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': taxes[0].rate, + 'FieldValue': taxes[0].rate + }, + { + 'FieldName': 'BASE_IMPONIBLE__1_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 8907, + 'Top': 10567, + 'Width': 419, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.totalWithoutVat, + 'FieldValue': ticket.totalWithoutVat + }, + { + 'FieldName': 'IMPORTE_IVA__1_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 10423, + 'Top': 10057, + 'Width': 419, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': taxes[0].tax, + 'FieldValue': taxes[0].tax + }, + { + 'FieldName': 'TIPO_IVA__2_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'BASE_IMPONIBLE__2_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'IMPORTE_IVA__2_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'TIPO_IVA__3_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'BASE_IMPONIBLE__3_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'IMPORTE_IVA__3_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'IRPF', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': null + }, + { + 'FieldName': 'TOTAL_FACTURA', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'Decimal', + 'PointAndShootInfo': { + 'Box': [ + { + 'Left': 10423, + 'Top': 10958, + 'Width': 419, + 'Height': 168, + 'PageNumber': 0 + } + ], + 'PageNumber': 0 + }, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': ticket.totalWithVat, + 'FieldValue': ticket.totalWithVat + }, + { + 'FieldName': 'ESTADO', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': 'Pendiente procesar', + 'FieldValue': 'Pendiente procesar' + }, + { + 'FieldName': 'URL', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': true, + 'Item': null, + 'FieldValue': '' + }, + { + 'FieldName': 'FIRMA_', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': 'Si', + 'FieldValue': 'Si' + }, + { + 'FieldName': 'FILTRO_TABLET', + 'ReadOnly': false, + 'SystemField': false, + 'ItemElementName': 'string', + 'PointAndShootInfo': null, + 'IsAutoNumber': false, + 'IsNull': false, + 'Item': 'Tablet1', + 'FieldValue': 'Tablet1' + } + ] + }; - throw error; - } + const uploadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${storeDialogId}`; + + const FormData = require('form-data'); + const data = new FormData(); + + data.append('document', JSON.stringify(templateJson), 'schema.json'); + data.append('file[]', deliveryNote[0], 'file.pdf'); + const uploadOptions = { + headers: { + 'Content-Type': 'multipart/form-data', + 'X-File-ModifiedDate': new Date(), + 'Cookie': cookie, + ...data.getHeaders() + }, + }; + + return await axios.post(uploadUri, data, uploadOptions) + .catch(() => { + throw new UserError('Failed to upload file'); + }); }; }; diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index 6b7fcd838..d8a869f8c 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -21,11 +21,11 @@ Add turn Show Delivery Note... - + @@ -41,11 +41,29 @@ translate> as CSV +
+ + Loading Docuware info +
+
+ + + as PDF + + + as CSV +
Send Delivery Note... @@ -55,6 +73,11 @@ translate> Send PDF + + Send PDF to Tablet + @@ -63,40 +86,6 @@
- - Docuware options... - - -
- - Loading Docuware info -
-
- - - Show PDF - - - Upload Salix PDF - - - Send PDF - - -
-
From a42723210f81f14463c78fa552504875460570b7 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 11 Jan 2023 09:13:18 +0100 Subject: [PATCH 56/72] refs #4951 delete function --- db/changes/230201/00-triggersXDiario.sql | 27 ++++-------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/db/changes/230201/00-triggersXDiario.sql b/db/changes/230201/00-triggersXDiario.sql index b31157a3f..5cf0b6253 100644 --- a/db/changes/230201/00-triggersXDiario.sql +++ b/db/changes/230201/00-triggersXDiario.sql @@ -11,7 +11,7 @@ BEGIN IF NEW.SUBCTA <=> '' THEN SET NEW.SUBCTA = NULL; END IF; - IF NEW.SUBCTA IS NOT NULL AND NOT util.checkStringLength(NEW.SUBCTA, 10) THEN + IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN CALL util.throw('INVALID_STRING_LENGTH'); END IF; END IF; @@ -19,7 +19,7 @@ BEGIN IF NEW.CONTRA <=> '' THEN SET NEW.CONTRA = NULL; END IF; - IF NEW.CONTRA IS NOT NULL AND NOT util.checkStringLength(NEW.CONTRA, 10) THEN + IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN CALL util.throw('INVALID_STRING_LENGTH'); END IF; END IF; @@ -54,13 +54,13 @@ BEGIN IF NEW.SUBCTA <=> '' THEN SET NEW.SUBCTA = NULL; END IF; - IF NEW.SUBCTA IS NOT NULL AND NOT util.checkStringLength(NEW.SUBCTA, 10) THEN + IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN CALL util.throw('INVALID_STRING_LENGTH'); END IF; IF NEW.CONTRA <=> '' THEN SET NEW.CONTRA = NULL; END IF; - IF NEW.CONTRA IS NOT NULL AND NOT util.checkStringLength(NEW.CONTRA, 10) THEN + IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN CALL util.throw('INVALID_STRING_LENGTH'); END IF; CALL XDiario_checkDate(NEW.FECHA); @@ -71,22 +71,3 @@ BEGIN END$$ DELIMITER ; - -DROP FUNCTION IF EXISTS `util`.`checkStringLength`; - -DELIMITER $$ -$$ -CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`checkStringLength`(vString VARCHAR(255), vLength INT(3)) RETURNS tinyint(1) - DETERMINISTIC -BEGIN -/** - * Comprueba la longitud de un string - * - * @param vString String a comprobar - * @param vLength Longitud que debe tener - * @return Devuelve TRUE/FALSE en caso de que tenga la longitud correcta o no - */ - RETURN LENGTH(vString) <=> vLength; -END$$ -DELIMITER ; - From 9f324483d6225aadb0002e7c9b6e5ed1141434db Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 11 Jan 2023 15:19:09 +0100 Subject: [PATCH 57/72] refactor(docuware): only use axios and refactor all --- back/methods/docuware/basic.js | 72 ++++++++++++++++ back/methods/docuware/checkFile.js | 75 +++++++---------- back/methods/docuware/download.js | 82 +++---------------- back/methods/docuware/specs/checkFile.spec.js | 2 +- back/methods/docuware/specs/download.spec.js | 2 +- back/methods/docuware/upload.js | 42 ++-------- back/models/docuware.js | 1 + back/models/docuware.json | 5 +- db/changes/230201/00-docuwareStore.sql | 15 +++- db/dump/fixtures.sql | 4 - .../ticket/front/descriptor-menu/index.html | 26 +++--- modules/ticket/front/descriptor-menu/index.js | 15 ++-- .../front/descriptor-menu/locale/es.yml | 4 +- 13 files changed, 159 insertions(+), 186 deletions(-) create mode 100644 back/methods/docuware/basic.js diff --git a/back/methods/docuware/basic.js b/back/methods/docuware/basic.js new file mode 100644 index 000000000..1f00e67e4 --- /dev/null +++ b/back/methods/docuware/basic.js @@ -0,0 +1,72 @@ +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(); + + 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 + } + }); + + 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 + * + * @param {string} cookie - The docuware cookie + * @return {object} - The headers + */ + Self.getOptions = async() => { + const docuwareConfig = await Self.app.models.DocuwareConfig.findOne(); + const headers = { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Cookie': docuwareConfig.token + } + }; + + return { + url: docuwareConfig.url, + headers + }; + }; +}; diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js index 38bc35183..c0fbbff8b 100644 --- a/back/methods/docuware/checkFile.js +++ b/back/methods/docuware/checkFile.js @@ -1,4 +1,4 @@ -const got = require('got'); +const axios = require('axios'); module.exports = Self => { Self.remoteMethodCtx('checkFile', { @@ -8,7 +8,7 @@ module.exports = Self => { { arg: 'id', type: 'number', - description: 'The id', + description: 'The id', http: {source: 'path'} }, { @@ -16,16 +16,10 @@ module.exports = Self => { type: 'string', required: true, description: 'The fileCabinet name' - }, - { - arg: 'dialog', - type: 'string', - required: true, - description: 'The dialog name' } ], returns: { - type: 'boolean', + type: 'object', root: true }, http: { @@ -34,64 +28,51 @@ module.exports = Self => { } }); - Self.checkFile = async function(ctx, id, fileCabinet, dialog) { - const myUserId = ctx.req.accessToken.userId; - if (!myUserId) - return false; - + Self.checkFile = async function(ctx, id, fileCabinet) { const models = Self.app.models; - const docuwareConfig = await models.DocuwareConfig.findOne(); + const action = 'find'; + const docuwareInfo = await models.Docuware.findOne({ where: { code: fileCabinet, - dialogName: dialog + action: action } }); - const docuwareUrl = docuwareConfig.url; - const cookie = docuwareConfig.token; - const fileCabinetName = docuwareInfo.fileCabinetName; - const find = docuwareInfo.find; - const options = { - 'headers': { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Cookie': cookie - } - }; const searchFilter = { condition: [ { - DBName: find, + DBName: docuwareInfo.findById, + Value: [id] } + ], + sortOrder: [ + { + Field: 'FILENAME', + Direction: 'Desc' + } ] }; try { - // get fileCabinetId - const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); - const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; - const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; + const options = await Self.getOptions(); - // get dialog - const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); - const dialogJson = JSON.parse(dialogResponse.body).Dialog; - const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id; + const fileCabinetId = await Self.getFileCabinet(fileCabinet); + const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId); - // get docuwareID - Object.assign(options, {'body': JSON.stringify(searchFilter)}); - const response = await got.post( - `${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options); - JSON.parse(response.body).Items[0].Id; + const response = await axios.post( + `${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, + searchFilter, + options.headers + ); + const [documents] = response.data.Items; + if (!documents) return false; - // const file = JSON.parse(response.body).Items[0]; - // const state = file.Fields.find(field => field.FieldName == 'ESTADO'); + const state = documents.Fields.find(field => field.FieldName == 'ESTADO'); + if (state != 'Firmado') return false; - // if (state != 'Firmado') - // return false; - - return true; + return {id: documents.Id}; } catch (error) { return false; } diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js index 29b2aefa2..7f5b71ffb 100644 --- a/back/methods/docuware/download.js +++ b/back/methods/docuware/download.js @@ -18,12 +18,6 @@ module.exports = Self => { type: 'string', description: 'The file cabinet', http: {source: 'path'} - }, - { - arg: 'dialog', - type: 'string', - description: 'The dialog', - http: {source: 'path'} } ], returns: [ @@ -42,79 +36,25 @@ module.exports = Self => { } ], http: { - path: `/:id/download/:fileCabinet/:dialog`, + path: `/:id/download/:fileCabinet`, verb: 'GET' } }); - Self.download = async function(ctx, id, fileCabinet, dialog) { - const myUserId = ctx.req.accessToken.userId; - if (!myUserId) - throw new UserError(`You don't have enough privileges`); - + Self.download = async function(ctx, id, fileCabinet) { const models = Self.app.models; - const docuwareConfig = await models.DocuwareConfig.findOne(); - const docuwareInfo = await models.Docuware.findOne({ - where: { - code: fileCabinet, - dialogName: dialog - } - }); + const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet); + if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists'); - const docuwareUrl = docuwareConfig.url; - const cookie = docuwareConfig.token; - const fileCabinetName = docuwareInfo.fileCabinetName; - const find = docuwareInfo.find; - const options = { - 'headers': { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Cookie': cookie - } - }; - const searchFilter = { - condition: [ - { - DBName: find, - Value: [id] - } - ] - }; + const fileCabinetId = await Self.getFileCabinet(fileCabinet); + const options = await Self.getOptions(); - try { - // get fileCabinetId - const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); - const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; - const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; + const fileName = `filename="${id}.pdf"`; + const contentType = 'application/pdf'; + const downloadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`; - // get dialog - const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); - const dialogJson = JSON.parse(dialogResponse.body).Dialog; - const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id; + const stream = got.stream(downloadUri, options.headers); - // get docuwareID - Object.assign(options, {'body': JSON.stringify(searchFilter)}); - const response = await got.post(`${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options); - const docuwareId = JSON.parse(response.body).Items[0].Id; - - // download & save file - const fileName = `filename="${id}.pdf"`; - const contentType = 'application/pdf'; - const downloadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents/${docuwareId}/FileDownload?targetFileType=Auto&keepAnnotations=false`; - const downloadOptions = { - 'headers': { - 'Cookie': cookie - } - }; - - const stream = got.stream(downloadUri, downloadOptions); - - return [stream, contentType, fileName]; - } catch (error) { - if (error.code === 'ENOENT') - throw new UserError('The DOCUWARE PDF document does not exists'); - - throw error; - } + return [stream, contentType, fileName]; }; }; diff --git a/back/methods/docuware/specs/checkFile.spec.js b/back/methods/docuware/specs/checkFile.spec.js index 0d0e4d71a..1f4308190 100644 --- a/back/methods/docuware/specs/checkFile.spec.js +++ b/back/methods/docuware/specs/checkFile.spec.js @@ -1,7 +1,7 @@ const models = require('vn-loopback/server/server').models; const got = require('got'); -describe('docuware download()', () => { +xdescribe('docuware download()', () => { const ticketId = 1; const userId = 9; const ctx = { diff --git a/back/methods/docuware/specs/download.spec.js b/back/methods/docuware/specs/download.spec.js index dc80c67d8..f373b34a1 100644 --- a/back/methods/docuware/specs/download.spec.js +++ b/back/methods/docuware/specs/download.spec.js @@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models; const got = require('got'); const stream = require('stream'); -describe('docuware download()', () => { +xdescribe('docuware download()', () => { const userId = 9; const ticketId = 1; const ctx = { diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js index 687c82e2b..49dcc2489 100644 --- a/back/methods/docuware/upload.js +++ b/back/methods/docuware/upload.js @@ -31,41 +31,15 @@ module.exports = Self => { } }); - Self.upload = async function(ctx, id, fileCabinet, dialog) { - const myUserId = ctx.req.accessToken.userId; - if (!myUserId) - throw new UserError(`You don't have enough privileges`); - + Self.upload = async function(ctx, id, fileCabinet) { const models = Self.app.models; - const docuwareConfig = await models.DocuwareConfig.findOne(); - const docuwareInfo = await models.Docuware.findOne({ - where: { - code: fileCabinet, - dialogName: dialog - } - }); + const action = 'store'; - const docuwareUrl = docuwareConfig.url; - const cookie = docuwareConfig.token; - const fileCabinetName = docuwareInfo.fileCabinetName; - const options = { - 'headers': { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Cookie': cookie - } - }; - - // get fileCabinetId - const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); - const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet; - const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id; - - // get dialog - const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); - const dialogJson = JSON.parse(dialogResponse.body).Dialog; - const storeDialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'Archivar').Id; + const options = await Self.getOptions(); + const fileCabinetId = await Self.getFileCabinet(fileCabinet); + const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId); + // get delivery note const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { id, type: 'deliveryNote' @@ -407,7 +381,7 @@ module.exports = Self => { ] }; - const uploadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${storeDialogId}`; + const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; const FormData = require('form-data'); const data = new FormData(); @@ -418,7 +392,7 @@ module.exports = Self => { headers: { 'Content-Type': 'multipart/form-data', 'X-File-ModifiedDate': new Date(), - 'Cookie': cookie, + 'Cookie': options.headers.headers.Cookie, ...data.getHeaders() }, }; diff --git a/back/models/docuware.js b/back/models/docuware.js index 0b29494eb..de057e7ec 100644 --- a/back/models/docuware.js +++ b/back/models/docuware.js @@ -3,4 +3,5 @@ module.exports = Self => { require('../methods/docuware/upload')(Self); require('../methods/docuware/checkFile')(Self); require('../methods/docuware/deliveryNoteEmail')(Self); + require('../methods/docuware/basic')(Self); }; diff --git a/back/models/docuware.json b/back/models/docuware.json index b7e1c57e5..dec20eede 100644 --- a/back/models/docuware.json +++ b/back/models/docuware.json @@ -19,10 +19,13 @@ "fileCabinetName": { "type": "string" }, + "action": { + "type": "string" + }, "dialogName": { "type": "string" }, - "find": { + "findById": { "type": "string" } } diff --git a/db/changes/230201/00-docuwareStore.sql b/db/changes/230201/00-docuwareStore.sql index a9fa01c76..6dfee7a86 100644 --- a/db/changes/230201/00-docuwareStore.sql +++ b/db/changes/230201/00-docuwareStore.sql @@ -1,6 +1,17 @@ -INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName`, `find`) +CREATE OR REPLACE TABLE `vn`.`docuware` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(50) COLLATE utf8mb3_unicode_ci NOT NULL, + `fileCabinetName` varchar(50) COLLATE utf8mb3_unicode_ci NOT NULL, + `action` varchar(255) COLLATE utf8mb3_unicode_ci NOT NULL, + `dialogName` varchar(100) COLLATE utf8mb3_unicode_ci NOT NULL, + `findById` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `action`, `dialogName`, `findById`) VALUES - ('deliveryClient', 'Albaranes cliente', 'storeTicket', 'N__ALBAR_N'); + ('deliveryNote', 'Albaranes cliente', 'find', 'find', 'N__ALBAR_N'), + ('deliveryNote', 'Albaranes cliente', 'store', 'Archivar', 'N__ALBAR_N'); INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) VALUES diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 1ea4fa114..5ef418e0c 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2580,10 +2580,6 @@ INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`) (4, 33.8, util.VN_CURDATE(), 1, 1101), (30, 34.4, util.VN_CURDATE(), 1, 1108); -INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName` , `find`) - VALUES - ('deliveryClient', 'deliveryClient', 'findTicket', 'word'); - INSERT INTO `vn`.`docuwareConfig` (`url`) VALUES ('https://verdnatura.docuware.cloud/docuware/platform'); diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index d8a869f8c..e9e99fc3e 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -25,7 +25,7 @@ translate> Show Delivery Note... - + @@ -36,23 +36,12 @@ translate> as PDF without prices - - as CSV - -
- - Loading Docuware info -
-
- - as PDF + as PDF signed - Send PDF to Tablet + Send PDF to tablet + + + + diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 11d07d3bf..c87d7f7d0 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -304,21 +304,18 @@ class Controller extends Section { } hasDocuware() { - const params = { - fileCabinet: 'deliveryClient', - dialog: 'findTicket' - }; - this.isLoadingDocuware = true; - this.$http.post(`Docuwares/${this.id}/checkFile`, params) + this.$http.post(`Docuwares/${this.id}/checkFile`, {fileCabinet: 'deliveryNote'}) .then(res => { this.hasDocuwareFile = res.data; - this.isLoadingDocuware = false; }); } uploadDocuware() { - return this.$http.post(`Docuwares/${this.id}/upload`, {fileCabinet: 'deliveryClient', dialog: 'storeTicket'}) - .then(() => this.vnApp.showSuccess(this.$t('PDF uploaded!'))); + return this.$http.post(`Docuwares/${this.id}/upload`, {fileCabinet: 'deliveryNote'}) + .then(() => { + this.vnApp.showSuccess(this.$t('PDF sent!')); + this.$.balanceCreate.show(); + }); } sendDocuwarePdfDeliveryNote($data) { diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index 3f06d4378..82425864c 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -1,10 +1,11 @@ Show Delivery Note...: Ver albarán... Send Delivery Note...: Enviar albarán... as PDF: como PDF -as PDF Docuware: como PDF Docuware +as PDF signed: como PDF firmado as CSV: como CSV as PDF without prices: como PDF sin precios Send PDF: Enviar PDF +Send PDF to tablet: Enviar PDF a tablet Send CSV: Enviar CSV Send CSV Delivery Note: Enviar albarán en CSV Send PDF Delivery Note: Enviar albarán en PDF @@ -14,3 +15,4 @@ Invoice sent: Factura enviada The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" Transfer client: Transferir cliente SMS Notify changes: SMS Notificar cambios +PDF sent!: ¡PDF enviado! From d448f41ac159493dcfe6de3d704500517d870fed Mon Sep 17 00:00:00 2001 From: guillermo Date: Wed, 11 Jan 2023 15:39:33 +0100 Subject: [PATCH 58/72] refs #4975 Changed description of last method --- modules/mdb/back/methods/mdbVersion/last.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mdb/back/methods/mdbVersion/last.js b/modules/mdb/back/methods/mdbVersion/last.js index 802899f76..5f89f10fb 100644 --- a/modules/mdb/back/methods/mdbVersion/last.js +++ b/modules/mdb/back/methods/mdbVersion/last.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('last', { - description: 'Upload and attach a access file', + description: 'Gets the latest version of a access file', accepts: [ { arg: 'appName', From ed90957ab594dcd19a6f2328839114c263817a17 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 12 Jan 2023 11:45:11 +0100 Subject: [PATCH 59/72] fix: corregida primaryKey de mdbApp --- db/changes/225202/00-mdbApp.sql | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 db/changes/225202/00-mdbApp.sql diff --git a/db/changes/225202/00-mdbApp.sql b/db/changes/225202/00-mdbApp.sql new file mode 100644 index 000000000..50c595d71 --- /dev/null +++ b/db/changes/225202/00-mdbApp.sql @@ -0,0 +1,28 @@ +ALTER TABLE `vn`.`mdbApp` DROP PRIMARY KEY; +ALTER TABLE `vn`.`mdbApp` ADD CONSTRAINT mdbApp_PK PRIMARY KEY (app,baselineBranchFk); + +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('com','master'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('enc','master'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('ent','master'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('eti','master'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('lab','master'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('tpv','master'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('com','dev'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('enc','dev'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('ent','dev'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('eti','dev'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('lab','dev'); +INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk) + VALUES ('tpv','dev'); + From b6de50848a634d78ff8bf0208b78a7f44406726f Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 12 Jan 2023 11:48:44 +0100 Subject: [PATCH 60/72] refs #5081 fix sms faltas --- front/salix/components/sendSms/locale/es.yml | 2 +- modules/route/front/sms/index.js | 2 +- modules/ticket/front/sale/index.html | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/front/salix/components/sendSms/locale/es.yml b/front/salix/components/sendSms/locale/es.yml index 64c3fcca6..94ab8e588 100644 --- a/front/salix/components/sendSms/locale/es.yml +++ b/front/salix/components/sendSms/locale/es.yml @@ -1,7 +1,7 @@ Send SMS: Enviar SMS Destination: Destinatario Message: Mensaje -SMS sent!: ¡SMS enviado! +SMS sent: ¡SMS enviado! Characters remaining: Carácteres restantes The destination can't be empty: El destinatario no puede estar vacio The message can't be empty: El mensaje no puede estar vacio diff --git a/modules/route/front/sms/index.js b/modules/route/front/sms/index.js index d8b1fc134..f466adea7 100644 --- a/modules/route/front/sms/index.js +++ b/modules/route/front/sms/index.js @@ -26,7 +26,7 @@ class Controller extends Component { throw new Error(`The message it's too long`); this.$http.post(`Routes/sendSms`, this.sms).then(res => { - this.vnApp.showMessage(this.$t('SMS sent!')); + this.vnApp.showMessage(this.$t('SMS sent')); if (res.data) this.emit('send', {response: res.data}); }); diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index c624b1a95..bb52089b3 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -441,10 +441,10 @@ - - + Date: Thu, 12 Jan 2023 11:52:27 +0100 Subject: [PATCH 61/72] fix: comprueba que hay registros --- modules/mdb/back/methods/mdbVersion/upload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mdb/back/methods/mdbVersion/upload.js b/modules/mdb/back/methods/mdbVersion/upload.js index 5dfe5d3ef..ea88c58f7 100644 --- a/modules/mdb/back/methods/mdbVersion/upload.js +++ b/modules/mdb/back/methods/mdbVersion/upload.js @@ -65,7 +65,7 @@ module.exports = Self => { try { const mdbApp = await models.MdbApp.findById(appName, null, myOptions); - if (mdbApp.locked && mdbApp.userFk != userId) { + if (mdbApp && mdbApp.locked && mdbApp.userFk != userId) { throw new UserError($t('App locked', { userId: mdbApp.userFk })); From 2678d5919167fe788f325a42954c26a279a52acb Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 12 Jan 2023 11:57:20 +0100 Subject: [PATCH 62/72] front test expected old value --- modules/route/front/sms/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/route/front/sms/index.spec.js b/modules/route/front/sms/index.spec.js index 42bf30931..8bf35e673 100644 --- a/modules/route/front/sms/index.spec.js +++ b/modules/route/front/sms/index.spec.js @@ -30,7 +30,7 @@ describe('Route', () => { controller.onResponse(); $httpBackend.flush(); - expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent!'); + expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent'); }); it('should call onResponse without the destination and show an error snackbar', () => { From d5a7691b9c16816072f9fbc23d4a583b668b5a96 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 12 Jan 2023 12:56:51 +0100 Subject: [PATCH 63/72] add controller function --- modules/ticket/front/sale/index.html | 3 ++- modules/ticket/front/sale/index.js | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index bb52089b3..d65a83c58 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -443,7 +443,8 @@ + sms="$ctrl.newSMS" + on-send="$ctrl.onSmsSend($sms)"> this.vnApp.showSuccess(this.$t('SMS sent'))); + } + /** * Inserts a new instance */ From 54356ece7d3b9fc0b0aec3e5e2eb8a0ef6b27a8b Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 12 Jan 2023 13:53:38 +0100 Subject: [PATCH 64/72] fix(monitor): refresh monitor when using ng-for track Refs: #5083 --- .../monitor/front/index/tickets/index.html | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/modules/monitor/front/index/tickets/index.html b/modules/monitor/front/index/tickets/index.html index 2f7c34e2d..b8559154e 100644 --- a/modules/monitor/front/index/tickets/index.html +++ b/modules/monitor/front/index/tickets/index.html @@ -79,51 +79,51 @@ @@ -133,64 +133,64 @@ - {{::ticket.id}} + {{ticket.id}} - {{::ticket.nickname}} + {{ticket.nickname}} - {{::ticket.userName | dashIfEmpty}} + {{ticket.userName | dashIfEmpty}} - - {{::ticket.shippedDate | date: 'dd/MM/yyyy'}} + + {{ticket.shippedDate | date: 'dd/MM/yyyy'}} - {{::ticket.zoneLanding | date: 'HH:mm'}} - {{::ticket.practicalHour | date: 'HH:mm'}} - {{::ticket.shipped | date: 'HH:mm'}} - {{::ticket.province}} + {{ticket.zoneLanding | date: 'HH:mm'}} + {{ticket.practicalHour | date: 'HH:mm'}} + {{ticket.shipped | date: 'HH:mm'}} + {{ticket.province}} - {{::ticket.refFk}} + {{ticket.refFk}} - {{::ticket.state}} + ng-show="!ticket.refFk" + class="chip {{ticket.classColor}}"> + {{ticket.state}} - {{::ticket.zoneName | dashIfEmpty}} + {{ticket.zoneName | dashIfEmpty}} - - {{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}} + + {{(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}} Date: Thu, 12 Jan 2023 15:16:38 +0100 Subject: [PATCH 65/72] test(docuware): back tests --- back/methods/docuware/basic.js | 6 + back/methods/docuware/checkFile.js | 2 +- back/methods/docuware/deliveryNoteEmail.js | 2 +- back/methods/docuware/download.js | 7 +- back/methods/docuware/specs/checkFile.spec.js | 104 ++++++++++-------- back/methods/docuware/specs/download.spec.js | 55 +++++---- back/methods/docuware/specs/upload.spec.js | 35 ++++++ back/methods/docuware/upload.js | 22 +++- loopback/locale/es.json | 6 +- modules/ticket/front/descriptor-menu/index.js | 12 +- package-lock.json | 28 +++-- package.json | 2 +- 12 files changed, 181 insertions(+), 100 deletions(-) create mode 100644 back/methods/docuware/specs/upload.spec.js diff --git a/back/methods/docuware/basic.js b/back/methods/docuware/basic.js index 1f00e67e4..5c1d7bfa9 100644 --- a/back/methods/docuware/basic.js +++ b/back/methods/docuware/basic.js @@ -20,6 +20,9 @@ module.exports = Self => { 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; @@ -41,6 +44,9 @@ module.exports = Self => { } }); + // 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; diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js index c0fbbff8b..c5c4abec0 100644 --- a/back/methods/docuware/checkFile.js +++ b/back/methods/docuware/checkFile.js @@ -70,7 +70,7 @@ module.exports = Self => { if (!documents) return false; const state = documents.Fields.find(field => field.FieldName == 'ESTADO'); - if (state != 'Firmado') return false; + if (state.Item != 'Firmado') return false; return {id: documents.Id}; } catch (error) { diff --git a/back/methods/docuware/deliveryNoteEmail.js b/back/methods/docuware/deliveryNoteEmail.js index afb8bf980..1f9d7556f 100644 --- a/back/methods/docuware/deliveryNoteEmail.js +++ b/back/methods/docuware/deliveryNoteEmail.js @@ -59,7 +59,7 @@ module.exports = Self => { const email = new Email('delivery-note', params); - const docuwareFile = await Self.app.models.Docuware.download(ctx, id, 'deliveryClient', 'findTicket'); + const docuwareFile = await Self.app.models.Docuware.download(ctx, id, 'deliveryNote'); return email.send({ overrideAttachments: true, diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js index 7f5b71ffb..880b51b34 100644 --- a/back/methods/docuware/download.js +++ b/back/methods/docuware/download.js @@ -1,5 +1,5 @@ /* eslint max-len: ["error", { "code": 180 }]*/ -const got = require('got'); +const axios = require('axios'); const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { @@ -48,13 +48,14 @@ module.exports = Self => { const fileCabinetId = await Self.getFileCabinet(fileCabinet); const options = await Self.getOptions(); + options.headers.responseType = 'stream'; const fileName = `filename="${id}.pdf"`; const contentType = 'application/pdf'; const downloadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`; - const stream = got.stream(downloadUri, options.headers); + const stream = await axios.get(downloadUri, options.headers); - return [stream, contentType, fileName]; + return [stream.data, contentType, fileName]; }; }; diff --git a/back/methods/docuware/specs/checkFile.spec.js b/back/methods/docuware/specs/checkFile.spec.js index 1f4308190..3cb1a4074 100644 --- a/back/methods/docuware/specs/checkFile.spec.js +++ b/back/methods/docuware/specs/checkFile.spec.js @@ -1,7 +1,7 @@ const models = require('vn-loopback/server/server').models; -const got = require('got'); +const axios = require('axios'); -xdescribe('docuware download()', () => { +fdescribe('docuware download()', () => { const ticketId = 1; const userId = 9; const ctx = { @@ -12,53 +12,71 @@ xdescribe('docuware download()', () => { } }; - const fileCabinetName = 'deliveryClient'; - const dialogDisplayName = 'find'; - const dialogName = 'findTicket'; + const docuwareModel = models.Docuware; + const fileCabinetName = 'deliveryNote'; - const gotGetResponse = { - body: JSON.stringify( - { - FileCabinet: [ - {Id: 12, Name: fileCabinetName} - ], - Dialog: [ - {Id: 34, DisplayName: dialogDisplayName} - ] - }) - }; - - it('should return exist file in docuware', async() => { - const gotPostResponse = { - body: JSON.stringify( - { - Items: [ - {Id: 56} - ], - }) - }; - - spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); - spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse))); - - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName); - - expect(result).toEqual(true); + 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 not exist file in docuware', async() => { - const gotPostResponse = { - body: JSON.stringify( - { - Items: [], - }) + 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(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); - spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse))); - - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName); + const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName); 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); + + expect(result).toEqual(false); + }); + + it('should return the document data', async() => { + const docuwareId = 1; + const response = { + data: { + Items: [ + { + Id: docuwareId, + Fields: [ + { + FieldName: 'ESTADO', + Item: 'Firmado' + } + ] + } + ] + } + }; + spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response))); + + const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName); + + expect(result.id).toEqual(docuwareId); + }); }); diff --git a/back/methods/docuware/specs/download.spec.js b/back/methods/docuware/specs/download.spec.js index f373b34a1..b1eff2c0d 100644 --- a/back/methods/docuware/specs/download.spec.js +++ b/back/methods/docuware/specs/download.spec.js @@ -1,8 +1,8 @@ const models = require('vn-loopback/server/server').models; -const got = require('got'); +const axios = require('axios'); const stream = require('stream'); -xdescribe('docuware download()', () => { +fdescribe('docuware download()', () => { const userId = 9; const ticketId = 1; const ctx = { @@ -13,36 +13,33 @@ xdescribe('docuware download()', () => { } }; - it('should return the downloaded file name', async() => { - const fileCabinetName = 'deliveryClient'; - const dialogDisplayName = 'find'; - const dialogName = 'findTicket'; - const gotGetResponse = { - body: JSON.stringify( - { - FileCabinet: [ - {Id: 12, Name: fileCabinetName} - ], - Dialog: [ - {Id: 34, DisplayName: dialogDisplayName} - ] - }) - }; + const docuwareModel = models.Docuware; + const fileCabinetName = 'deliveryNote'; - const gotPostResponse = { - body: JSON.stringify( - { - Items: [ - {Id: 56} - ], - }) - }; + beforeAll(() => { + spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); + spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); + }); - spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); - spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse))); - spyOn(got, 'stream').and.returnValue(new stream.PassThrough({objectMode: true})); + it('should return error if file not exist', async() => { + spyOn(docuwareModel, 'checkFile').and.returnValue(false); + spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true})); - const result = await models.Docuware.download(ctx, ticketId, fileCabinetName, dialogName); + let error; + try { + await models.Docuware.download(ctx, ticketId, fileCabinetName); + } catch (e) { + error = e.message; + } + + expect(error).toEqual('The DOCUWARE PDF document does not exists'); + }); + + it('should return the downloaded file if exist file ', async() => { + spyOn(docuwareModel, 'checkFile').and.returnValue({}); + spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true})); + + const result = await models.Docuware.download(ctx, ticketId, fileCabinetName); expect(result[1]).toEqual('application/pdf'); expect(result[2]).toEqual(`filename="${ticketId}.pdf"`); diff --git a/back/methods/docuware/specs/upload.spec.js b/back/methods/docuware/specs/upload.spec.js new file mode 100644 index 000000000..c46dc01fd --- /dev/null +++ b/back/methods/docuware/specs/upload.spec.js @@ -0,0 +1,35 @@ +const models = require('vn-loopback/server/server').models; + +fdescribe('docuware download()', () => { + const userId = 9; + const ticketId = 1; + 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 try upload file', async() => { + spyOn(docuwareModel, 'checkFile').and.returnValue(false); + spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true})); + + let error; + try { + await models.Docuware.download(ctx, ticketId, fileCabinetName); + } catch (e) { + error = e.message; + } + + expect(error).toEqual('The DOCUWARE PDF document does not exists'); + }); +}); diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js index 49dcc2489..23aa2d8d0 100644 --- a/back/methods/docuware/upload.js +++ b/back/methods/docuware/upload.js @@ -1,4 +1,3 @@ -const got = require('got'); const UserError = require('vn-loopback/util/user-error'); const axios = require('axios'); @@ -381,7 +380,26 @@ module.exports = Self => { ] }; - const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; + // if (process.env.NODE_ENV != 'production') + // throw new UserError('Action not allowed on the test environment'); + const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet); + + console.log(docuwareFile, id, fileCabinet); + // replace + if (docuwareFile) { + console.log(docuwareFile); + const uri = `${options.url}/FileCabinets/${fileCabinetId}/Sections?DocId=${docuwareFile.id}`; + console.log(uri); + return await axios.post(uri, deliveryNote[0], {headers: { + 'Content-Type': 'application/pdf', + 'Content-Disposition': 'file; filename="10.pdf"', + 'X-File-ModifiedDate': '2020-08-26T00:00:00.000Z' + } + + }); + } + + let uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; const FormData = require('form-data'); const data = new FormData(); diff --git a/loopback/locale/es.json b/loopback/locale/es.json index ea83b36c4..ad845cb38 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -135,7 +135,7 @@ "Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})", "Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})", "Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}", - "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*", + "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*", "Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*", "Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", @@ -252,5 +252,5 @@ "Receipt's bank was not found": "No se encontró el banco del recibo", "This receipt was not compensated": "Este recibo no ha sido compensado", "Client's email was not found": "No se encontró el email del cliente", - "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9" -} \ No newline at end of file + "Failed to upload file": "Error al subir archivo" +} diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index c87d7f7d0..14d884ba7 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -141,7 +141,10 @@ class Controller extends Section { } sendPdfDeliveryNote($data) { - return this.vnEmail.send(`tickets/${this.id}/delivery-note-email`, { + let query = `tickets/${this.id}/delivery-note-email`; + if (this.hasDocuwareFile) query = `docuwares/${this.id}/delivery-note-email`; + + return this.vnEmail.send(query, { recipientId: this.ticket.client.id, recipient: $data.email }); @@ -317,13 +320,6 @@ class Controller extends Section { this.$.balanceCreate.show(); }); } - - sendDocuwarePdfDeliveryNote($data) { - return this.vnEmail.send(`Docuwares/${this.id}/delivery-note-email`, { - recipientId: this.ticket.client.id, - recipient: $data.email - }); - } } Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail']; diff --git a/package-lock.json b/package-lock.json index 550a1ec76..31820196f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "9.0.0", "license": "GPL-3.0", "dependencies": { - "axios": "^0.25.0", + "axios": "^1.2.2", "bcrypt": "^5.0.1", "bmp-js": "^0.1.0", "compression": "^1.7.3", @@ -3893,10 +3893,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "0.25.0", - "license": "MIT", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz", + "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==", "dependencies": { - "follow-redirects": "^1.14.7" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/babel-jest": { @@ -8401,14 +8404,15 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.9", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -28842,9 +28846,13 @@ "version": "1.11.0" }, "axios": { - "version": "0.25.0", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz", + "integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==", "requires": { - "follow-redirects": "^1.14.7" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "babel-jest": { @@ -31964,7 +31972,9 @@ } }, "follow-redirects": { - "version": "1.14.9" + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, "for-in": { "version": "1.0.2", diff --git a/package.json b/package.json index 8cc33526d..9633751a0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node": ">=14" }, "dependencies": { - "axios": "^0.25.0", + "axios": "^1.2.2", "bcrypt": "^5.0.1", "bmp-js": "^0.1.0", "compression": "^1.7.3", From def41c011c79d03dae71bc70f796e9677d6fa7f8 Mon Sep 17 00:00:00 2001 From: alexm Date: Fri, 13 Jan 2023 13:33:59 +0100 Subject: [PATCH 66/72] feat(docuware_upload): send to trash last file --- back/methods/docuware/checkFile.js | 10 +++++-- back/methods/docuware/{basic.js => core.js} | 10 +++---- back/methods/docuware/download.js | 2 +- back/methods/docuware/specs/checkFile.spec.js | 8 +++--- back/methods/docuware/specs/download.spec.js | 2 +- back/methods/docuware/specs/upload.spec.js | 20 +++++++------- back/methods/docuware/upload.js | 27 +++++++------------ back/models/docuware-config.json | 4 +-- back/models/docuware.js | 2 +- db/changes/230201/00-docuwareStore.sql | 2 ++ db/dump/fixtures.sql | 2 +- db/export-data.sh | 1 + loopback/locale/es.json | 5 ++-- .../ticket/front/descriptor-menu/index.html | 10 ++++++- modules/ticket/front/descriptor-menu/index.js | 7 +++-- .../front/descriptor-menu/index.spec.js | 27 ++++++++++++++++++- .../front/descriptor-menu/locale/es.yml | 2 ++ 17 files changed, 92 insertions(+), 49 deletions(-) rename back/methods/docuware/{basic.js => core.js} (91%) diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js index c5c4abec0..c0a4e8ef3 100644 --- a/back/methods/docuware/checkFile.js +++ b/back/methods/docuware/checkFile.js @@ -16,6 +16,12 @@ module.exports = Self => { type: 'string', required: true, description: 'The fileCabinet name' + }, + { + arg: 'signed', + type: 'boolean', + required: true, + description: 'If pdf is necessary to be signed' } ], returns: { @@ -28,7 +34,7 @@ module.exports = Self => { } }); - Self.checkFile = async function(ctx, id, fileCabinet) { + Self.checkFile = async function(ctx, id, fileCabinet, signed) { const models = Self.app.models; const action = 'find'; @@ -70,7 +76,7 @@ module.exports = Self => { if (!documents) return false; const state = documents.Fields.find(field => field.FieldName == 'ESTADO'); - if (state.Item != 'Firmado') return false; + if (signed && state.Item != 'Firmado') return false; return {id: documents.Id}; } catch (error) { diff --git a/back/methods/docuware/basic.js b/back/methods/docuware/core.js similarity index 91% rename from back/methods/docuware/basic.js rename to back/methods/docuware/core.js index 5c1d7bfa9..2053ddf85 100644 --- a/back/methods/docuware/basic.js +++ b/back/methods/docuware/core.js @@ -20,8 +20,8 @@ module.exports = Self => { const options = await Self.getOptions(); - // if (!process.env.NODE_ENV) - // return Math.round(); + 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; @@ -44,8 +44,8 @@ module.exports = Self => { } }); - // if (!process.env.NODE_ENV) - // return Math.round(); + if (!process.env.NODE_ENV) + return Math.round(); const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers); const fileCabinets = fileCabinetResponse.data.FileCabinet; @@ -66,7 +66,7 @@ module.exports = Self => { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', - 'Cookie': docuwareConfig.token + 'Cookie': docuwareConfig.cookie } }; diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js index 880b51b34..56d006ee7 100644 --- a/back/methods/docuware/download.js +++ b/back/methods/docuware/download.js @@ -43,7 +43,7 @@ module.exports = Self => { Self.download = async function(ctx, id, fileCabinet) { const models = Self.app.models; - const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet); + const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true); 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 3cb1a4074..dd11951cc 100644 --- a/back/methods/docuware/specs/checkFile.spec.js +++ b/back/methods/docuware/specs/checkFile.spec.js @@ -1,7 +1,7 @@ const models = require('vn-loopback/server/server').models; const axios = require('axios'); -fdescribe('docuware download()', () => { +describe('docuware download()', () => { const ticketId = 1; const userId = 9; const ctx = { @@ -28,7 +28,7 @@ fdescribe('docuware download()', () => { }; spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response))); - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName); + const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true); expect(result).toEqual(false); }); @@ -51,7 +51,7 @@ fdescribe('docuware download()', () => { }; spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response))); - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName); + const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true); expect(result).toEqual(false); }); @@ -75,7 +75,7 @@ fdescribe('docuware download()', () => { }; spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response))); - const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName); + const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true); expect(result.id).toEqual(docuwareId); }); diff --git a/back/methods/docuware/specs/download.spec.js b/back/methods/docuware/specs/download.spec.js index b1eff2c0d..fcc1671a6 100644 --- a/back/methods/docuware/specs/download.spec.js +++ b/back/methods/docuware/specs/download.spec.js @@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models; const axios = require('axios'); const stream = require('stream'); -fdescribe('docuware download()', () => { +describe('docuware download()', () => { const userId = 9; const ticketId = 1; const ctx = { diff --git a/back/methods/docuware/specs/upload.spec.js b/back/methods/docuware/specs/upload.spec.js index c46dc01fd..7ac873e95 100644 --- a/back/methods/docuware/specs/upload.spec.js +++ b/back/methods/docuware/specs/upload.spec.js @@ -1,35 +1,37 @@ const models = require('vn-loopback/server/server').models; -fdescribe('docuware download()', () => { +describe('docuware upload()', () => { const userId = 9; - const ticketId = 1; + const ticketId = 10; const ctx = { req: { - + getLocale: () => { + return 'en'; + }, accessToken: {userId: userId}, headers: {origin: 'http://localhost:5000'}, } }; const docuwareModel = models.Docuware; + const ticketModel = models.Ticket; 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())))); + spyOn(docuwareModel, 'getFileCabinet').and.returnValue(new Promise(resolve => resolve(Math.random()))); + spyOn(docuwareModel, 'getDialog').and.returnValue(new Promise(resolve => resolve(Math.random()))); }); it('should try upload file', async() => { - spyOn(docuwareModel, 'checkFile').and.returnValue(false); - spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true})); + spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({}))); let error; try { - await models.Docuware.download(ctx, ticketId, fileCabinetName); + await models.Docuware.upload(ctx, ticketId, fileCabinetName); } catch (e) { error = e.message; } - expect(error).toEqual('The DOCUWARE PDF document does not exists'); + expect(error).toEqual('Action not allowed on the test environment'); }); }); diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js index 23aa2d8d0..76067e84a 100644 --- a/back/methods/docuware/upload.js +++ b/back/methods/docuware/upload.js @@ -380,27 +380,20 @@ module.exports = Self => { ] }; - // if (process.env.NODE_ENV != 'production') - // throw new UserError('Action not allowed on the test environment'); - const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet); + if (process.env.NODE_ENV != 'production') + throw new UserError('Action not allowed on the test environment'); - console.log(docuwareFile, id, fileCabinet); - // replace + // delete old + const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false); if (docuwareFile) { - console.log(docuwareFile); - const uri = `${options.url}/FileCabinets/${fileCabinetId}/Sections?DocId=${docuwareFile.id}`; - console.log(uri); - return await axios.post(uri, deliveryNote[0], {headers: { - 'Content-Type': 'application/pdf', - 'Content-Disposition': 'file; filename="10.pdf"', - 'X-File-ModifiedDate': '2020-08-26T00:00:00.000Z' - } - - }); + const deleteJson = { + 'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}] + }; + const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`; + await axios.put(deleteUri, deleteJson, options.headers); } - let uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; - + const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; const FormData = require('form-data'); const data = new FormData(); diff --git a/back/models/docuware-config.json b/back/models/docuware-config.json index 8ca76d8ba..9d06c4874 100644 --- a/back/models/docuware-config.json +++ b/back/models/docuware-config.json @@ -16,7 +16,7 @@ "url": { "type": "string" }, - "token": { + "cookie": { "type": "string" } }, @@ -29,4 +29,4 @@ "permission": "ALLOW" } ] -} \ No newline at end of file +} diff --git a/back/models/docuware.js b/back/models/docuware.js index de057e7ec..b983f7bb4 100644 --- a/back/models/docuware.js +++ b/back/models/docuware.js @@ -3,5 +3,5 @@ module.exports = Self => { require('../methods/docuware/upload')(Self); require('../methods/docuware/checkFile')(Self); require('../methods/docuware/deliveryNoteEmail')(Self); - require('../methods/docuware/basic')(Self); + require('../methods/docuware/core')(Self); }; diff --git a/db/changes/230201/00-docuwareStore.sql b/db/changes/230201/00-docuwareStore.sql index 6dfee7a86..b20c2554f 100644 --- a/db/changes/230201/00-docuwareStore.sql +++ b/db/changes/230201/00-docuwareStore.sql @@ -19,3 +19,5 @@ INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`princip ('Docuware','download','READ','ALLOW','salesPerson'), ('Docuware','upload','WRITE','ALLOW','productionAssi'), ('Docuware','deliveryNoteEmail','WRITE','ALLOW','salesPerson'); + +ALTER TABLE `vn`.`docuwareConfig` CHANGE token cookie varchar(1000) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 8ac4dc5b5..b80b781da 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2582,7 +2582,7 @@ INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`) INSERT INTO `vn`.`docuwareConfig` (`url`) VALUES - ('https://verdnatura.docuware.cloud/docuware/platform'); + ('http://docuware.url/'); INSERT INTO `vn`.`calendarHolidaysName` (`id`, `name`) VALUES diff --git a/db/export-data.sh b/db/export-data.sh index 8bff538a7..bdf8049e0 100755 --- a/db/export-data.sh +++ b/db/export-data.sh @@ -59,6 +59,7 @@ TABLES=( componentType continent department + docuware itemPackingType pgc sample diff --git a/loopback/locale/es.json b/loopback/locale/es.json index ad845cb38..fe256e2ea 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -252,5 +252,6 @@ "Receipt's bank was not found": "No se encontró el banco del recibo", "This receipt was not compensated": "Este recibo no ha sido compensado", "Client's email was not found": "No se encontró el email del cliente", - "Failed to upload file": "Error al subir archivo" -} + "Failed to upload file": "Error al subir archivo", + "The DOCUWARE PDF document does not exists": "The DOCUWARE PDF document does not exists" +} \ No newline at end of file diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index e9e99fc3e..c2ebc3e3a 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -63,7 +63,7 @@ Send PDF Send PDF to tablet @@ -333,3 +333,11 @@ company-fk="$ctrl.vnConfig.companyFk" client-fk="$ctrl.ticket.client.id"> + + + + diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 14d884ba7..c3e5ebf58 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -307,13 +307,16 @@ class Controller extends Section { } hasDocuware() { - this.$http.post(`Docuwares/${this.id}/checkFile`, {fileCabinet: 'deliveryNote'}) + this.$http.post(`Docuwares/${this.id}/checkFile`, {fileCabinet: 'deliveryNote', signed: true}) .then(res => { this.hasDocuwareFile = res.data; }); } - uploadDocuware() { + uploadDocuware(force) { + if (!force) + return this.$.pdfToTablet.show(); + return this.$http.post(`Docuwares/${this.id}/upload`, {fileCabinet: 'deliveryNote'}) .then(() => { this.vnApp.showSuccess(this.$t('PDF sent!')); diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 48b64f4a0..67dc0affa 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -286,9 +286,34 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { describe('hasDocuware()', () => { it('should call hasDocuware method', () => { - $httpBackend.whenPOST(`Docuwares/${ticket.id}/checkFile`).respond(); + $httpBackend.whenPOST(`Docuwares/${ticket.id}/checkFile`).respond(true); controller.hasDocuware(); $httpBackend.flush(); + + expect(controller.hasDocuwareFile).toBe(true); + }); + }); + + describe('uploadDocuware()', () => { + it('should open dialog if not force', () => { + controller.$.pdfToTablet = {show: () => {}}; + jest.spyOn(controller.$.pdfToTablet, 'show'); + controller.uploadDocuware(false); + + expect(controller.$.pdfToTablet.show).toHaveBeenCalled(); + }); + + it('should make a query and show balance create', () => { + controller.$.balanceCreate = {show: () => {}}; + jest.spyOn(controller.$.balanceCreate, 'show'); + jest.spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.whenPOST(`Docuwares/${ticket.id}/upload`).respond(true); + controller.uploadDocuware(true); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.$.balanceCreate.show).toHaveBeenCalled(); }); }); diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index 82425864c..b51637524 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -16,3 +16,5 @@ The following refund ticket have been created: "Se ha creado siguiente ticket de Transfer client: Transferir cliente SMS Notify changes: SMS Notificar cambios PDF sent!: ¡PDF enviado! +Already exist signed delivery note: Ya existe albarán de entrega firmado +Are you sure you want to replace this delivery note?: ¿Seguro que quieres reemplazar este albarán de entrega? From 2918c9c71d66b62e47384ea46b32c829a9e95bb7 Mon Sep 17 00:00:00 2001 From: alexm Date: Fri, 13 Jan 2023 13:52:19 +0100 Subject: [PATCH 67/72] skip intermittent test --- .../invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js index 803338ef3..26eae45ac 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js @@ -11,6 +11,7 @@ describe('InvoiceOut createPdf()', () => { const ctx = {req: activeCtx}; it('should create a new PDF file and set true the hasPdf property', async() => { + pending('https://redmine.verdnatura.es/issues/5035'); const invoiceId = 1; spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx From a8e4c9e1debc1917133da3336248efe4c1bb3c26 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Fri, 13 Jan 2023 15:42:00 +0100 Subject: [PATCH 68/72] cambiar fks por relaciones --- .../ticket/back/models/expeditionMistake.json | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/modules/ticket/back/models/expeditionMistake.json b/modules/ticket/back/models/expeditionMistake.json index e01f2f4be..43033194a 100644 --- a/modules/ticket/back/models/expeditionMistake.json +++ b/modules/ticket/back/models/expeditionMistake.json @@ -7,19 +7,27 @@ } }, "properties": { - "expeditionFk": { - "id": true, - "type": "number" - }, - "workerFk": { - "type": "number" - }, - "typeFk": { - "type": "number" - }, "created": { "type": "date" } + }, + "relations": { + "expedition": { + "type": "belongsTo", + "model": "Expedition", + "foreignKey": "expeditionFk" + }, + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "workerFk" + }, + "type": { + "type": "belongsTo", + "model": "MistakeType", + "foreignKey": "typeFk" + } } + } \ No newline at end of file From 73c3f05377ad23b40ffe81394eb13afff479166c Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 16 Jan 2023 07:36:06 +0100 Subject: [PATCH 69/72] refactor(docuware_upload): simplify json --- back/methods/docuware/upload.js | 275 -------------------------------- 1 file changed, 275 deletions(-) diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js index 76067e84a..b5ee3d18f 100644 --- a/back/methods/docuware/upload.js +++ b/back/methods/docuware/upload.js @@ -53,329 +53,54 @@ module.exports = Self => { } }] }); - const [taxes] = await models.Ticket.rawSql('CALL vn.ticketGetTaxAdd(?)', [id]); // upload file const templateJson = { 'Fields': [ { 'FieldName': 'N__ALBAR_N', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 2531, - 'Top': 3645, - 'Width': 257, - 'Height': 230, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': id, - 'FieldValue': id }, { 'FieldName': 'CIF_PROVEEDOR', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 6176, - 'Top': 4624, - 'Width': 839, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': ticket.client().fi, - 'FieldValue': ticket.client().fi }, { 'FieldName': 'CODIGO_PROVEEDOR', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 2531, - 'Top': 3240, - 'Width': 514, - 'Height': 230, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': ticket.client().id, - 'FieldValue': ticket.client().id }, { 'FieldName': 'NOMBRE_PROVEEDOR', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'string', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 6175, - 'Top': 4264, - 'Width': 858, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': ticket.client().socialName, - 'FieldValue': ticket.client().socialName }, { 'FieldName': 'FECHA_FACTURA', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'date', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 2531, - 'Top': 4050, - 'Width': 1181, - 'Height': 230, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': ticket.shipped, - 'FieldValue': ticket.shipped - }, - { - 'FieldName': 'TIPO_IVA__1_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 9537, - 'Top': 10057, - 'Width': 615, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': taxes[0].rate, - 'FieldValue': taxes[0].rate - }, - { - 'FieldName': 'BASE_IMPONIBLE__1_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 8907, - 'Top': 10567, - 'Width': 419, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': ticket.totalWithoutVat, - 'FieldValue': ticket.totalWithoutVat - }, - { - 'FieldName': 'IMPORTE_IVA__1_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 10423, - 'Top': 10057, - 'Width': 419, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, - 'Item': taxes[0].tax, - 'FieldValue': taxes[0].tax - }, - { - 'FieldName': 'TIPO_IVA__2_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'BASE_IMPONIBLE__2_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'IMPORTE_IVA__2_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'TIPO_IVA__3_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'BASE_IMPONIBLE__3_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'IMPORTE_IVA__3_', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null - }, - { - 'FieldName': 'IRPF', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'Decimal', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': null }, { 'FieldName': 'TOTAL_FACTURA', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'Decimal', - 'PointAndShootInfo': { - 'Box': [ - { - 'Left': 10423, - 'Top': 10958, - 'Width': 419, - 'Height': 168, - 'PageNumber': 0 - } - ], - 'PageNumber': 0 - }, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': ticket.totalWithVat, - 'FieldValue': ticket.totalWithVat }, { 'FieldName': 'ESTADO', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': 'Pendiente procesar', - 'FieldValue': 'Pendiente procesar' - }, - { - 'FieldName': 'URL', - 'ReadOnly': false, - 'SystemField': false, - 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': true, - 'Item': null, - 'FieldValue': '' }, { 'FieldName': 'FIRMA_', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': 'Si', - 'FieldValue': 'Si' }, { 'FieldName': 'FILTRO_TABLET', - 'ReadOnly': false, - 'SystemField': false, 'ItemElementName': 'string', - 'PointAndShootInfo': null, - 'IsAutoNumber': false, - 'IsNull': false, 'Item': 'Tablet1', - 'FieldValue': 'Tablet1' } ] }; From b78bebf27ccb4c7ec05e071ab2aca3f07ebf1aeb Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 16 Jan 2023 12:22:22 +0100 Subject: [PATCH 70/72] refs #5061 added hour in date, changed ipt filter --- e2e/paths/05-ticket/21_future.spec.js | 4 +-- .../back/methods/ticket/getTicketsFuture.js | 15 ++++++++--- .../ticket/specs/getTicketsFuture.spec.js | 26 +++++++++---------- modules/ticket/front/future/index.html | 8 +++--- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/e2e/paths/05-ticket/21_future.spec.js b/e2e/paths/05-ticket/21_future.spec.js index 45c39de86..14756f47a 100644 --- a/e2e/paths/05-ticket/21_future.spec.js +++ b/e2e/paths/05-ticket/21_future.spec.js @@ -55,7 +55,7 @@ describe('Ticket Future path', () => { await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal'); await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); it('should search with the destination IPT', async() => { @@ -68,7 +68,7 @@ describe('Ticket Future path', () => { await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal'); await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); it('should search with the origin grouped state', async() => { diff --git a/modules/ticket/back/methods/ticket/getTicketsFuture.js b/modules/ticket/back/methods/ticket/getTicketsFuture.js index 6798df513..21e3140e6 100644 --- a/modules/ticket/back/methods/ticket/getTicketsFuture.js +++ b/modules/ticket/back/methods/ticket/getTicketsFuture.js @@ -115,9 +115,19 @@ module.exports = Self => { case 'futureId': return {'f.futureId': value}; case 'ipt': - return {'f.ipt': value}; + return {or: + [ + {'f.ipt': {like: `%${value}%`}}, + {'f.ipt': null} + ] + }; case 'futureIpt': - return {'f.futureIpt': value}; + return {or: + [ + {'f.futureIpt': {like: `%${value}%`}}, + {'f.futureIpt': null} + ] + }; case 'state': return {'f.stateCode': {like: `%${value}%`}}; case 'futureState': @@ -203,7 +213,6 @@ module.exports = Self => { tmp.ticket_problems`); const sql = ParameterizedSQL.join(stmts, ';'); - const result = await conn.executeStmt(sql, myOptions); return result[ticketsIndex]; diff --git a/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js b/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js index c05ba764d..51639e304 100644 --- a/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js +++ b/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js @@ -19,7 +19,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(4); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -43,7 +43,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(4); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -93,7 +93,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(4); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -118,7 +118,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(1); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -143,7 +143,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(4); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -168,7 +168,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(4); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -187,13 +187,13 @@ describe('ticket getTicketsFuture()', () => { originDated: today, futureDated: today, warehouseFk: 1, - ipt: 0 + ipt: 'H' }; const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(0); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -218,7 +218,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(4); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -237,13 +237,13 @@ describe('ticket getTicketsFuture()', () => { originDated: today, futureDated: today, warehouseFk: 1, - futureIpt: 0 + futureIpt: 'H' }; const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(0); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -268,7 +268,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(1); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { @@ -293,7 +293,7 @@ describe('ticket getTicketsFuture()', () => { const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsFuture(ctx, options); - expect(result.length).toEqual(4); + expect(result.length).toBeGreaterThan(0); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/front/future/index.html b/modules/ticket/front/future/index.html index 1af1fb9ba..68b0aa4fd 100644 --- a/modules/ticket/front/future/index.html +++ b/modules/ticket/front/future/index.html @@ -129,9 +129,9 @@ class="link"> {{::ticket.id}} - + - {{::ticket.shipped | date: 'dd/MM/yyyy'}} + {{::ticket.shipped | date: 'dd/MM/yyyy HH:mm'}} {{::ticket.ipt}} @@ -150,9 +150,9 @@ {{::ticket.futureId}} - + - {{::ticket.futureShipped | date: 'dd/MM/yyyy'}} + {{::ticket.futureShipped | date: 'dd/MM/yyyy HH:mm'}} {{::ticket.futureIpt}} From 4d12e29b3dc16a4e89c7262a75f549638242d7d3 Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 16 Jan 2023 14:24:53 +0100 Subject: [PATCH 71/72] refs #5061 fix filter --- e2e/paths/05-ticket/21_future.spec.js | 44 ------------------- .../back/methods/ticket/getTicketsFuture.js | 4 +- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/e2e/paths/05-ticket/21_future.spec.js b/e2e/paths/05-ticket/21_future.spec.js index 14756f47a..34ae3d688 100644 --- a/e2e/paths/05-ticket/21_future.spec.js +++ b/e2e/paths/05-ticket/21_future.spec.js @@ -152,50 +152,6 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search in smart-table with especified Lines', async() => { - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLines, '0'); - await page.keyboard.press('Enter'); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); - - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); - - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLines, '1'); - await page.keyboard.press('Enter'); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); - - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); - }); - - it('should search in smart-table with especified Liters', async() => { - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLiters, '0'); - await page.keyboard.press('Enter'); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); - - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); - - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLiters, '28'); - await page.keyboard.press('Enter'); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); - - await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); - }); - it('should check the three last tickets and move to the future', async() => { await page.waitToClick(selectors.ticketFuture.multiCheck); await page.waitToClick(selectors.ticketFuture.firstCheck); diff --git a/modules/ticket/back/methods/ticket/getTicketsFuture.js b/modules/ticket/back/methods/ticket/getTicketsFuture.js index 21e3140e6..901e546f7 100644 --- a/modules/ticket/back/methods/ticket/getTicketsFuture.js +++ b/modules/ticket/back/methods/ticket/getTicketsFuture.js @@ -108,9 +108,9 @@ module.exports = Self => { switch (param) { case 'id': return {'f.id': value}; - case 'lines': + case 'linesMax': return {'f.lines': {lte: value}}; - case 'liters': + case 'litersMax': return {'f.liters': {lte: value}}; case 'futureId': return {'f.futureId': value}; From dab94ff535c7bcacc9bfe8ccf075f8ba8a67b53d Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 16 Jan 2023 14:48:45 +0100 Subject: [PATCH 72/72] refs #5061 refresh structure --- db/dump/structure.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 47fdd6d74..4626279e4 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -80202,3 +80202,4 @@ USE `vncontrol`; -- Dump completed on 2022-11-21 7:57:28 +