From 0afc26e20c1a65c53aca7d495ac5148673483246 Mon Sep 17 00:00:00 2001 From: carlossa Date: Thu, 8 Jun 2023 10:30:03 +0200 Subject: [PATCH 01/39] refs #5728 remove add, quit e2e --- e2e/paths/12-entry/07_buys.spec.js | 19 -- modules/entry/back/methods/entry/addBuy.js | 165 ------------------ .../back/methods/entry/specs/addBuy.spec.js | 42 ----- modules/entry/back/models/entry.js | 1 - modules/entry/front/buy/index/index.html | 7 - modules/entry/front/buy/index/index.js | 5 - modules/entry/front/buy/index/index.spec.js | 11 -- 7 files changed, 250 deletions(-) delete mode 100644 modules/entry/back/methods/entry/addBuy.js delete mode 100644 modules/entry/back/methods/entry/specs/addBuy.spec.js diff --git a/e2e/paths/12-entry/07_buys.spec.js b/e2e/paths/12-entry/07_buys.spec.js index e501452bc..260d1be57 100644 --- a/e2e/paths/12-entry/07_buys.spec.js +++ b/e2e/paths/12-entry/07_buys.spec.js @@ -67,25 +67,6 @@ describe('Entry import, create and edit buys path', () => { await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 1); }); - it('should add a new buy', async() => { - await page.waitToClick(selectors.entryBuys.addBuyButton); - await page.write(selectors.entryBuys.secondBuyPackingPrice, '999'); - await page.write(selectors.entryBuys.secondBuyGroupingPrice, '999'); - await page.write(selectors.entryBuys.secondBuyPrice, '999'); - await page.write(selectors.entryBuys.secondBuyGrouping, '999'); - await page.write(selectors.entryBuys.secondBuyPacking, '999'); - await page.write(selectors.entryBuys.secondBuyWeight, '999'); - await page.write(selectors.entryBuys.secondBuyStickers, '999'); - await page.autocompleteSearch(selectors.entryBuys.secondBuyPackage, '1'); - await page.write(selectors.entryBuys.secondBuyQuantity, '999'); - await page.autocompleteSearch(selectors.entryBuys.secondBuyItem, '1'); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - - await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 2); - }); - it('should edit the newest buy and check data', async() => { await page.clearInput(selectors.entryBuys.secondBuyPackingPrice); await page.waitForTimeout(250); diff --git a/modules/entry/back/methods/entry/addBuy.js b/modules/entry/back/methods/entry/addBuy.js deleted file mode 100644 index f612c1651..000000000 --- a/modules/entry/back/methods/entry/addBuy.js +++ /dev/null @@ -1,165 +0,0 @@ - -const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; - -module.exports = Self => { - Self.remoteMethodCtx('addBuy', { - description: 'Inserts a new buy for the current entry', - accessType: 'WRITE', - accepts: [{ - arg: 'id', - type: 'number', - required: true, - description: 'The entry id', - http: {source: 'path'} - }, - { - arg: 'itemFk', - type: 'number', - required: true - }, - { - arg: 'quantity', - type: 'number', - required: true - }, - { - arg: 'packageFk', - type: 'string', - required: true - }, - { - arg: 'packing', - type: 'number', - }, - { - arg: 'grouping', - type: 'number' - }, - { - arg: 'weight', - type: 'number', - }, - { - arg: 'stickers', - type: 'number', - }, - { - arg: 'price2', - type: 'number', - }, - { - arg: 'price3', - type: 'number', - }, - { - arg: 'buyingValue', - type: 'number' - }], - returns: { - type: 'object', - root: true - }, - http: { - path: `/:id/addBuy`, - verb: 'POST' - } - }); - - Self.addBuy = async(ctx, options) => { - const conn = Self.dataSource.connector; - let tx; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } - - try { - const models = Self.app.models; - - ctx.args.entryFk = ctx.args.id; - - // remove unwanted properties - delete ctx.args.id; - delete ctx.args.ctx; - - const newBuy = await models.Buy.create(ctx.args, myOptions); - - const filter = { - fields: [ - 'id', - 'itemFk', - 'stickers', - 'packing', - 'grouping', - 'quantity', - 'packageFk', - 'weight', - 'buyingValue', - 'price2', - 'price3' - ], - include: { - relation: 'item', - scope: { - fields: [ - 'id', - 'typeFk', - 'name', - 'size', - 'minPrice', - 'tag5', - 'value5', - 'tag6', - 'value6', - 'tag7', - 'value7', - 'tag8', - 'value8', - 'tag9', - 'value9', - 'tag10', - 'value10', - 'groupingMode' - ], - include: { - relation: 'itemType', - scope: { - fields: ['code', 'description'] - } - } - } - } - }; - - const stmts = []; - let stmt; - - stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc'); - stmt = new ParameterizedSQL( - `CREATE TEMPORARY TABLE tmp.buyRecalc - (INDEX (id)) - ENGINE = MEMORY - SELECT ? AS id`, [newBuy.id]); - - stmts.push(stmt); - stmts.push('CALL buy_recalcPrices()'); - - const sql = ParameterizedSQL.join(stmts, ';'); - await conn.executeStmt(sql, myOptions); - - const buy = await models.Buy.findById(newBuy.id, filter, myOptions); - - if (tx) await tx.commit(); - - return buy; - } catch (e) { - if (tx) await tx.rollback(); - throw e; - } - }; -}; diff --git a/modules/entry/back/methods/entry/specs/addBuy.spec.js b/modules/entry/back/methods/entry/specs/addBuy.spec.js deleted file mode 100644 index ead75f2d2..000000000 --- a/modules/entry/back/methods/entry/specs/addBuy.spec.js +++ /dev/null @@ -1,42 +0,0 @@ -const models = require('vn-loopback/server/server').models; -const LoopBackContext = require('loopback-context'); - -describe('entry addBuy()', () => { - const activeCtx = { - accessToken: {userId: 18}, - }; - - const ctx = { - req: activeCtx - }; - - const entryId = 2; - it('should create a new buy for the given entry', async() => { - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - const itemId = 4; - const quantity = 10; - - ctx.args = { - id: entryId, - itemFk: itemId, - quantity: quantity, - packageFk: 3 - }; - - const tx = await models.Entry.beginTransaction({}); - const options = {transaction: tx}; - - try { - const newBuy = await models.Entry.addBuy(ctx, options); - - expect(newBuy.itemFk).toEqual(itemId); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/entry/back/models/entry.js b/modules/entry/back/models/entry.js index 0eabd70ee..6148ae559 100644 --- a/modules/entry/back/models/entry.js +++ b/modules/entry/back/models/entry.js @@ -3,7 +3,6 @@ module.exports = Self => { require('../methods/entry/filter')(Self); require('../methods/entry/getEntry')(Self); require('../methods/entry/getBuys')(Self); - require('../methods/entry/addBuy')(Self); require('../methods/entry/importBuys')(Self); require('../methods/entry/importBuysPreview')(Self); require('../methods/entry/lastItemBuys')(Self); diff --git a/modules/entry/front/buy/index/index.html b/modules/entry/front/buy/index/index.html index e6d1a0b76..28fdabdb4 100644 --- a/modules/entry/front/buy/index/index.html +++ b/modules/entry/front/buy/index/index.html @@ -222,13 +222,6 @@
- - { if (!res.data) return; diff --git a/modules/entry/front/buy/index/index.spec.js b/modules/entry/front/buy/index/index.spec.js index aff52bc80..0e221302c 100644 --- a/modules/entry/front/buy/index/index.spec.js +++ b/modules/entry/front/buy/index/index.spec.js @@ -25,17 +25,6 @@ describe('Entry buy', () => { controller.saveBuy(buy); $httpBackend.flush(); }); - - it(`should call the entry addBuy post route if the received buy has no ID`, () => { - controller.entry = {id: 1}; - const buy = {itemFk: 1, quantity: 1, packageFk: 1}; - - const query = `Entries/${controller.entry.id}/addBuy`; - - $httpBackend.expectPOST(query).respond(200); - controller.saveBuy(buy); - $httpBackend.flush(); - }); }); describe('deleteBuys()', () => { From 9cdaf2e51916f481cc9327f7cb5b9e39a3c812a6 Mon Sep 17 00:00:00 2001 From: carlossa Date: Thu, 8 Jun 2023 10:45:27 +0200 Subject: [PATCH 02/39] refs #5728 fix e2e buy --- e2e/helpers/selectors.js | 2 - e2e/paths/12-entry/07_buys.spec.js | 74 ------------------------------ 2 files changed, 76 deletions(-) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index fff0a2f15..ed9c9daba 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -1179,8 +1179,6 @@ export default { allBuyCheckbox: 'vn-entry-buy-index thead vn-check', firstBuyCheckbox: 'vn-entry-buy-index tbody:nth-child(2) vn-check', deleteBuysButton: 'vn-entry-buy-index vn-button[icon="delete"]', - addBuyButton: 'vn-entry-buy-index vn-icon[icon="add"]', - secondBuyPackingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price3"]', secondBuyGroupingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price2"]', secondBuyPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.buyingValue"]', secondBuyGrouping: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.grouping"]', diff --git a/e2e/paths/12-entry/07_buys.spec.js b/e2e/paths/12-entry/07_buys.spec.js index 260d1be57..28d39fb1d 100644 --- a/e2e/paths/12-entry/07_buys.spec.js +++ b/e2e/paths/12-entry/07_buys.spec.js @@ -66,78 +66,4 @@ describe('Entry import, create and edit buys path', () => { await page.waitToClick(selectors.globalItems.acceptButton); await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 1); }); - - it('should edit the newest buy and check data', async() => { - await page.clearInput(selectors.entryBuys.secondBuyPackingPrice); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyPackingPrice, '100'); - await page.keyboard.press('Enter'); - await page.waitForSnackbar(); - - await page.clearInput(selectors.entryBuys.secondBuyGroupingPrice); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyGroupingPrice, '200'); - await page.keyboard.press('Enter'); - await page.waitForSnackbar(); - - await page.clearInput(selectors.entryBuys.secondBuyPrice); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyPrice, '300'); - await page.keyboard.press('Enter'); - await page.waitForSnackbar(); - - await page.clearInput(selectors.entryBuys.secondBuyGrouping); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyGrouping, '400'); - await page.keyboard.press('Enter'); - await page.waitForSnackbar(); - - await page.clearInput(selectors.entryBuys.secondBuyPacking); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyPacking, '500'); - await page.keyboard.press('Enter'); - await page.waitForSnackbar(); - - await page.clearInput(selectors.entryBuys.secondBuyWeight); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyWeight, '600'); - await page.keyboard.press('Enter'); - await page.waitForSnackbar(); - - await page.clearInput(selectors.entryBuys.secondBuyStickers); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyStickers, '700'); - await page.keyboard.press('Enter'); - await page.waitForSnackbar(); - - await page.autocompleteSearch(selectors.entryBuys.secondBuyPackage, '94'); - await page.waitForSnackbar(); - - await page.clearInput(selectors.entryBuys.secondBuyQuantity); - await page.waitForTimeout(250); - await page.write(selectors.entryBuys.secondBuyQuantity, '800'); - await page.keyboard.press('Enter'); - - await page.reloadSection('entry.card.buy.index'); - - const secondBuyPackingPrice = await page.getValue(selectors.entryBuys.secondBuyPackingPrice); - const secondBuyGroupingPrice = await page.getValue(selectors.entryBuys.secondBuyGroupingPrice); - const secondBuyPrice = await page.getValue(selectors.entryBuys.secondBuyPrice); - const secondBuyGrouping = await page.getValue(selectors.entryBuys.secondBuyGrouping); - const secondBuyPacking = await page.getValue(selectors.entryBuys.secondBuyPacking); - const secondBuyWeight = await page.getValue(selectors.entryBuys.secondBuyWeight); - const secondBuyStickers = await page.getValue(selectors.entryBuys.secondBuyStickers); - const secondBuyPackage = await page.getValue(selectors.entryBuys.secondBuyPackage); - const secondBuyQuantity = await page.getValue(selectors.entryBuys.secondBuyQuantity); - - expect(secondBuyPackingPrice).toEqual('100'); - expect(secondBuyGroupingPrice).toEqual('200'); - expect(secondBuyPrice).toEqual('300'); - expect(secondBuyGrouping).toEqual('400'); - expect(secondBuyPacking).toEqual('500'); - expect(secondBuyWeight).toEqual('600'); - expect(secondBuyStickers).toEqual('700'); - expect(secondBuyPackage).toEqual('94'); - expect(secondBuyQuantity).toEqual('800'); - }); }); From 72b779f5c0409781b95f2722c59952e48648dd79 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 13 Jun 2023 10:26:12 +0200 Subject: [PATCH 03/39] refs #5528 fix: editar compras (seleccionar todas), cuando aplicas un filtro --- modules/entry/back/methods/entry/editLatestBuys.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/entry/back/methods/entry/editLatestBuys.js b/modules/entry/back/methods/entry/editLatestBuys.js index 2642d4f4d..0da2b1625 100644 --- a/modules/entry/back/methods/entry/editLatestBuys.js +++ b/modules/entry/back/methods/entry/editLatestBuys.js @@ -75,7 +75,7 @@ module.exports = Self => { value[field] = newValue; if (filter) { - ctx.args.filter = {where: filter, limit: null}; + ctx.args = {where: filter, limit: null}; lines = await models.Buy.latestBuysFilter(ctx, null, myOptions); } From 65108943abeae3fcb9a67d0573708f43fa1e1f70 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 14 Jun 2023 12:48:14 +0200 Subject: [PATCH 04/39] refs #5528 feat: back test --- modules/entry/back/methods/entry/specs/editLatestBuys.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js index 99d2df67b..1ffe27918 100644 --- a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js +++ b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js @@ -53,7 +53,7 @@ describe('Buy editLatestsBuys()', () => { const options = {transaction: tx}; try { - const filter = {'i.typeFk': 1}; + const filter = {'categoryFk': 1}; const ctx = { args: { filter: filter From 98b270b885a061dff168408e561db8156bbc419e Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 14 Jun 2023 13:05:46 +0200 Subject: [PATCH 05/39] refs #5528 feat: add back test --- .../entry/specs/editLatestBuys.spec.js | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js index 1ffe27918..a4dd185fd 100644 --- a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js +++ b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js @@ -53,7 +53,36 @@ describe('Buy editLatestsBuys()', () => { const options = {transaction: tx}; try { - const filter = {'categoryFk': 1}; + const filter = {'categoryFk': 1, 'tags': []}; + const ctx = { + args: { + filter: filter + }, + req: {accessToken: {userId: 1}} + }; + + const field = 'size'; + const newValue = 88; + + await models.Buy.editLatestBuys(ctx, field, newValue, null, filter, options); + + const [result] = await models.Buy.latestBuysFilter(ctx, null, options); + + expect(result[field]).toEqual(newValue); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should change the value of a given column for filter tags', async() => { + const tx = await models.Buy.beginTransaction({}); + const options = {transaction: tx}; + + try { + const filter = {'tags': [{tagFk: 1, value: 'Brown'}]}; const ctx = { args: { filter: filter From b6c8ca20ee9e2eb7b932499300522913f0338175 Mon Sep 17 00:00:00 2001 From: sergiodt Date: Mon, 19 Jun 2023 08:58:20 +0200 Subject: [PATCH 06/39] refs #5339 data --- modules/worker/back/models/operator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/worker/back/models/operator.js b/modules/worker/back/models/operator.js index e9b590fa6..63b5154ed 100644 --- a/modules/worker/back/models/operator.js +++ b/modules/worker/back/models/operator.js @@ -1,6 +1,6 @@ module.exports = function(Self) { Self.observe('after save', async function(ctx) { - const instance = ctx.instance; + const instance = ctx.data; const models = Self.app.models; const options = ctx.options; From cea6fae64949f828aaf202e06ee82963e357cc07 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 20 Jun 2023 10:40:57 +0200 Subject: [PATCH 07/39] fix: eliminada variable que no estaba definida --- modules/ticket/back/methods/ticket/setDeleted.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index 7cc300547..40f3db8c5 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -81,7 +81,7 @@ module.exports = Self => { throw new UserError('You must delete all the buy requests first'); // removes item shelvings - if (hasItemShelvingSales && isSalesAssistant) { + if (hasItemShelvingSales && canDeleteTicketWithPartPrepared) { const promises = []; for (let sale of sales) { if (sale.itemShelvingSale()) { From e99627c0b238c4019fdd894ee154d38616f839dc Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 20 Jun 2023 11:28:25 +0200 Subject: [PATCH 08/39] codigo innecesario --- modules/ticket/back/methods/ticket/setDeleted.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index 40f3db8c5..4d7fe73d4 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -81,17 +81,15 @@ module.exports = Self => { throw new UserError('You must delete all the buy requests first'); // removes item shelvings - if (hasItemShelvingSales && canDeleteTicketWithPartPrepared) { - const promises = []; - for (let sale of sales) { - if (sale.itemShelvingSale()) { - const itemShelvingSale = sale.itemShelvingSale(); - const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id, myOptions); - promises.push(destroyedShelving); - } + const promises = []; + for (let sale of sales) { + if (sale.itemShelvingSale()) { + const itemShelvingSale = sale.itemShelvingSale(); + const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id, myOptions); + promises.push(destroyedShelving); } - await Promise.all(promises); } + await Promise.all(promises); // Remove ticket greuges const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}}, myOptions); From 2649db852f215a477ac0804068887c853336dfc8 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Tue, 20 Jun 2023 11:50:36 +0200 Subject: [PATCH 09/39] refs #5772 Invoice PDF size reduced --- print/templates/reports/invoice/assets/css/style.css | 6 +++++- print/templates/reports/invoice/invoice.html | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/print/templates/reports/invoice/assets/css/style.css b/print/templates/reports/invoice/assets/css/style.css index 9fda2a613..d4526ce56 100644 --- a/print/templates/reports/invoice/assets/css/style.css +++ b/print/templates/reports/invoice/assets/css/style.css @@ -16,6 +16,10 @@ h2 { font-size: 22px } +.column-oriented td, +.column-oriented th { + padding: 6px +} #nickname h2 { max-width: 400px; @@ -39,4 +43,4 @@ h2 { .phytosanitary-info { margin-top: 10px -} \ No newline at end of file +} diff --git a/print/templates/reports/invoice/invoice.html b/print/templates/reports/invoice/invoice.html index d74325dfd..32138befc 100644 --- a/print/templates/reports/invoice/invoice.html +++ b/print/templates/reports/invoice/invoice.html @@ -62,7 +62,7 @@
-
+

{{$t('deliveryNote')}}

@@ -106,6 +106,7 @@ {{sale.vatType}} {{saleImport(sale) | currency('EUR', $i18n.locale)}} + From 6f9fd9bb72109aed288fe46671ca40aa5c417c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Wed, 21 Jun 2023 09:40:14 +0200 Subject: [PATCH 10/39] refs #5772 mensajes de error --- loopback/locale/es.json | 2 +- modules/invoiceOut/front/global-invoicing/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 7e2701f95..b66a13569 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -265,7 +265,7 @@ "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", "A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.", "There is no assigned email for this client": "No hay correo asignado para este cliente", - "Exists an invoice with a previous date": "Existe una factura con fecha anterior", + "Exists an invoice with a future date": "Existe una factura con fecha posterior", "Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite", "Warehouse inventory not set": "El almacén inventario no está establecido", "This locker has already been assigned": "Esta taquilla ya ha sido asignada", diff --git a/modules/invoiceOut/front/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js index 5a078dfbb..70838a5a2 100644 --- a/modules/invoiceOut/front/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -49,7 +49,7 @@ class Controller extends Section { if (this.invoiceDate < this.maxShipped) throw new UserError('Invoice date can\'t be less than max date'); if (this.minInvoicingDate && this.invoiceDate.getTime() < this.minInvoicingDate.getTime()) - throw new UserError('Exists an invoice with a previous date'); + throw new UserError('Exists an invoice with a future date'); if (!this.companyFk) throw new UserError('Choose a valid company'); if (!this.printerFk) From 44192c0606ba7611946f21bf1189d2fa8a3e9df3 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Wed, 21 Jun 2023 11:08:28 +0200 Subject: [PATCH 11/39] refs #5667 Client translations added --- modules/client/back/locale/client/en.yml | 3 ++- modules/client/back/locale/client/es.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/client/back/locale/client/en.yml b/modules/client/back/locale/client/en.yml index 74384461c..ad21a6ce6 100644 --- a/modules/client/back/locale/client/en.yml +++ b/modules/client/back/locale/client/en.yml @@ -52,4 +52,5 @@ columns: hasInvoiceSimplified: simplified invoice typeFk: type lastSalesPersonFk: last salesperson - + rating: rating + recommendedCredit: recommended credit diff --git a/modules/client/back/locale/client/es.yml b/modules/client/back/locale/client/es.yml index 47bf896d9..b17f53b1c 100644 --- a/modules/client/back/locale/client/es.yml +++ b/modules/client/back/locale/client/es.yml @@ -52,4 +52,5 @@ columns: hasInvoiceSimplified: factura simple typeFk: tipo lastSalesPersonFk: último comercial - + rating: clasificación + recommendedCredit: crédito recomendado From 10a25ff59058ff198880d4e66434783196ccfe2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Thu, 22 Jun 2023 07:49:35 +0200 Subject: [PATCH 12/39] refs #5772 --- loopback/locale/en.json | 2 +- loopback/locale/es.json | 8 ++- .../back/methods/invoiceOut/createPdf.js | 42 +++--------- .../back/methods/invoiceOut/download.js | 6 +- .../back/methods/invoiceOut/invoiceClient.js | 65 +++++++++++-------- .../back/methods/invoiceOut/invoiceEmail.js | 7 +- modules/invoiceOut/back/models/invoice-out.js | 33 ++++++++++ .../front/global-invoicing/index.html | 6 +- .../front/global-invoicing/index.js | 13 +++- print/core/smtp.js | 13 +++- 10 files changed, 122 insertions(+), 73 deletions(-) diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 43ff4b86a..7a973460d 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -115,7 +115,7 @@ "This client is not invoiceable": "This client is not invoiceable", "INACTIVE_PROVIDER": "Inactive provider", "reference duplicated": "reference duplicated", - "The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option", + "The PDF document does not exist": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option", "This item is not available": "This item is not available", "Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}", "The type of business must be filled in basic data": "The type of business must be filled in basic data", diff --git a/loopback/locale/es.json b/loopback/locale/es.json index b66a13569..2afe5bab5 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -211,7 +211,7 @@ "You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito", "You can't change the credit set to zero from a financialBoss": "No puedes cambiar el cŕedito establecido a cero por un jefe de finanzas", "Amounts do not match": "Las cantidades no coinciden", - "The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'", + "The PDF document does not exist": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'", "The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos", "You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días", "The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día", @@ -293,5 +293,7 @@ "Pass expired": "La contraseña ha caducado, cambiela desde Salix", "Invalid NIF for VIES": "Invalid NIF for VIES", "Ticket does not exist": "Este ticket no existe", - "Ticket is already signed": "Este ticket ya ha sido firmado" -} + "Ticket is already signed": "Este ticket ya ha sido firmado", + "Fecha fuera de rango": "Fecha fuera de rango", + "Error while generating PDF": "Error while generating PDF" +} \ No newline at end of file diff --git a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js index dfdb3c1a7..8cc584c94 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js +++ b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js @@ -1,5 +1,4 @@ const UserError = require('vn-loopback/util/user-error'); -const print = require('vn-print'); module.exports = Self => { Self.remoteMethodCtx('createPdf', { @@ -26,9 +25,6 @@ module.exports = Self => { Self.createPdf = async function(ctx, id, options) { const models = Self.app.models; - if (process.env.NODE_ENV == 'test') - throw new UserError(`Action not allowed on the test environment`); - let tx; const myOptions = {}; @@ -41,40 +37,20 @@ module.exports = Self => { } try { - const invoiceOut = await Self.findById(id, null, myOptions); - const canCreatePdf = await models.ACL.checkAccessAcl(ctx, 'InvoiceOut', 'canCreatePdf', 'WRITE'); + const invoiceOut = await Self.findById(id, {fields: ['hasPdf']}, myOptions); - if (invoiceOut.hasPdf && !canCreatePdf) - throw new UserError(`You don't have enough privileges`); + if (invoiceOut.hasPdf) { + const canCreatePdf = await models.ACL.checkAccessAcl(ctx, 'InvoiceOut', 'canCreatePdf', 'WRITE'); + if (!canCreatePdf) + throw new UserError(`You don't have enough privileges`); + } - await invoiceOut.updateAttributes({ - hasPdf: true - }, myOptions); - - const invoiceReport = new print.Report('invoice', { - reference: invoiceOut.ref, - recipientId: invoiceOut.clientFk - }); - const buffer = await invoiceReport.toPdfStream(); - - const issued = invoiceOut.issued; - const year = issued.getFullYear().toString(); - const month = (issued.getMonth() + 1).toString(); - const day = issued.getDate().toString(); - - const fileName = `${year}${invoiceOut.ref}.pdf`; - - // Store invoice - await print.storage.write(buffer, { - type: 'invoice', - path: `${year}/${month}/${day}`, - fileName: fileName - }); + await Self.makePdf(id, myOptions); if (tx) await tx.commit(); - } catch (e) { + } catch (err) { if (tx) await tx.rollback(); - throw e; + throw err; } }; }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index 5c787428b..49bba046b 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -43,7 +43,9 @@ module.exports = Self => { Object.assign(myOptions, options); try { - const invoiceOut = await models.InvoiceOut.findById(id, null, myOptions); + const invoiceOut = await models.InvoiceOut.findById(id, { + fields: ['ref', 'issued'] + }, myOptions); const issued = invoiceOut.issued; const year = issued.getFullYear().toString(); @@ -73,7 +75,7 @@ module.exports = Self => { return [stream, file.contentType, `filename="${file.name}"`]; } catch (error) { if (error.code === 'ENOENT') - throw new UserError('The PDF document does not exists'); + throw new UserError('The PDF document does not exist'); throw error; } diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index 421cbaea1..ff814fea9 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -109,17 +109,22 @@ module.exports = Self => { ], myOptions); const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions); - if (newInvoice.id) { - await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions); + if (!newInvoice) + throw new UserError('No tickets to invoice', 'notInvoiced'); - invoiceOut = await models.InvoiceOut.findById(newInvoice.id, { - include: { - relation: 'client' + await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions); + + invoiceOut = await models.InvoiceOut.findById(newInvoice.id, { + fields: ['id', 'ref', 'clientFk'], + include: { + relation: 'client', + scope: { + fields: ['email', 'isToBeMailed'] } - }, myOptions); + } + }, myOptions); - invoiceId = newInvoice.id; - } + invoiceId = newInvoice.id; if (tx) await tx.commit(); } catch (e) { @@ -127,26 +132,32 @@ module.exports = Self => { throw e; } - if (invoiceId) { - if (!invoiceOut.client().isToBeMailed) { - const query = ` - CALL vn.report_print( - 'invoice', - ?, - account.myUser_getId(), - JSON_OBJECT('refFk', ?), - 'normal' - );`; - await models.InvoiceOut.rawSql(query, [args.printerFk, invoiceOut.ref]); - } else { - ctx.args = { - reference: invoiceOut.ref, - recipientId: invoiceOut.clientFk, - recipient: invoiceOut.client().email - }; - await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); - } + try { + await Self.makePdf(invoiceId); + } catch (err) { + console.error(err); + throw new UserError('Error while generating PDF', 'pdfError'); } + + if (invoiceOut.client().isToBeMailed) { + ctx.args = { + reference: invoiceOut.ref, + recipientId: args.clientId, + recipient: invoiceOut.client().email + }; + await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); + } else { + const query = ` + CALL vn.report_print( + 'invoice', + ?, + account.myUser_getId(), + JSON_OBJECT('refFk', ?), + 'normal' + );`; + await models.InvoiceOut.rawSql(query, [args.printerFk, invoiceOut.ref]); + } + return invoiceId; }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js index 83cb84881..45894e9c5 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js @@ -1,3 +1,4 @@ +const UserError = require('vn-loopback/util/user-error'); const {Email} = require('vn-print'); module.exports = Self => { @@ -74,6 +75,10 @@ module.exports = Self => { ] }; - return email.send(mailOptions); + try { + return email.send(mailOptions); + } catch(err) { + throw new UserError('Error when sending mail to client', 'mailNotSent'); + } }; }; diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 5fcef7744..bfef57f58 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -1,3 +1,5 @@ +const print = require('vn-print'); + module.exports = Self => { require('../methods/invoiceOut/filter')(Self); require('../methods/invoiceOut/summary')(Self); @@ -19,4 +21,35 @@ module.exports = Self => { require('../methods/invoiceOut/getInvoiceDate')(Self); require('../methods/invoiceOut/negativeBases')(Self); require('../methods/invoiceOut/negativeBasesCsv')(Self); + + Self.makePdf = async function(id, options) { + const fields = ['id', 'hasPdf', 'ref', 'issued']; + const invoiceOut = await Self.findById(id, {fields}, options); + + const invoiceReport = new print.Report('invoice', { + reference: invoiceOut.ref + }); + const buffer = await invoiceReport.toPdfStream(); + + const issued = invoiceOut.issued; + const year = issued.getFullYear().toString(); + const month = (issued.getMonth() + 1).toString(); + const day = issued.getDate().toString(); + + const fileName = `${year}${invoiceOut.ref}.pdf`; + + // Store invoice + + await invoiceOut.updateAttributes({ + hasPdf: true + }, options); + + if (process.env.NODE_ENV !== 'test') { + await print.storage.write(buffer, { + type: 'invoice', + path: `${year}/${month}/${day}`, + fileName: fileName + }); + } + } }; diff --git a/modules/invoiceOut/front/global-invoicing/index.html b/modules/invoiceOut/front/global-invoicing/index.html index 3f0a10650..52bf8b619 100644 --- a/modules/invoiceOut/front/global-invoicing/index.html +++ b/modules/invoiceOut/front/global-invoicing/index.html @@ -55,7 +55,11 @@ {{::error.address.nickname}} - {{::error.message}} + + {{::error.message}} + diff --git a/modules/invoiceOut/front/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js index 70838a5a2..2e4ac1888 100644 --- a/modules/invoiceOut/front/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -108,9 +108,18 @@ class Controller extends Section { this.$http.post(`InvoiceOuts/invoiceClient`, params) .then(() => this.invoiceNext()) .catch(res => { - const message = res.data?.error?.message || res.message; if (res.status >= 400 && res.status < 500) { - this.errors.unshift({address, message}); + const error = res.data?.error; + let isWarning; + switch(error?.code) { + case 'pdfError': + case 'mailNotSent': + isWarning = true; + break; + } + + const message = error?.message || res.message; + this.errors.unshift({address, message, isWarning}); this.invoiceNext(); } else { this.invoicing = false; diff --git a/print/core/smtp.js b/print/core/smtp.js index 61b115b5a..04bede5b5 100644 --- a/print/core/smtp.js +++ b/print/core/smtp.js @@ -46,14 +46,21 @@ module.exports = { const fileNames = attachments.join(',\n'); await db.rawSql(` - INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status) - VALUES (?, ?, 1, ?, ?, ?, ?)`, [ + INSERT INTO vn.mail + SET receiver = ?, + replyTo = ?, + sent = ?, + subject = ?, + body = ?, + attachment = ?, + status = ?`, [ options.to, options.replyTo, + error ? 2 : 1, options.subject, options.text || options.html, fileNames, - error && error.message || 'Sent' + error && error.message || 'OK' ]); } From 807596f735d9684419e9757a020a0a43fad680dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Thu, 22 Jun 2023 08:50:18 +0200 Subject: [PATCH 13/39] refs #5772 --- loopback/locale/es.json | 3 ++- .../back/methods/invoiceOut/invoiceClient.js | 4 ++-- .../invoiceOut/specs/invoiceClient.spec.js | 2 +- modules/invoiceOut/back/models/invoice-out.js | 16 +++++++++++++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 2afe5bab5..5870dcb4f 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -295,5 +295,6 @@ "Ticket does not exist": "Este ticket no existe", "Ticket is already signed": "Este ticket ya ha sido firmado", "Fecha fuera de rango": "Fecha fuera de rango", - "Error while generating PDF": "Error while generating PDF" + "Error while generating PDF": "Error al generar PDF", + "Error when sending mail to client": "Error al enviar el correo al cliente" } \ No newline at end of file diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index ff814fea9..5d61bfcd0 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -131,9 +131,9 @@ module.exports = Self => { if (tx) await tx.rollback(); throw e; } - + try { - await Self.makePdf(invoiceId); + await Self.makePdf(invoiceId, myOptions); } catch (err) { console.error(err); throw new UserError('Error while generating PDF', 'pdfError'); diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js index 9a0574dba..e26a0b3f1 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js @@ -23,7 +23,7 @@ describe('InvoiceOut invoiceClient()', () => { const ctx = {req: activeCtx}; it('should make a global invoicing', async() => { - spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true))); + spyOn(models.InvoiceOut, 'makePdf').and.returnValue(new Promise(resolve => resolve(true))); spyOn(models.InvoiceOut, 'invoiceEmail'); const tx = await models.InvoiceOut.beginTransaction({}); diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index bfef57f58..027ec2fb2 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -23,9 +23,19 @@ module.exports = Self => { require('../methods/invoiceOut/negativeBasesCsv')(Self); Self.makePdf = async function(id, options) { - const fields = ['id', 'hasPdf', 'ref', 'issued']; - const invoiceOut = await Self.findById(id, {fields}, options); + let tx; + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + const fields = ['id', 'hasPdf', 'ref', 'issued']; + const invoiceOut = await Self.findById(id, {fields}, myOptions); const invoiceReport = new print.Report('invoice', { reference: invoiceOut.ref }); @@ -42,7 +52,7 @@ module.exports = Self => { await invoiceOut.updateAttributes({ hasPdf: true - }, options); + }, myOptions); if (process.env.NODE_ENV !== 'test') { await print.storage.write(buffer, { From 18a810646136d890f9a38035d20c12720e84f079 Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 22 Jun 2023 08:56:27 +0200 Subject: [PATCH 14/39] refs #5339 fix after save --- modules/worker/back/models/operator.js | 4 ++-- modules/worker/back/models/operator.json | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/modules/worker/back/models/operator.js b/modules/worker/back/models/operator.js index 63b5154ed..db1ac7e49 100644 --- a/modules/worker/back/models/operator.js +++ b/modules/worker/back/models/operator.js @@ -1,10 +1,10 @@ module.exports = function(Self) { Self.observe('after save', async function(ctx) { - const instance = ctx.data; + const instance = ctx.data || ctx.instance; const models = Self.app.models; const options = ctx.options; - if (!instance.sectorFk || !instance.labelerFk) return; + if (!instance?.sectorFk || !instance?.labelerFk) return; const sector = await models.Sector.findById(instance.sectorFk, { fields: ['mainPrinterFk'] diff --git a/modules/worker/back/models/operator.json b/modules/worker/back/models/operator.json index 9433a0fd5..2417078e1 100644 --- a/modules/worker/back/models/operator.json +++ b/modules/worker/back/models/operator.json @@ -16,18 +16,12 @@ "type": "number" }, "trainFk": { - "type": "number", - "required": true + "type": "number" }, "itemPackingTypeFk": { - "type": "string", - "required": true + "type": "string" }, "warehouseFk": { - "type": "number", - "required": true - }, - "sectorFk": { "type": "number" }, "labelerFk": { From 9d1a181b73e7738dde3fa5cde757416e3cfd1e76 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 22 Jun 2023 11:10:56 +0200 Subject: [PATCH 15/39] refs #5772 feat: si falla al enviar el pdf, envia un rocket al comercial --- .../front/global-invoicing/index.js | 69 ++++++++++++------- .../front/global-invoicing/locale/en.yml | 1 + .../front/global-invoicing/locale/es.yml | 3 +- 3 files changed, 49 insertions(+), 24 deletions(-) create mode 100644 modules/invoiceOut/front/global-invoicing/locale/en.yml diff --git a/modules/invoiceOut/front/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js index 2e4ac1888..36552d25b 100644 --- a/modules/invoiceOut/front/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -7,29 +7,29 @@ class Controller extends Section { $onInit() { const date = Date.vnNew(); Object.assign(this, { - maxShipped: new Date(date.getFullYear(), date.getMonth(), 0), - clientsToInvoice: 'all', + maxShipped: new Date(date.getFullYear(), date.getMonth(), 0), + clientsToInvoice: 'all', }); this.$http.get('UserConfigs/getUserConfig') - .then(res => { - this.companyFk = res.data.companyFk; - this.getInvoiceDate(this.companyFk); - }); - } + .then(res => { + this.companyFk = res.data.companyFk; + this.getInvoiceDate(this.companyFk); + }); + } - getInvoiceDate(companyFk) { - const params = { companyFk: companyFk }; + getInvoiceDate(companyFk) { + const params = {companyFk: companyFk}; this.fetchInvoiceDate(params); - } + } - fetchInvoiceDate(params) { - this.$http.get('InvoiceOuts/getInvoiceDate', { params }) - .then(res => { - this.minInvoicingDate = res.data.issued ? new Date(res.data.issued) : null; - this.invoiceDate = this.minInvoicingDate; - }); - } + fetchInvoiceDate(params) { + this.$http.get('InvoiceOuts/getInvoiceDate', {params}) + .then(res => { + this.minInvoicingDate = res.data.issued ? new Date(res.data.issued) : null; + this.invoiceDate = this.minInvoicingDate; + }); + } stopInvoicing() { this.status = 'stopping'; @@ -111,11 +111,34 @@ class Controller extends Section { if (res.status >= 400 && res.status < 500) { const error = res.data?.error; let isWarning; - switch(error?.code) { - case 'pdfError': - case 'mailNotSent': - isWarning = true; - break; + const filter = { + where: { + id: address.clientId + } + }; + switch (error?.code) { + case 'pdfError': + isWarning = true; + break; + case 'mailNotSent': + this.$http.get(`Clients/findOne`, {filter}) + .then(res => { + const salesPersonFk = res.data.salesPersonFk; + if (!salesPersonFk) return; + + const message = this.$t('Mail not sent', { + clientId: address.clientId, + clientUrl: `${window.location.origin}/#!/client/${this.clientId}/summary` + }); + + const params = { + workerId: salesPersonFk, + message: message, + }; + this.$http.post('Chats/sendCheckingPresence', params); + }); + isWarning = true; + break; } const message = error?.message || res.message; @@ -126,7 +149,7 @@ class Controller extends Section { this.status = 'done'; throw new UserError(`Critical invoicing error, proccess stopped`); } - }) + }); } invoiceNext() { diff --git a/modules/invoiceOut/front/global-invoicing/locale/en.yml b/modules/invoiceOut/front/global-invoicing/locale/en.yml new file mode 100644 index 000000000..171822ef7 --- /dev/null +++ b/modules/invoiceOut/front/global-invoicing/locale/en.yml @@ -0,0 +1 @@ +Mail not sent: There has been an error sending the invoice to the client {{clientId}} {{clientUrl}}, please check the email address diff --git a/modules/invoiceOut/front/global-invoicing/locale/es.yml b/modules/invoiceOut/front/global-invoicing/locale/es.yml index 5b1f7e883..4c31df729 100644 --- a/modules/invoiceOut/front/global-invoicing/locale/es.yml +++ b/modules/invoiceOut/front/global-invoicing/locale/es.yml @@ -18,4 +18,5 @@ Invoice out: Facturar One client: Un solo cliente Choose a valid client: Selecciona un cliente válido Stop: Parar -Critical invoicing error, proccess stopped: Error crítico al facturar, proceso detenido \ No newline at end of file +Critical invoicing error, proccess stopped: Error crítico al facturar, proceso detenido +Mail not sent: Se ha producido un fallo al enviar la factura al cliente {{clientId}} {{clientUrl}}, por favor revisa la dirección de correo electrónico From 591fc14ffaf192fea7683b63beccc64994513391 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 22 Jun 2023 12:48:50 +0200 Subject: [PATCH 16/39] refs #5772 refactor: movido codigo para enviar rocket al back --- loopback/locale/en.json | 3 +- loopback/locale/es.json | 6 ++-- .../back/methods/invoiceOut/invoiceClient.js | 34 ++++++++++++++----- .../back/methods/invoiceOut/invoiceEmail.js | 2 +- modules/invoiceOut/back/models/invoice-out.js | 17 ++-------- .../front/global-invoicing/index.js | 18 ---------- .../front/global-invoicing/locale/en.yml | 1 - .../front/global-invoicing/locale/es.yml | 1 - 8 files changed, 35 insertions(+), 47 deletions(-) delete mode 100644 modules/invoiceOut/front/global-invoicing/locale/en.yml diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 7a973460d..40df9a712 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -175,5 +175,6 @@ "Pass expired": "The password has expired, change it from Salix", "Can't transfer claimed sales": "Can't transfer claimed sales", "Invalid quantity": "Invalid quantity", - "Failed to upload delivery note": "Error to upload delivery note {{id}}" + "Failed to upload delivery note": "Error to upload delivery note {{id}}", + "Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 5870dcb4f..ce93b27ce 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -296,5 +296,7 @@ "Ticket is already signed": "Este ticket ya ha sido firmado", "Fecha fuera de rango": "Fecha fuera de rango", "Error while generating PDF": "Error al generar PDF", - "Error when sending mail to client": "Error al enviar el correo al cliente" -} \ No newline at end of file + "Error when sending mail to client": "Error al enviar el correo al cliente", + "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico" + +} diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index 5d61bfcd0..d010ef631 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -51,6 +51,9 @@ module.exports = Self => { const args = ctx.args; const models = Self.app.models; const myOptions = {userId: ctx.req.accessToken.userId}; + const $t = ctx.req.__; // $translate + const origin = ctx.req.headers.origin; + let tx; if (typeof options == 'object') @@ -119,7 +122,7 @@ module.exports = Self => { include: { relation: 'client', scope: { - fields: ['email', 'isToBeMailed'] + fields: ['email', 'isToBeMailed', 'salesPersonFk'] } } }, myOptions); @@ -131,21 +134,34 @@ module.exports = Self => { if (tx) await tx.rollback(); throw e; } - + try { - await Self.makePdf(invoiceId, myOptions); + await Self.makePdf(invoiceId); } catch (err) { console.error(err); throw new UserError('Error while generating PDF', 'pdfError'); } if (invoiceOut.client().isToBeMailed) { - ctx.args = { - reference: invoiceOut.ref, - recipientId: args.clientId, - recipient: invoiceOut.client().email - }; - await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); + try { + ctx.args = { + reference: invoiceOut.ref, + recipientId: args.clientId, + recipient: invoiceOut.client().email + }; + await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); + } catch (err) { + const message = $t('Mail not sent', { + clientId: args.clientId, + clientUrl: `${origin}/#!/claim/${args.id}/summary` + }); + const salesPersonId = invoiceOut.client().salesPersonFk; + + if (salesPersonId) + await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); + + throw new UserError('Error when sending mail to client', 'mailNotSent'); + } } else { const query = ` CALL vn.report_print( diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js index 45894e9c5..113526484 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js @@ -77,7 +77,7 @@ module.exports = Self => { try { return email.send(mailOptions); - } catch(err) { + } catch (err) { throw new UserError('Error when sending mail to client', 'mailNotSent'); } }; diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 027ec2fb2..0d5ed021c 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -23,19 +23,8 @@ module.exports = Self => { require('../methods/invoiceOut/negativeBasesCsv')(Self); Self.makePdf = async function(id, options) { - let tx; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } - const fields = ['id', 'hasPdf', 'ref', 'issued']; - const invoiceOut = await Self.findById(id, {fields}, myOptions); + const invoiceOut = await Self.findById(id, {fields}, options); const invoiceReport = new print.Report('invoice', { reference: invoiceOut.ref }); @@ -52,7 +41,7 @@ module.exports = Self => { await invoiceOut.updateAttributes({ hasPdf: true - }, myOptions); + }, options); if (process.env.NODE_ENV !== 'test') { await print.storage.write(buffer, { @@ -61,5 +50,5 @@ module.exports = Self => { fileName: fileName }); } - } + }; }; diff --git a/modules/invoiceOut/front/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js index 36552d25b..930dfd459 100644 --- a/modules/invoiceOut/front/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -118,25 +118,7 @@ class Controller extends Section { }; switch (error?.code) { case 'pdfError': - isWarning = true; - break; case 'mailNotSent': - this.$http.get(`Clients/findOne`, {filter}) - .then(res => { - const salesPersonFk = res.data.salesPersonFk; - if (!salesPersonFk) return; - - const message = this.$t('Mail not sent', { - clientId: address.clientId, - clientUrl: `${window.location.origin}/#!/client/${this.clientId}/summary` - }); - - const params = { - workerId: salesPersonFk, - message: message, - }; - this.$http.post('Chats/sendCheckingPresence', params); - }); isWarning = true; break; } diff --git a/modules/invoiceOut/front/global-invoicing/locale/en.yml b/modules/invoiceOut/front/global-invoicing/locale/en.yml deleted file mode 100644 index 171822ef7..000000000 --- a/modules/invoiceOut/front/global-invoicing/locale/en.yml +++ /dev/null @@ -1 +0,0 @@ -Mail not sent: There has been an error sending the invoice to the client {{clientId}} {{clientUrl}}, please check the email address diff --git a/modules/invoiceOut/front/global-invoicing/locale/es.yml b/modules/invoiceOut/front/global-invoicing/locale/es.yml index 4c31df729..ed61e832a 100644 --- a/modules/invoiceOut/front/global-invoicing/locale/es.yml +++ b/modules/invoiceOut/front/global-invoicing/locale/es.yml @@ -19,4 +19,3 @@ One client: Un solo cliente Choose a valid client: Selecciona un cliente válido Stop: Parar Critical invoicing error, proccess stopped: Error crítico al facturar, proceso detenido -Mail not sent: Se ha producido un fallo al enviar la factura al cliente {{clientId}} {{clientUrl}}, por favor revisa la dirección de correo electrónico From b7cd32f4ce2bb0a558e4ca17e887f4400f2f2b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Thu, 22 Jun 2023 13:18:16 +0200 Subject: [PATCH 17/39] refs #5772 eliminar tags en report invoice --- print/templates/reports/invoice/invoice.html | 9 --------- 1 file changed, 9 deletions(-) diff --git a/print/templates/reports/invoice/invoice.html b/print/templates/reports/invoice/invoice.html index 32138befc..45b6c3934 100644 --- a/print/templates/reports/invoice/invoice.html +++ b/print/templates/reports/invoice/invoice.html @@ -106,15 +106,6 @@ {{sale.vatType}} {{saleImport(sale) | currency('EUR', $i18n.locale)}} - From 389b53ceff499dc3d186df5886b9495b5e893356 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 22 Jun 2023 13:37:43 +0200 Subject: [PATCH 18/39] refs #5772 fix: back test --- .../back/methods/invoiceOut/specs/invoiceClient.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js index e26a0b3f1..0faa8fe1a 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/invoiceClient.spec.js @@ -18,7 +18,9 @@ describe('InvoiceOut invoiceClient()', () => { accessToken: {userId: userId}, __: value => { return value; - } + }, + headers: {origin: 'http://localhost'} + }; const ctx = {req: activeCtx}; From 344ba298f8390ebd7369dc62c989481572ff1063 Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 22 Jun 2023 14:27:33 +0200 Subject: [PATCH 19/39] refs #5832 232801 init --- CHANGELOG.md | 9 +++++++++ db/changes/232801/.gitkeep | 0 package.json | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 db/changes/232801/.gitkeep diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2ebcd62..cbfc6e9e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2328.01] - 2023-07-13 + +### Added + +### Changed + +### Fixed + + ## [2326.01] - 2023-06-29 ### Added diff --git a/db/changes/232801/.gitkeep b/db/changes/232801/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/package.json b/package.json index 4358c86a7..64c374841 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.26.01", + "version": "23.28.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", From 47a4a9950f30fd3c87cdff002ac12a85f5695c48 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 11:42:30 +0200 Subject: [PATCH 20/39] refs #5772 Parallelism added to PDF generation --- .eslintrc.yml | 2 +- db/changes/232601/01-invoiceOutPdf.sql | 13 ++ db/dump/fixtures.sql | 3 + .../back/methods/invoiceOut/invoiceClient.js | 133 +++++------------- .../methods/invoiceOut/makePdfAndNotify.js | 87 ++++++++++++ modules/invoiceOut/back/model-config.json | 3 + .../back/models/invoice-out config.json | 22 +++ modules/invoiceOut/back/models/invoice-out.js | 1 + .../front/global-invoicing/index.html | 16 ++- .../front/global-invoicing/index.js | 86 ++++++----- .../front/global-invoicing/locale/es.yml | 1 + .../front/global-invoicing/style.scss | 14 +- 12 files changed, 231 insertions(+), 150 deletions(-) create mode 100644 db/changes/232601/01-invoiceOutPdf.sql create mode 100644 modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js create mode 100644 modules/invoiceOut/back/models/invoice-out config.json diff --git a/.eslintrc.yml b/.eslintrc.yml index ee20324ff..edbc47195 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -17,7 +17,7 @@ rules: camelcase: 0 default-case: 0 no-eq-null: 0 - no-console: ["error"] + no-console: ["warn"] no-warning-comments: 0 no-empty: [error, allowEmptyCatch: true] complexity: 0 diff --git a/db/changes/232601/01-invoiceOutPdf.sql b/db/changes/232601/01-invoiceOutPdf.sql new file mode 100644 index 000000000..38e0b8bbb --- /dev/null +++ b/db/changes/232601/01-invoiceOutPdf.sql @@ -0,0 +1,13 @@ +INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId) + VALUES + ('InvoiceOut','makePdfAndNotify','WRITE','ALLOW','ROLE','invoicing'), + ('InvoiceOutConfig','*','READ','ALLOW','ROLE','invoicing'); + +CREATE OR REPLACE TABLE vn.invoiceOutConfig ( + id INT UNSIGNED auto_increment NOT NULL, + parallelism int UNSIGNED DEFAULT 1 NOT NULL, + PRIMARY KEY (id) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8mb3 +COLLATE=utf8mb3_unicode_ci; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 9c930222f..c4338a555 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -603,6 +603,9 @@ UPDATE `vn`.`invoiceOut` SET ref = 'T3333333' WHERE id = 3; UPDATE `vn`.`invoiceOut` SET ref = 'T4444444' WHERE id = 4; UPDATE `vn`.`invoiceOut` SET ref = 'A1111111' WHERE id = 5; +INSERT INTO vn.invoiceOutConfig + SET parallelism = 8; + INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`) VALUES (1, 895.76, 89.58, 4722000010), diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index d010ef631..ab076b46f 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -30,15 +30,10 @@ module.exports = Self => { type: 'number', description: 'The company id to invoice', required: true - }, { - arg: 'printerFk', - type: 'number', - description: 'The printer to print', - required: true } ], returns: { - type: 'object', + type: 'number', root: true }, http: { @@ -50,29 +45,23 @@ module.exports = Self => { Self.invoiceClient = async(ctx, options) => { const args = ctx.args; const models = Self.app.models; - const myOptions = {userId: ctx.req.accessToken.userId}; - const $t = ctx.req.__; // $translate - const origin = ctx.req.headers.origin; + + options = typeof options == 'object' + ? Object.assign({}, options) : {}; + options.userId = ctx.req.accessToken.userId; let tx; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } + if (!options.transaction) + tx = options.transaction = await Self.beginTransaction({}); const minShipped = Date.vnNew(); minShipped.setFullYear(args.maxShipped.getFullYear() - 1); let invoiceId; - let invoiceOut; try { const client = await models.Client.findById(args.clientId, { fields: ['id', 'hasToInvoiceByAddress'] - }, myOptions); + }, options); if (client.hasToInvoiceByAddress) { await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [ @@ -80,53 +69,57 @@ module.exports = Self => { args.maxShipped, args.addressId, args.companyFk - ], myOptions); + ], options); } else { await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [ args.maxShipped, client.id, args.companyFk - ], myOptions); + ], options); } - // Make invoice - const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions); + // Check negative bases - // Validates ticket nagative base - const hasAnyNegativeBase = await getNegativeBase(myOptions); + let query = + `SELECT COUNT(*) isSpanishCompany + FROM supplier s + JOIN country c ON c.id = s.countryFk + AND c.code = 'ES' + WHERE s.id = ?`; + const [supplierCompany] = await Self.rawSql(query, [ + args.companyFk + ], options); + + const isSpanishCompany = supplierCompany?.isSpanishCompany; + + query = 'SELECT hasAnyNegativeBase() AS base'; + const [result] = await Self.rawSql(query, null, options); + + const hasAnyNegativeBase = result?.base; if (hasAnyNegativeBase && isSpanishCompany) throw new UserError('Negative basis'); + // Invoicing + query = `SELECT invoiceSerial(?, ?, ?) AS serial`; const [invoiceSerial] = await Self.rawSql(query, [ client.id, args.companyFk, 'G' - ], myOptions); + ], options); const serialLetter = invoiceSerial.serial; query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`; await Self.rawSql(query, [ serialLetter, args.invoiceDate - ], myOptions); + ], options); - const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions); + const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, options); if (!newInvoice) throw new UserError('No tickets to invoice', 'notInvoiced'); - await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions); - - invoiceOut = await models.InvoiceOut.findById(newInvoice.id, { - fields: ['id', 'ref', 'clientFk'], - include: { - relation: 'client', - scope: { - fields: ['email', 'isToBeMailed', 'salesPersonFk'] - } - } - }, myOptions); - + await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], options); invoiceId = newInvoice.id; if (tx) await tx.commit(); @@ -135,66 +128,6 @@ module.exports = Self => { throw e; } - try { - await Self.makePdf(invoiceId); - } catch (err) { - console.error(err); - throw new UserError('Error while generating PDF', 'pdfError'); - } - - if (invoiceOut.client().isToBeMailed) { - try { - ctx.args = { - reference: invoiceOut.ref, - recipientId: args.clientId, - recipient: invoiceOut.client().email - }; - await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); - } catch (err) { - const message = $t('Mail not sent', { - clientId: args.clientId, - clientUrl: `${origin}/#!/claim/${args.id}/summary` - }); - const salesPersonId = invoiceOut.client().salesPersonFk; - - if (salesPersonId) - await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); - - throw new UserError('Error when sending mail to client', 'mailNotSent'); - } - } else { - const query = ` - CALL vn.report_print( - 'invoice', - ?, - account.myUser_getId(), - JSON_OBJECT('refFk', ?), - 'normal' - );`; - await models.InvoiceOut.rawSql(query, [args.printerFk, invoiceOut.ref]); - } - return invoiceId; }; - - async function getNegativeBase(options) { - const models = Self.app.models; - const query = 'SELECT hasAnyNegativeBase() AS base'; - const [result] = await models.InvoiceOut.rawSql(query, null, options); - return result && result.base; - } - - async function getIsSpanishCompany(companyId, options) { - const models = Self.app.models; - const query = `SELECT COUNT(*) isSpanishCompany - FROM supplier s - JOIN country c ON c.id = s.countryFk - AND c.code = 'ES' - WHERE s.id = ?`; - const [supplierCompany] = await models.InvoiceOut.rawSql(query, [ - companyId - ], options); - - return supplierCompany && supplierCompany.isSpanishCompany; - } }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js new file mode 100644 index 000000000..a999437c0 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js @@ -0,0 +1,87 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('makePdfAndNotify', { + description: 'Create invoice PDF and send it to client', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The invoice id', + required: true, + http: {source: 'path'} + }, { + arg: 'printerFk', + type: 'number', + description: 'The printer to print', + required: true + } + ], + http: { + path: '/:id/makePdfAndNotify', + verb: 'POST' + } + }); + + Self.makePdfAndNotify = async function(ctx, id, printerFk) { + const models = Self.app.models; + + options = typeof options == 'object' + ? Object.assign({}, options) : {}; + options.userId = ctx.req.accessToken.userId; + + try { + await Self.makePdf(id, options); + } catch (err) { + console.error(err); + throw new UserError('Error while generating PDF', 'pdfError'); + } + + const invoiceOut = await Self.findById(id, { + fields: ['ref', 'clientFk'], + include: { + relation: 'client', + scope: { + fields: ['id', 'email', 'isToBeMailed', 'salesPersonFk'] + } + } + }, options); + + const ref = invoiceOut.ref; + const client = invoiceOut.client(); + + if (client.isToBeMailed) { + try { + ctx.args = { + reference: ref, + recipientId: client.id, + recipient: client.email + }; + await Self.invoiceEmail(ctx, ref); + } catch (err) { + const origin = ctx.req.headers.origin; + const message = ctx.req.__('Mail not sent', { + clientId: client.id, + clientUrl: `${origin}/#!/claim/${id}/summary` + }); + const salesPersonId = client.salesPersonFk; + + if (salesPersonId) + await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); + + throw new UserError('Error when sending mail to client', 'mailNotSent'); + } + } else { + const query = ` + CALL vn.report_print( + 'invoice', + ?, + account.myUser_getId(), + JSON_OBJECT('refFk', ?), + 'normal' + );`; + await Self.rawSql(query, [printerFk, ref], options); + } + }; +}; diff --git a/modules/invoiceOut/back/model-config.json b/modules/invoiceOut/back/model-config.json index b190126ea..9e8b119ab 100644 --- a/modules/invoiceOut/back/model-config.json +++ b/modules/invoiceOut/back/model-config.json @@ -2,6 +2,9 @@ "InvoiceOut": { "dataSource": "vn" }, + "InvoiceOutConfig": { + "dataSource": "vn" + }, "InvoiceOutSerial": { "dataSource": "vn" }, diff --git a/modules/invoiceOut/back/models/invoice-out config.json b/modules/invoiceOut/back/models/invoice-out config.json new file mode 100644 index 000000000..d5595f687 --- /dev/null +++ b/modules/invoiceOut/back/models/invoice-out config.json @@ -0,0 +1,22 @@ +{ + "name": "InvoiceOutConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "invoiceOutConfig" + } + }, + "properties": { + "id": { + "id": true, + "type": "number", + "description": "Identifier" + }, + "parallelism": { + "type": "number", + "required": true + } + } +} + + diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 0d5ed021c..9baa1e7e6 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -12,6 +12,7 @@ module.exports = Self => { require('../methods/invoiceOut/createManualInvoice')(Self); require('../methods/invoiceOut/clientsToInvoice')(Self); require('../methods/invoiceOut/invoiceClient')(Self); + require('../methods/invoiceOut/makePdfAndNotify')(Self); require('../methods/invoiceOut/refund')(Self); require('../methods/invoiceOut/invoiceEmail')(Self); require('../methods/invoiceOut/exportationPdf')(Self); diff --git a/modules/invoiceOut/front/global-invoicing/index.html b/modules/invoiceOut/front/global-invoicing/index.html index 52bf8b619..3ece30862 100644 --- a/modules/invoiceOut/front/global-invoicing/index.html +++ b/modules/invoiceOut/front/global-invoicing/index.html @@ -1,7 +1,6 @@ + class="status vn-w-lg vn-pa-md"> @@ -20,8 +19,15 @@ Ended process
-
- {{$ctrl.percentage | percentage: 0}} ({{$ctrl.addressNumber}} {{'of' | translate}} {{$ctrl.nAddresses}}) +
+
+ {{$ctrl.percentage | percentage: 0}} + ({{$ctrl.addressNumber}} of {{$ctrl.nAddresses}}) +
+
+ {{$ctrl.nPdfs}} of {{$ctrl.totalPdfs}} + PDFs +
@@ -141,7 +147,7 @@ + ng-click="$ctrl.status = 'stopping'"> diff --git a/modules/invoiceOut/front/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js index 930dfd459..283e84a6c 100644 --- a/modules/invoiceOut/front/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -9,30 +9,21 @@ class Controller extends Section { Object.assign(this, { maxShipped: new Date(date.getFullYear(), date.getMonth(), 0), clientsToInvoice: 'all', + companyFk: this.vnConfig.companyFk }); - this.$http.get('UserConfigs/getUserConfig') - .then(res => { - this.companyFk = res.data.companyFk; - this.getInvoiceDate(this.companyFk); - }); - } - - getInvoiceDate(companyFk) { - const params = {companyFk: companyFk}; - this.fetchInvoiceDate(params); - } - - fetchInvoiceDate(params) { + const params = {companyFk: this.companyFk}; this.$http.get('InvoiceOuts/getInvoiceDate', {params}) .then(res => { this.minInvoicingDate = res.data.issued ? new Date(res.data.issued) : null; this.invoiceDate = this.minInvoicingDate; }); - } - stopInvoicing() { - this.status = 'stopping'; + const filter = {fields: ['parallelism']}; + this.$http.get('InvoiceOutConfigs/findOne', {filter}) + .then(res => { + this.parallelism = res.data.parallelism || 1; + }); } makeInvoice() { @@ -70,8 +61,11 @@ class Controller extends Section { if (!this.addresses.length) throw new UserError(`There aren't tickets to invoice`); + this.nRequests = 0; + this.nPdfs = 0; + this.totalPdfs = 0; this.addressIndex = 0; - return this.invoiceOut(); + this.invoiceClient(); }) .catch(err => this.handleError(err)); } catch (err) { @@ -85,8 +79,11 @@ class Controller extends Section { throw err; } - invoiceOut() { - if (this.addressIndex == this.addresses.length || this.status == 'stopping') { + invoiceClient() { + if (this.nRequests == this.parallelism || this.isInvoicing) return; + + if (this.addressIndex >= this.addresses.length || this.status == 'stopping') { + if (this.nRequests) return; this.invoicing = false; this.status = 'done'; return; @@ -95,36 +92,27 @@ class Controller extends Section { this.status = 'invoicing'; const address = this.addresses[this.addressIndex]; this.currentAddress = address; + this.isInvoicing = true; const params = { clientId: address.clientId, addressId: address.id, invoiceDate: this.invoiceDate, maxShipped: this.maxShipped, - companyFk: this.companyFk, - printerFk: this.printerFk, + companyFk: this.companyFk }; this.$http.post(`InvoiceOuts/invoiceClient`, params) - .then(() => this.invoiceNext()) + .then(res => { + this.isInvoicing = false; + if (res.data) + this.makePdfAndNotify(res.data, address); + this.invoiceNext(); + }) .catch(res => { + this.isInvoicing = false; if (res.status >= 400 && res.status < 500) { - const error = res.data?.error; - let isWarning; - const filter = { - where: { - id: address.clientId - } - }; - switch (error?.code) { - case 'pdfError': - case 'mailNotSent': - isWarning = true; - break; - } - - const message = error?.message || res.message; - this.errors.unshift({address, message, isWarning}); + this.invoiceError(address, res); this.invoiceNext(); } else { this.invoicing = false; @@ -136,7 +124,27 @@ class Controller extends Section { invoiceNext() { this.addressIndex++; - this.invoiceOut(); + this.invoiceClient(); + } + + makePdfAndNotify(invoiceId, address) { + this.nRequests++; + this.totalPdfs++; + const params = {printerFk: this.printerFk}; + this.$http.post(`InvoiceOuts/${invoiceId}/makePdfAndNotify`, params) + .catch(res => { + this.invoiceError(address, res, true); + }) + .finally(() => { + this.nPdfs++; + this.nRequests--; + this.invoiceClient(); + }); + } + + invoiceError(address, res, isWarning) { + const message = res.data?.error?.message || res.message; + this.errors.unshift({address, message, isWarning}); } get nAddresses() { diff --git a/modules/invoiceOut/front/global-invoicing/locale/es.yml b/modules/invoiceOut/front/global-invoicing/locale/es.yml index ed61e832a..f1a411ba1 100644 --- a/modules/invoiceOut/front/global-invoicing/locale/es.yml +++ b/modules/invoiceOut/front/global-invoicing/locale/es.yml @@ -10,6 +10,7 @@ Build packaging tickets: Generando tickets de embalajes Address id: Id dirección Printer: Impresora of: de +PDFs: PDFs Client: Cliente Current client id: Id cliente actual Invoicing client: Facturando cliente diff --git a/modules/invoiceOut/front/global-invoicing/style.scss b/modules/invoiceOut/front/global-invoicing/style.scss index 6fdfac0ba..3ad767aba 100644 --- a/modules/invoiceOut/front/global-invoicing/style.scss +++ b/modules/invoiceOut/front/global-invoicing/style.scss @@ -1,17 +1,21 @@ @import "variables"; -vn-invoice-out-global-invoicing{ - - h5{ +vn-invoice-out-global-invoicing { + h5 { color: $color-primary; } - + .status { + height: 80px; + display: flex; + align-items: center; + justify-content: center; + gap: 20px; + } #error { line-break: normal; overflow-wrap: break-word; white-space: normal; } - } From 8c0e8e9e1223acc2ee55401fb2b986b4e07bbfe4 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 11:50:40 +0200 Subject: [PATCH 21/39] refs #5772 Fixes fetching parallelism from front --- modules/invoiceOut/front/global-invoicing/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/invoiceOut/front/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js index 283e84a6c..9a936611a 100644 --- a/modules/invoiceOut/front/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -9,7 +9,8 @@ class Controller extends Section { Object.assign(this, { maxShipped: new Date(date.getFullYear(), date.getMonth(), 0), clientsToInvoice: 'all', - companyFk: this.vnConfig.companyFk + companyFk: this.vnConfig.companyFk, + parallelism: 1 }); const params = {companyFk: this.companyFk}; @@ -22,7 +23,12 @@ class Controller extends Section { const filter = {fields: ['parallelism']}; this.$http.get('InvoiceOutConfigs/findOne', {filter}) .then(res => { - this.parallelism = res.data.parallelism || 1; + if (res.data.parallelism) + this.parallelism = res.data.parallelism; + }) + .catch(res => { + if (res.status == 404) return; + throw res; }); } From c9f11830a202dc5d15423fa43cd79dc3bad971a1 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 20:03:56 +0200 Subject: [PATCH 22/39] refs #5772 smtp.send() error throw flow fix --- print/core/smtp.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/print/core/smtp.js b/print/core/smtp.js index 04bede5b5..7ff9ca41d 100644 --- a/print/core/smtp.js +++ b/print/core/smtp.js @@ -20,14 +20,18 @@ module.exports = { options.to = config.app.senderEmail; } + let res; let error; - return this.transporter.sendMail(options).catch(err => { + try { + res = await this.transporter.sendMail(options); + } catch (err) { error = err; - throw err; - }).finally(async() => { + } finally { await this.mailLog(options, error); - }); + } + + return res; }, async mailLog(options, error) { From 665339c8d25252c9fc0eced9b0fd8dea1e8de239 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 22:02:33 +0200 Subject: [PATCH 23/39] refs #5772 Fix for unhandled ENOENT error --- .../back/methods/invoiceOut/download.js | 68 +++++++++---------- print/core/email.js | 4 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index 49bba046b..ce80ecffd 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -42,42 +42,42 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const invoiceOut = await models.InvoiceOut.findById(id, { + fields: ['ref', 'issued'] + }, myOptions); + + const issued = invoiceOut.issued; + const year = issued.getFullYear().toString(); + const month = (issued.getMonth() + 1).toString(); + const day = issued.getDate().toString(); + + const container = await models.InvoiceContainer.container(year); + const rootPath = container.client.root; + const src = path.join(rootPath, year, month, day); + const fileName = `${year}${invoiceOut.ref}.pdf`; + const fileSrc = path.join(src, fileName); + + const file = { + path: fileSrc, + contentType: 'application/pdf', + name: fileName + }; + try { - const invoiceOut = await models.InvoiceOut.findById(id, { - fields: ['ref', 'issued'] - }, myOptions); - - const issued = invoiceOut.issued; - const year = issued.getFullYear().toString(); - const month = (issued.getMonth() + 1).toString(); - const day = issued.getDate().toString(); - - const container = await models.InvoiceContainer.container(year); - const rootPath = container.client.root; - const src = path.join(rootPath, year, month, day); - const fileName = `${year}${invoiceOut.ref}.pdf`; - const fileSrc = path.join(src, fileName); - - const file = { - path: fileSrc, - contentType: 'application/pdf', - name: fileName - }; - - try { - await fs.access(file.path); - } catch (error) { - await Self.createPdf(ctx, id, myOptions); - } - - const stream = fs.createReadStream(file.path); - - return [stream, file.contentType, `filename="${file.name}"`]; + await fs.access(file.path); } catch (error) { - if (error.code === 'ENOENT') - throw new UserError('The PDF document does not exist'); - - throw error; + await Self.createPdf(ctx, id, myOptions); } + + const stream = await fs.createReadStream(file.path); + // XXX: To prevent unhandled ENOENT error + // https://stackoverflow.com/questions/17136536/is-enoent-from-fs-createreadstream-uncatchable + stream.on('error', err => { + const e = new Error(err.message); + err.stack = e.stack; + console.error(err); + }); + + return [stream, file.contentType, `filename="${file.name}"`]; }; }; diff --git a/print/core/email.js b/print/core/email.js index 6e96f5c2e..43e92e619 100644 --- a/print/core/email.js +++ b/print/core/email.js @@ -63,12 +63,12 @@ class Email extends Component { await getAttachments(componentPath, component.attachments); if (component.components) - await getSubcomponentAttachments(component) + await getSubcomponentAttachments(component); } } } - await getSubcomponentAttachments(instance) + await getSubcomponentAttachments(instance); if (this.attachments) await getAttachments(this.path, this.attachments); From 7660d7685061241206e6c5324113fdbdc29b1959 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 22:52:03 +0200 Subject: [PATCH 24/39] refs #5772 Test environment fix --- .../back/methods/invoiceOut/createPdf.js | 17 +++----- .../back/methods/invoiceOut/download.js | 39 ++++++++----------- .../back/methods/invoiceOut/invoiceClient.js | 1 - .../back/methods/invoiceOut/invoiceEmail.js | 16 +++----- modules/invoiceOut/back/models/invoice-out.js | 30 +++++++++----- 5 files changed, 49 insertions(+), 54 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js index 8cc584c94..c4d50a4ee 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js +++ b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js @@ -24,20 +24,15 @@ module.exports = Self => { Self.createPdf = async function(ctx, id, options) { const models = Self.app.models; + options = typeof options == 'object' + ? Object.assign({}, options) : {}; let tx; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } + if (!options.transaction) + tx = options.transaction = await Self.beginTransaction({}); try { - const invoiceOut = await Self.findById(id, {fields: ['hasPdf']}, myOptions); + const invoiceOut = await Self.findById(id, {fields: ['hasPdf']}, options); if (invoiceOut.hasPdf) { const canCreatePdf = await models.ACL.checkAccessAcl(ctx, 'InvoiceOut', 'canCreatePdf', 'WRITE'); @@ -45,7 +40,7 @@ module.exports = Self => { throw new UserError(`You don't have enough privileges`); } - await Self.makePdf(id, myOptions); + await Self.makePdf(id, options); if (tx) await tx.commit(); } catch (err) { diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index ce80ecffd..4c76f7c07 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -1,6 +1,5 @@ const fs = require('fs-extra'); const path = require('path'); -const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('download', { @@ -37,39 +36,27 @@ module.exports = Self => { Self.download = async function(ctx, id, options) { const models = Self.app.models; - const myOptions = {}; + options = typeof options == 'object' + ? Object.assign({}, options) : {}; - if (typeof options == 'object') - Object.assign(myOptions, options); + const pdfFile = await Self.filePath(id, options); - const invoiceOut = await models.InvoiceOut.findById(id, { - fields: ['ref', 'issued'] - }, myOptions); - - const issued = invoiceOut.issued; - const year = issued.getFullYear().toString(); - const month = (issued.getMonth() + 1).toString(); - const day = issued.getDate().toString(); - - const container = await models.InvoiceContainer.container(year); + const container = await models.InvoiceContainer.container(pdfFile.year); const rootPath = container.client.root; - const src = path.join(rootPath, year, month, day); - const fileName = `${year}${invoiceOut.ref}.pdf`; - const fileSrc = path.join(src, fileName); const file = { - path: fileSrc, + path: path.join(rootPath, pdfFile.path, pdfFile.name), contentType: 'application/pdf', - name: fileName + name: pdfFile.name }; try { await fs.access(file.path); } catch (error) { - await Self.createPdf(ctx, id, myOptions); + await Self.createPdf(ctx, id, options); } - const stream = await fs.createReadStream(file.path); + let stream = await fs.createReadStream(file.path); // XXX: To prevent unhandled ENOENT error // https://stackoverflow.com/questions/17136536/is-enoent-from-fs-createreadstream-uncatchable stream.on('error', err => { @@ -78,6 +65,14 @@ module.exports = Self => { console.error(err); }); - return [stream, file.contentType, `filename="${file.name}"`]; + if (process.env.NODE_ENV == 'test') { + try { + await fs.access(file.path); + } catch (error) { + stream = null; + } + } + + return [stream, file.contentType, `filename="${pdfFile.name}"`]; }; }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index ab076b46f..ddd008942 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -45,7 +45,6 @@ module.exports = Self => { Self.invoiceClient = async(ctx, options) => { const args = ctx.args; const models = Self.app.models; - options = typeof options == 'object' ? Object.assign({}, options) : {}; options.userId = ctx.req.accessToken.userId; diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js index 113526484..400328fef 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js @@ -11,20 +11,17 @@ module.exports = Self => { type: 'string', required: true, http: {source: 'path'} - }, - { + }, { arg: 'recipient', type: 'string', description: 'The recipient email', required: true, - }, - { + }, { arg: 'replyTo', type: 'string', description: 'The sender email to reply to', required: false - }, - { + }, { arg: 'recipientId', type: 'number', description: 'The recipient id to send to the recipient preferred language', @@ -43,16 +40,13 @@ module.exports = Self => { Self.invoiceEmail = async(ctx, reference) => { const args = Object.assign({}, ctx.args); - const {InvoiceOut} = Self.app.models; const params = { recipient: args.recipient, lang: ctx.req.getLocale() }; - const invoiceOut = await InvoiceOut.findOne({ - where: { - ref: reference - } + const invoiceOut = await Self.findOne({ + where: {ref: reference} }); delete args.ctx; diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 9baa1e7e6..d3aaf3b3d 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -1,4 +1,5 @@ const print = require('vn-print'); +const path = require('path'); module.exports = Self => { require('../methods/invoiceOut/filter')(Self); @@ -23,20 +24,31 @@ module.exports = Self => { require('../methods/invoiceOut/negativeBases')(Self); require('../methods/invoiceOut/negativeBasesCsv')(Self); - Self.makePdf = async function(id, options) { - const fields = ['id', 'hasPdf', 'ref', 'issued']; + Self.filePath = async function(id, options) { + const fields = ['ref', 'issued']; const invoiceOut = await Self.findById(id, {fields}, options); - const invoiceReport = new print.Report('invoice', { - reference: invoiceOut.ref - }); - const buffer = await invoiceReport.toPdfStream(); const issued = invoiceOut.issued; const year = issued.getFullYear().toString(); const month = (issued.getMonth() + 1).toString(); const day = issued.getDate().toString(); - const fileName = `${year}${invoiceOut.ref}.pdf`; + return { + path: path.join(year, month, day), + name: `${year}${invoiceOut.ref}.pdf`, + year + }; + }; + + Self.makePdf = async function(id, options) { + const fields = ['id', 'hasPdf', 'ref']; + const invoiceOut = await Self.findById(id, {fields}, options); + const invoiceReport = new print.Report('invoice', { + reference: invoiceOut.ref + }); + const buffer = await invoiceReport.toPdfStream(); + + const pdfFile = await Self.filePath(id, options); // Store invoice @@ -47,8 +59,8 @@ module.exports = Self => { if (process.env.NODE_ENV !== 'test') { await print.storage.write(buffer, { type: 'invoice', - path: `${year}/${month}/${day}`, - fileName: fileName + path: pdfFile.path, + fileName: pdfFile.name }); } }; From f70df714df81b75d675f37a4751c5670b101114e Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 16:31:24 +0200 Subject: [PATCH 25/39] refs #5772, #5900 InvoiceOut back test removed, logs improved --- e2e/helpers/selectors.js | 12 - front/salix/components/log/index.html | 199 +++++++----- front/salix/components/log/index.js | 242 ++++++++++---- front/salix/components/log/locale/es.yml | 1 + front/salix/components/log/style.scss | 296 +++++++++++------- loopback/common/methods/log/pitInstance.js | 56 ++++ loopback/common/models/log.js | 1 + .../methods/invoiceOut/specs/download.spec.js | 28 -- 8 files changed, 545 insertions(+), 290 deletions(-) create mode 100644 loopback/common/methods/log/pitInstance.js delete mode 100644 modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index b8eaa99a1..d92cb129b 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -479,9 +479,6 @@ export default { fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance > span', firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance' }, - itemLog: { - anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', - }, ticketSummary: { header: 'vn-ticket-summary > vn-card > h5', state: 'vn-ticket-summary vn-label-value[label="State"] > section > span', @@ -667,15 +664,6 @@ export default { thirdRemoveRequestButton: 'vn-ticket-request-index vn-tr:nth-child(3) vn-icon[icon="delete"]', thirdRequestQuantity: 'vn-ticket-request-index vn-table vn-tr:nth-child(3) > vn-td:nth-child(6) vn-input-number', saveButton: 'vn-ticket-request-create button[type=submit]', - - }, - ticketLog: { - firstTD: 'vn-ticket-log vn-table vn-td:nth-child(1)', - logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]', - user: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(2)', - action: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(4)', - changes: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr table tr:nth-child(2) td.after', - id: 'vn-ticket-log vn-tr:nth-child(1) table tr:nth-child(1) td.before' }, ticketService: { addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button', diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 8b33c811b..ac4e5cb6e 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -2,8 +2,6 @@ vn-id="model" url="{{$ctrl.url}}" filter="$ctrl.filter" - link="{originFk: $ctrl.originId}" - where="{changedModel: $ctrl.changedModel, changedModelId: $ctrl.changedModelId}" data="$ctrl.logs" order="creationDate DESC, id DESC" limit="20"> @@ -17,90 +15,107 @@ -
-
- - - - -
+
+
+
+ {{$ctrl.modelI18n}} #{{::originLog.originFk}} +
- -
-
- - {{::log.changedModelI18n}} - -
-
- {{::$ctrl.relativeDate(log.creationDate)}} - - -
+
+
+ + + + +
+
-
- #{{::log.changedModelId}} - - - {{::log.changedModelValue}} -
-
- - - - - - {{::prop.nameI18n}}: +
+
+
+ + + + {{::modelLog.modelI18n}} - , - -
-
- - {{::prop.nameI18n}}: - - - - ← - -
+ #{{::modelLog.id}} + {{::modelLog.showValue}}
- - - {{::log.description}} - - + +
+
+ {{::$ctrl.relativeDate(log.creationDate)}} +
+
+ + + + +
+
+
+ + + + + + {{::prop.nameI18n}}: + + , + +
+
+ + {{::prop.nameI18n}}: + + + + ← + +
+
+
+ + {{::log.description}} + + +
+
+
- + + + + + +
+
+ {{$ctrl.instance.modelLog.modelI18n}} #{{$ctrl.instance.modelLog.id}} +
+
+
+ + {{::prop.nameI18n}}: + + +
+
+ No data +
+
+
+
+
diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index 8aea255a2..ecbeaefe7 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -3,7 +3,10 @@ import Section from '../section'; import {hashToColor} from 'core/lib/string'; import './style.scss'; -const 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)?$/; +const validDate = new RegExp( + /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source + + /T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source +); export default class Controller extends Section { constructor($element, $) { @@ -28,6 +31,20 @@ export default class Controller extends Section { select: 'visibility' }; this.filter = { + fields: [ + 'id', + 'originFk', + 'userFk', + 'action', + 'changedModel', + 'oldInstance', + 'newInstance', + 'creationDate', + 'changedModel', + 'changedModelId', + 'changedModelValue', + 'description' + ], include: [{ relation: 'user', scope: { @@ -48,6 +65,12 @@ export default class Controller extends Section { this.today.setHours(0, 0, 0, 0); } + $onInit() { + const match = this.url?.match(/(.*)Logs$/); + this.model = match && match[1]; + this.modelI18n = this.translateModel(this.model); + } + $postLink() { this.resetFilter(); this.$.$watch( @@ -63,47 +86,80 @@ export default class Controller extends Section { set logs(value) { this._logs = value; + this.logTree = []; if (!value) return; const empty = {}; const validations = window.validations; - const castJsonValue = this.castJsonValue; - for (const log of value) { + let originLog; + let userLog; + let modelLog; + let nLogs; + + for (let i = 0; i < value.length; i++) { + const log = value[i]; + const prevLog = i > 0 ? value[i - 1] : null; + const locale = validations[log.changedModel]?.locale || empty; + + // Origin + + const originChanged = !prevLog + || log.originFk != prevLog.originFk; + if (originChanged) { + this.logTree.push(originLog = { + originFk: log.originFk, + logs: [] + }); + } + + // User + + const day = 86400 * 1000; + const time = new Date(log.creationDate).getTime(); + const prevTime = prevLog && new Date(prevLog.creationDate).getTime(); + const userChanged = !prevLog + || log.userFk != prevLog.userFk + || time < prevTime - day * 2 + || nLogs >= 10; + + if (userChanged) { + originLog.logs.push(userLog = { + user: log.user, + userFk: log.userFk, + logs: [] + }); + nLogs = 0; + } + nLogs++; + + // Model + + const modelChanged = userChanged + || log.changedModel != prevLog.changedModel + || log.changedModelId != prevLog.changedModelId; + if (modelChanged) { + userLog.logs.push(modelLog = { + model: log.changedModel, + modelI18n: firstUpper(locale.name) || log.changedModel, + id: log.changedModelId, + showValue: log.changedModelValue, + logs: [] + }); + } + + modelLog.logs.push(log); + + // Changes + const notDelete = log.action != 'delete'; const olds = (notDelete ? log.oldInstance : null) || empty; const vals = (notDelete ? log.newInstance : log.oldInstance) || empty; - const locale = validations[log.changedModel]?.locale || empty; - log.changedModelI18n = firstUpper(locale.name) || log.changedModel; - let props = Object.keys(olds).concat(Object.keys(vals)); - props = [...new Set(props)]; + let propNames = Object.keys(olds).concat(Object.keys(vals)); + propNames = [...new Set(propNames)]; - log.props = []; - for (const prop of props) { - if (prop.endsWith('$')) continue; - log.props.push({ - name: prop, - nameI18n: firstUpper(locale.columns?.[prop]) || prop, - old: getVal(olds, prop), - val: getVal(vals, prop) - }); - } - log.props.sort( - (a, b) => a.nameI18n.localeCompare(b.nameI18n)); - } - - function getVal(vals, prop) { - let val, id; - const showProp = `${prop}$`; - - if (vals[showProp] != null) { - val = vals[showProp]; - id = vals[prop]; - } else - val = vals[prop]; - - return {val: castJsonValue(val), id}; + log.props = this.parseProps(propNames, locale, vals, olds); } } @@ -114,17 +170,76 @@ export default class Controller extends Section { set models(value) { this._models = value; if (!value) return; - for (const model of value) { - const name = model.changedModel; - model.changedModelI18n = - firstUpper(window.validations[name]?.locale?.name) || name; - } + for (const model of value) + model.changedModelI18n = this.translateModel(model.changedModel); } get showModelName() { return !(this.changedModel && this.changedModelId); } + parseProps(propNames, locale, vals, olds) { + const castJsonValue = this.castJsonValue; + const props = []; + + for (const prop of propNames) { + if (prop.endsWith('$')) continue; + props.push({ + name: prop, + nameI18n: firstUpper(locale.columns?.[prop]) || prop, + old: getVal(olds, prop), + val: getVal(vals, prop) + }); + } + props.sort( + (a, b) => a.nameI18n.localeCompare(b.nameI18n)); + + function getVal(vals, prop) { + let val; let id; + const showProp = `${prop}$`; + + if (vals[showProp] != null) { + val = vals[showProp]; + id = vals[prop]; + } else + val = vals[prop]; + + return {val: castJsonValue(val), id}; + } + + return props; + } + + viewPitInstance(event, id, modelLog) { + if (this.instance?.canceler) + this.instance.canceler.resolve(); + + const canceler = this.$q.defer(); + this.instance = { + modelLog, + canceler + }; + const options = {timeout: canceler.promise}; + + this.$http.get(`${this.url}/${id}/pitInstance`, options) + .then(res => { + const instance = res.data; + const propNames = Object.keys(instance); + const locale = window.validations[modelLog.model]?.locale || {}; + this.instance.props = this.parseProps(propNames, locale, instance, {}); + }) + .finally(() => { + this.instance.canceler = null; + this.$.$applyAsync(() => this.$.instancePopover.relocate()); + }); + + this.$.instancePopover.show(event); + } + + translateModel(name) { + return firstUpper(window.validations[name]?.locale?.name) || name; + } + castJsonValue(value) { return typeof value === 'string' && validDate.test(value) ? new Date(value) @@ -160,12 +275,11 @@ export default class Controller extends Section { applyFilter() { const filter = this.$.filter; - function getParam(prop, value) { + const getParam = (prop, value) => { if (value == null || value == '') return null; switch (prop) { case 'search': - const or = []; - if (/^\s*[0-9]+\s*$/.test(value)) + if (/^\s*[0-9]+\s*$/.test(value) || this.byEntity) return {changedModelId: value.trim()}; else return {changedModelValue: {like: `%${value}%`}}; @@ -177,72 +291,86 @@ export default class Controller extends Section { ]}; case 'who': switch (value) { - case 'all': - return null; case 'user': return {userFk: {neq: null}}; case 'system': return {userFk: null}; + case 'all': + default: + return null; } - case 'actions': + case 'actions': { const inq = []; for (const action in value) { if (value[action]) inq.push(action); } return inq.length ? {action: {inq}} : null; + } case 'from': - if (filter.to) { + if (filter.to) return {creationDate: {gte: value}}; - } else { + else { const to = new Date(value); to.setHours(23, 59, 59, 999); return {creationDate: {between: [value, to]}}; } - case 'to': + case 'to': { const to = new Date(value); to.setHours(23, 59, 59, 999); return {creationDate: {lte: to}}; + } case 'userFk': return filter.who != 'system' ? {[prop]: value} : null; default: return {[prop]: value}; } - } + }; + this.hasFilter = false; const and = []; + + if (!filter.search || !filter.changedModel) + this.byEntity = false; + if (!this.byEntity) + and.push({originFk: this.originId}); + for (const prop in filter) { const param = getParam(prop, filter[prop]); - if (param) and.push(param); + if (param) { + and.push(param); + this.hasFilter = true; + } } const lbFilter = and.length ? {where: {and}} : null; return this.$.model.applyFilter(lbFilter); } - filterByEntity(log) { + filterByEntity(modelLog) { + this.byEntity = true; this.$.filter = { who: 'all', - search: log.changedModelId, - changedModel: log.changedModel + search: modelLog.id, + changedModel: modelLog.model }; } searchUser(search) { - if (/^[0-9]+$/.test(search)) { + if (/^[0-9]+$/.test(search)) return {id: search}; - } else { + else { return {or: [ {name: search}, {nickname: {like: `%${search}%`}} - ]} + ]}; } } - showWorkerDescriptor(event, log) { - if (log.user?.worker) - this.$.workerDescriptor.show(event.target, log.userFk); + showWorkerDescriptor(event, userLog) { + if (userLog.user?.worker) + this.$.workerDescriptor.show(event.target, userLog.userFk); } } diff --git a/front/salix/components/log/locale/es.yml b/front/salix/components/log/locale/es.yml index 90a42911b..09154f04e 100644 --- a/front/salix/components/log/locale/es.yml +++ b/front/salix/components/log/locale/es.yml @@ -24,4 +24,5 @@ Changes: Cambios today: hoy yesterday: ayer Show all record changes: Mostrar todos los cambios realizados en el registro +View record at this point in time: Ver el registro en este punto Quit filter: Quitar filtro diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index de294972d..8c39559fc 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -1,13 +1,49 @@ @import "util"; vn-log { - .change { + .origin-log { + & > .origin-info { + display: flex; + text-align: right; + justify-content: space-between; + align-items: center; + column-gap: 4px; + padding-left: 48px; + margin-bottom: 10px; + + & > .line { + flex-grow: 1; + background-color: $color-font-secondary; + height: 2px; + } + & > .origin-id { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 1.1rem; + color: $color-font-secondary; + } + } + } + .user-log { display: flex; - & > .left { + & > .timeline { position: relative; padding-right: 10px; + width: 38px; + min-width: 38px; + flex-grow: auto; + & > .arrow { + height: 8px; + width: 8px; + position: absolute; + transform: rotateY(0deg) rotate(45deg); + top: 14px; + right: -4px; + z-index: 1; + } & > vn-avatar { cursor: pointer; @@ -15,15 +51,6 @@ vn-log { background-color: $color-main !important; } } - & > .arrow { - height: 8px; - width: 8px; - position: absolute; - transform: rotateY(0deg) rotate(45deg); - top: 18px; - right: -4px; - z-index: 1; - } & > .line { position: absolute; background-color: $color-main; @@ -34,134 +61,173 @@ vn-log { bottom: -8px; } } - &:last-child > .left > .line { + &:last-child > .timeline > .line { display: none; } - .detail { - position: relative; + & > .user-changes { flex-grow: 1; - width: 100%; - border-radius: 2px; overflow: hidden; + } + } + .model-log { + & > .model-info { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin-top: 6px; - & > .header { - display: flex; - justify-content: space-between; - align-items: center; - overflow: hidden; + & > .model-name { + display: inline-block; + padding: 2px 5px; + color: $color-font-dark; + border-radius: 8px; + vertical-align: middle; + } + & > .model-value { + font-style: italic; + } + & > .model-id { + color: $color-font-secondary; + font-size: .9rem; + } + & > vn-icon[icon="filter_alt"] { + @extend %clickable-light; + vertical-align: middle; + font-size: 18px; + color: $color-font-secondary; + float: right; + display: none; - & > .action-model { - display: inline-flex; - overflow: hidden; - - & > .model-name { - display: inline-block; - padding: 2px 5px; - color: $color-font-dark; - border-radius: 8px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - & > .action-date { - white-space: nowrap; - - & > .action { - display: inline-flex; - align-items: center; - justify-content: center; - color: $color-font-bg; - vertical-align: middle; - border-radius: 50%; - width: 24px; - height: 24px; - font-size: 18px; - - &.notice { - background-color: $color-notice-medium - } - &.success { - background-color: $color-success-medium; - } - &.warning { - background-color: $color-main-medium; - } - &.alert { - background-color: lighten($color-alert, 5%); - } - } + @include mobile { + display: initial; } } - & > .model { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-height: 18px; + } + &:hover > .model-info > vn-icon[icon="filter_alt"] { + display: initial; + } + } + .changes-log { + position: relative; + max-width: 100%; + width: 100%; + border-radius: 2px; + overflow: hidden; - & > vn-icon { + & > .change-info { + display: flex; + justify-content: space-between; + align-items: center; + overflow: hidden; + + & > .date { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + & > div { + white-space: nowrap; + + & > vn-icon.pit { @extend %clickable-light; vertical-align: middle; - padding: 2px; - margin: -2px; font-size: 18px; color: $color-font-secondary; - float: right; display: none; @include mobile { - display: initial; + display: inline-block; } } - & > .model-value { - font-style: italic; - } - & > .model-id { - color: $color-font-secondary; - font-size: .9rem; - } - } - &:hover > .model > vn-icon { - display: initial; - } - } - } - .changes { - overflow: hidden; - background-color: rgba(255, 255, 255, .05); - color: $color-font-light; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-height: 34px; - box-sizing: border-box; + & > .action { + display: inline-flex; + align-items: center; + justify-content: center; + color: $color-font-bg; + vertical-align: middle; + border-radius: 50%; + width: 24px; + height: 24px; + font-size: 18px; - & > vn-icon { - @extend %clickable; - float: right; - position: relative; - transition-property: transform, background-color; - transition-duration: 150ms; - margin: -5px; - margin-left: 4px; - padding: 1px; - border-radius: 50%; + &.notice { + background-color: $color-notice-medium + } + &.success { + background-color: $color-success-medium; + } + &.warning { + background-color: $color-main-medium; + } + &.alert { + background-color: lighten($color-alert, 5%); + } + } + } + &:hover vn-icon.pit { + display: inline-block; + } } - &.expanded { - text-overflow: initial; - white-space: initial; + & > .change-detail { + overflow: hidden; + background-color: rgba(255, 255, 255, .05); + color: $color-font-light; + position: relative; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-height: 34px; + box-sizing: border-box; & > vn-icon { - transform: rotate(180deg); + @extend %clickable; + float: right; + position: relative; + transition-property: transform, background-color; + transition-duration: 150ms; + margin: -5px; + margin-left: 4px; + padding: 1px; + border-radius: 50%; + } + &.expanded { + text-overflow: initial; + white-space: initial; + + & > vn-icon { + transform: rotate(180deg); + } + } + & > .no-changes { + font-style: italic; } } - & > .no-changes { - font-style: italic; + } + .id-value { + font-size: .9rem; + color: $color-font-secondary; + } +} +.vn-log-instance { + display: block; + + & > .loading { + display: flex; + justify-content: center; + } + & > .instance { + min-width: 180px; + max-width: 400px; + + & > .header { + background-color: $color-main; + color: $color-font-dark; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + & > .change-detail { + color: $color-font-light; } } } -vn-log-value > .id-value { - font-size: .9rem; - color: $color-font-secondary; -} diff --git a/loopback/common/methods/log/pitInstance.js b/loopback/common/methods/log/pitInstance.js new file mode 100644 index 000000000..3cbe013b3 --- /dev/null +++ b/loopback/common/methods/log/pitInstance.js @@ -0,0 +1,56 @@ +module.exports = Self => { + Self.remoteMethod('pitInstance', { + description: 'Gets the status of instance at specific point in time', + accepts: [ + { + arg: 'id', + type: 'integer', + description: 'The log id', + required: true + } + ], + returns: { + type: [Self], + root: true + }, + http: { + path: `/:id/pitInstance`, + verb: 'GET' + } + }); + + Self.pitInstance = async function(id) { + const log = await Self.findById(id, { + fields: ['changedModel', 'changedModelId'] + }); + + const where = { + changedModel: log.changedModel, + changedModelId: log.changedModelId + }; + + const createdWhere = {action: 'insert'}; + const createdLog = await Self.findOne({ + fields: ['id'], + where: Object.assign(createdWhere, where) + }); + + const logsWhere = { + id: {between: [ + Math.min(id, createdLog.id), + Math.max(id, createdLog.id) + ]} + }; + const logs = await Self.find({ + fields: ['newInstance'], + where: Object.assign(logsWhere, where), + order: 'creationDate' + }); + + const instance = {}; + for (const log of logs) + Object.assign(instance, log.newInstance); + + return instance; + }; +}; diff --git a/loopback/common/models/log.js b/loopback/common/models/log.js index 0622431a6..b0d3cfc00 100644 --- a/loopback/common/models/log.js +++ b/loopback/common/models/log.js @@ -5,6 +5,7 @@ module.exports = function(Self) { Self.super_.setup.call(this); require('../methods/log/editors')(this); require('../methods/log/models')(this); + require('../methods/log/pitInstance')(this); } }); }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js deleted file mode 100644 index 4029bfbcf..000000000 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -const models = require('vn-loopback/server/server').models; -const fs = require('fs-extra'); - -describe('InvoiceOut download()', () => { - const userId = 9; - const invoiceId = 1; - const ctx = { - req: { - - accessToken: {userId: userId}, - headers: {origin: 'http://localhost:5000'}, - } - }; - - it('should return the downloaded file name', async() => { - spyOn(models.InvoiceContainer, 'container').and.returnValue({ - client: {root: '/path'} - }); - spyOn(fs, 'createReadStream').and.returnValue(new Promise(resolve => resolve('streamObject'))); - spyOn(fs, 'access').and.returnValue(true); - spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true))); - - const result = await models.InvoiceOut.download(ctx, invoiceId); - - expect(result[1]).toEqual('application/pdf'); - expect(result[2]).toMatch(/filename="\d{4}T1111111.pdf"/); - }); -}); From 98301f8e709158e8431d93e583b7bcb41ee6d641 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:06:38 +0200 Subject: [PATCH 26/39] refs #5900 Small fixes & code clean --- .../02-client/07_edit_web_access.spec.js | 4 +-- front/salix/components/log/index.html | 8 +++--- front/salix/components/log/index.js | 13 ++++----- front/salix/components/log/style.scss | 5 ++-- loopback/common/methods/log/pitInstance.js | 28 ++++++++++++++++--- 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/e2e/paths/02-client/07_edit_web_access.spec.js b/e2e/paths/02-client/07_edit_web_access.spec.js index 5386d12bd..3847ec7cd 100644 --- a/e2e/paths/02-client/07_edit_web_access.spec.js +++ b/e2e/paths/02-client/07_edit_web_access.spec.js @@ -5,8 +5,8 @@ const $ = { userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]', email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]', saveButton: 'vn-client-web-access button[type=submit]', - nameValue: 'vn-client-log .change:nth-child(1) .basic-json:nth-child(2) vn-json-value', - activeValue: 'vn-client-log .change:nth-child(2) .basic-json:nth-child(1) vn-json-value' + nameValue: 'vn-client-log .changes-log:nth-child(2) .basic-json:nth-child(2) vn-json-value', + activeValue: 'vn-client-log .changes-log:nth-child(3) .basic-json:nth-child(1) vn-json-value' }; describe('Client web access path', () => { diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index ac4e5cb6e..e0465b840 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -33,16 +33,16 @@ ng-src="/api/Images/user/160x160/{{::userLog.userFk}}/download?access_token={{::$ctrl.vnToken.token}}"> -
+
-
+
+ ng-click="$ctrl.filterByRecord(modelLog)"> diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index ecbeaefe7..822a31801 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -67,8 +67,7 @@ export default class Controller extends Section { $onInit() { const match = this.url?.match(/(.*)Logs$/); - this.model = match && match[1]; - this.modelI18n = this.translateModel(this.model); + this.modelI18n = this.translateModel(match && match[1]); } $postLink() { @@ -279,7 +278,7 @@ export default class Controller extends Section { if (value == null || value == '') return null; switch (prop) { case 'search': - if (/^\s*[0-9]+\s*$/.test(value) || this.byEntity) + if (/^\s*[0-9]+\s*$/.test(value) || this.byRecord) return {changedModelId: value.trim()}; else return {changedModelValue: {like: `%${value}%`}}; @@ -332,8 +331,8 @@ export default class Controller extends Section { const and = []; if (!filter.search || !filter.changedModel) - this.byEntity = false; - if (!this.byEntity) + this.byRecord = false; + if (!this.byRecord) and.push({originFk: this.originId}); for (const prop in filter) { @@ -348,8 +347,8 @@ export default class Controller extends Section { return this.$.model.applyFilter(lbFilter); } - filterByEntity(modelLog) { - this.byEntity = true; + filterByRecord(modelLog) { + this.byRecord = true; this.$.filter = { who: 'all', search: modelLog.id, diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 8c39559fc..f11fc2f25 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -74,7 +74,8 @@ vn-log { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - margin-top: 6px; + margin-top: 5px; + min-height: 22px; & > .model-name { display: inline-block; @@ -131,7 +132,7 @@ vn-log { & > vn-icon.pit { @extend %clickable-light; vertical-align: middle; - font-size: 18px; + font-size: 20px; color: $color-font-secondary; display: none; diff --git a/loopback/common/methods/log/pitInstance.js b/loopback/common/methods/log/pitInstance.js index 3cbe013b3..1c054c53e 100644 --- a/loopback/common/methods/log/pitInstance.js +++ b/loopback/common/methods/log/pitInstance.js @@ -1,3 +1,5 @@ +const NotFoundError = require('vn-loopback/util/not-found-error'); + module.exports = Self => { Self.remoteMethod('pitInstance', { description: 'Gets the status of instance at specific point in time', @@ -21,24 +23,40 @@ module.exports = Self => { Self.pitInstance = async function(id) { const log = await Self.findById(id, { - fields: ['changedModel', 'changedModelId'] + fields: [ + 'changedModel', + 'changedModelId', + 'creationDate' + ] }); + if (!log) + throw new NotFoundError(); const where = { changedModel: log.changedModel, changedModelId: log.changedModelId }; - const createdWhere = {action: 'insert'}; + const createdWhere = { + action: 'insert', + creationDate: {lte: log.creationDate} + }; const createdLog = await Self.findOne({ - fields: ['id'], - where: Object.assign(createdWhere, where) + fields: ['id', 'creationDate'], + where: Object.assign(createdWhere, where), + order: 'creationDate DESC' }); + if (!createdLog) + throw new NotFoundError('Cannot find creation log'); const logsWhere = { id: {between: [ Math.min(id, createdLog.id), Math.max(id, createdLog.id) + ]}, + creationDate: {between: [ + createdLog.creationDate, + log.creationDate ]} }; const logs = await Self.find({ @@ -46,6 +64,8 @@ module.exports = Self => { where: Object.assign(logsWhere, where), order: 'creationDate' }); + if (!logs.length) + throw new NotFoundError('No logs found for record'); const instance = {}; for (const log of logs) From 21988fba8c2939da1e0e2eb0d3e6497931654c59 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:11:01 +0200 Subject: [PATCH 27/39] refs #5900 Hide PIT button on insert actions --- front/salix/components/log/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index e0465b840..2a6bd9abe 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -65,6 +65,7 @@ class="pit vn-ml-xs" icon="preview" translate-attr="::{title: 'View record at this point in time'}" + ng-show="::log.action != 'insert'" ng-click="$ctrl.viewPitInstance($event, log.id, modelLog)"> Date: Sun, 25 Jun 2023 17:14:41 +0200 Subject: [PATCH 28/39] refs #5900 Optimization: remove unnecessary watcher --- front/salix/components/log/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 2a6bd9abe..892268a64 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -19,7 +19,7 @@
- {{$ctrl.modelI18n}} #{{::originLog.originFk}} + {{::$ctrl.modelI18n}} #{{::originLog.originFk}}
From 57277ceb2e2b406a8d4b0ec212c11b6dba886a42 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:17:04 +0200 Subject: [PATCH 29/39] refs #5900 Show user every 6 changes, model info margin fix --- front/salix/components/log/index.html | 2 +- front/salix/components/log/index.js | 2 +- front/salix/components/log/style.scss | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 892268a64..7c7c5b068 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -38,7 +38,7 @@
-
+
= 10; + || nLogs >= 6; if (userChanged) { originLog.logs.push(userLog = { diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index f11fc2f25..481a7fbb3 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -74,7 +74,6 @@ vn-log { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - margin-top: 5px; min-height: 22px; & > .model-name { From d31d1e5a133f5a978253692d1da651ebe70f6170 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:25:11 +0200 Subject: [PATCH 30/39] refs #5900 User avatar margin fix --- front/salix/components/log/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 481a7fbb3..53d13b7f9 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -46,6 +46,7 @@ vn-log { } & > vn-avatar { cursor: pointer; + margin-top: 4px; &.system { background-color: $color-main !important; From fd69d69ff0bb2a76cc0f02c9511916223305e57f Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:40:09 +0200 Subject: [PATCH 31/39] refs #5900 Style: margin adjustments --- front/core/components/avatar/style.scss | 4 ++-- front/salix/components/log/style.scss | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/front/core/components/avatar/style.scss b/front/core/components/avatar/style.scss index 272930821..b3a80db55 100644 --- a/front/core/components/avatar/style.scss +++ b/front/core/components/avatar/style.scss @@ -4,8 +4,8 @@ vn-avatar { display: block; border-radius: 50%; overflow: hidden; - height: 36px; - width: 36px; + height: 38px; + width: 38px; font-size: 22px; background-color: $color-main; position: relative; diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 53d13b7f9..050e3f9d9 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -40,13 +40,12 @@ vn-log { width: 8px; position: absolute; transform: rotateY(0deg) rotate(45deg); - top: 14px; + top: 15px; right: -4px; z-index: 1; } & > vn-avatar { cursor: pointer; - margin-top: 4px; &.system { background-color: $color-main !important; @@ -56,10 +55,10 @@ vn-log { position: absolute; background-color: $color-main; width: 2px; - left: 17px; + left: 18px; z-index: -1; top: 44px; - bottom: -8px; + bottom: -2px; } } &:last-child > .timeline > .line { @@ -115,6 +114,9 @@ vn-log { border-radius: 2px; overflow: hidden; + &:last-child { + margin-bottom: 0; + } & > .change-info { display: flex; justify-content: space-between; From 631c0e312139bdecf1ca44ba21e0ada85f0cb808 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 18:09:28 +0200 Subject: [PATCH 32/39] refs #5900 Log grouping fix --- front/salix/components/log/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index b144b6446..ad5a3b355 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -117,7 +117,7 @@ export default class Controller extends Section { const day = 86400 * 1000; const time = new Date(log.creationDate).getTime(); const prevTime = prevLog && new Date(prevLog.creationDate).getTime(); - const userChanged = !prevLog + const userChanged = originChanged || log.userFk != prevLog.userFk || time < prevTime - day * 2 || nLogs >= 6; From c15d9a4753e271fe54b528e4b470425464e99b0d Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 18:49:38 +0200 Subject: [PATCH 33/39] refs #5900 Log order fix --- front/salix/components/log/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 7c7c5b068..5c10aa205 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -3,7 +3,7 @@ url="{{$ctrl.url}}" filter="$ctrl.filter" data="$ctrl.logs" - order="creationDate DESC, id DESC" + order="creationDate DESC, originFk DESC, id DESC" limit="20"> Date: Sun, 25 Jun 2023 19:01:46 +0200 Subject: [PATCH 34/39] refs #5900 Removed code to split user changes based on date --- front/salix/components/log/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index ad5a3b355..b5dac196e 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -114,14 +114,9 @@ export default class Controller extends Section { // User - const day = 86400 * 1000; - const time = new Date(log.creationDate).getTime(); - const prevTime = prevLog && new Date(prevLog.creationDate).getTime(); const userChanged = originChanged || log.userFk != prevLog.userFk - || time < prevTime - day * 2 || nLogs >= 6; - if (userChanged) { originLog.logs.push(userLog = { user: log.user, From d69cbe0ad4c3dd5451c7fafb9fc82722ed1e054d Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 19:39:27 +0200 Subject: [PATCH 35/39] refs #5900 Origin separator style fix --- front/salix/components/log/index.html | 9 +++------ front/salix/components/log/style.scss | 27 +++++++-------------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 5c10aa205..68c828772 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -16,12 +16,9 @@ model="model" class="vn-w-sm vn-px-sm vn-pb-xl">
-
-
-
- {{::$ctrl.modelI18n}} #{{::originLog.originFk}} -
-
+
+ {{::$ctrl.modelI18n}} #{{::originLog.originFk}} +
.origin-info { + margin-top: 0; + } & > .origin-info { - display: flex; - text-align: right; - justify-content: space-between; - align-items: center; - column-gap: 4px; - padding-left: 48px; - margin-bottom: 10px; - - & > .line { - flex-grow: 1; - background-color: $color-font-secondary; - height: 2px; - } - & > .origin-id { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-size: 1.1rem; - color: $color-font-secondary; - } + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: $color-font-secondary; } } .user-log { From 544445c4ae6f6c976db61e09702d6f0c75440d23 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 19:52:14 +0200 Subject: [PATCH 36/39] refs #5800 Origin separator style more clear --- front/salix/components/log/index.html | 9 ++++++--- front/salix/components/log/style.scss | 21 +++++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 68c828772..084bf9834 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -16,9 +16,12 @@ model="model" class="vn-w-sm vn-px-sm vn-pb-xl">
-
- {{::$ctrl.modelI18n}} #{{::originLog.originFk}} -
+
+
+ {{::$ctrl.modelI18n}} #{{::originLog.originFk}} +
+
+
.origin-info { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - color: $color-font-secondary; + display: flex; + align-items: center; + margin-top: 28px; + gap: 6px; + + & > .origin-id { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: $color-font-secondary; + margin: 0; + } + & > .line { + flex-grow: 1; + background-color: $color-font-secondary; + height: 2px; + } } } .user-log { From c42b38c5ccdd296e8130db159d849891afdb05a1 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 26 Jun 2023 10:23:34 +0200 Subject: [PATCH 37/39] refs #5900 Code clean, refactor & accurated --- front/salix/components/log/index.html | 12 +++--- front/salix/components/log/index.js | 10 ++--- front/salix/components/log/style.scss | 1 + loopback/common/methods/log/pitInstance.js | 47 ++++++++++++++-------- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 084bf9834..16342783d 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -3,7 +3,7 @@ url="{{$ctrl.url}}" filter="$ctrl.filter" data="$ctrl.logs" - order="creationDate DESC, originFk DESC, id DESC" + order="creationDate DESC, id DESC" limit="20"> - -
-
+
{{$ctrl.instance.modelLog.modelI18n}} #{{$ctrl.instance.modelLog.id}} -
+
@@ -258,3 +255,6 @@
+ + diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index b5dac196e..3df367cae 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -67,7 +67,7 @@ export default class Controller extends Section { $onInit() { const match = this.url?.match(/(.*)Logs$/); - this.modelI18n = this.translateModel(match && match[1]); + this.modelI18n = match && this.translateModel(match[1]); } $postLink() { @@ -116,7 +116,7 @@ export default class Controller extends Section { const userChanged = originChanged || log.userFk != prevLog.userFk - || nLogs >= 6; + || nLogs >= 5; if (userChanged) { originLog.logs.push(userLog = { user: log.user, @@ -181,8 +181,8 @@ export default class Controller extends Section { props.push({ name: prop, nameI18n: firstUpper(locale.columns?.[prop]) || prop, - old: getVal(olds, prop), - val: getVal(vals, prop) + val: getVal(vals, prop), + old: olds && getVal(olds, prop) }); } props.sort( @@ -220,7 +220,7 @@ export default class Controller extends Section { const instance = res.data; const propNames = Object.keys(instance); const locale = window.validations[modelLog.model]?.locale || {}; - this.instance.props = this.parseProps(propNames, locale, instance, {}); + this.instance.props = this.parseProps(propNames, locale, instance); }) .finally(() => { this.instance.canceler = null; diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 3ecbbb609..d729d09a3 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -228,6 +228,7 @@ vn-log { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + margin: 0; } & > .change-detail { color: $color-font-light; diff --git a/loopback/common/methods/log/pitInstance.js b/loopback/common/methods/log/pitInstance.js index 1c054c53e..cb671cb7e 100644 --- a/loopback/common/methods/log/pitInstance.js +++ b/loopback/common/methods/log/pitInstance.js @@ -37,37 +37,52 @@ module.exports = Self => { changedModelId: log.changedModelId }; + // Fetch creation and all update logs for record up to requested log + const createdWhere = { action: 'insert', creationDate: {lte: log.creationDate} }; const createdLog = await Self.findOne({ - fields: ['id', 'creationDate'], + fields: ['id', 'creationDate', 'newInstance'], where: Object.assign(createdWhere, where), - order: 'creationDate DESC' + order: 'creationDate DESC, id DESC' }); - if (!createdLog) - throw new NotFoundError('Cannot find creation log'); - const logsWhere = { - id: {between: [ - Math.min(id, createdLog.id), - Math.max(id, createdLog.id) - ]}, - creationDate: {between: [ - createdLog.creationDate, - log.creationDate - ]} + const instance = {}; + + let logsWhere = { + action: 'update' }; + if (createdLog) { + Object.assign(instance, createdLog.newInstance); + Object.assign(logsWhere, { + creationDate: {between: [ + createdLog.creationDate, + log.creationDate + ]}, + id: {between: [ + Math.min(id, createdLog.id), + Math.max(id, createdLog.id) + ]} + }); + } else { + Object.assign(logsWhere, { + creationDate: {lte: log.creationDate}, + id: {lte: id} + }); + } + const logs = await Self.find({ fields: ['newInstance'], where: Object.assign(logsWhere, where), - order: 'creationDate' + order: 'creationDate, id' }); - if (!logs.length) + if (!logs.length && !createdLog) throw new NotFoundError('No logs found for record'); - const instance = {}; + // Merge all logs in order into one instance + for (const log of logs) Object.assign(instance, log.newInstance); From e980717d149466c5b2c87a7ff4b25f331e02e19a Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 26 Jun 2023 10:56:53 +0200 Subject: [PATCH 38/39] refs #5900, #5772 Changelog updated --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2ebcd62..47eeb47a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - (Entradas -> Correo) Al cambiar el tipo de cambio enviará un correo a las personas designadas +- (General -> Históricos) Botón para ver el estado del registro en cada punto +- (General -> Históricos) Al filtar por registro se muestra todo el histórial desde que fue creado ### Changed +- (General -> Históricos) Los registros se muestran agrupados por usuario y entidad +- (Facturas -> Facturación global) Optimizada, generación de PDFs y notificaciones en paralelo ### Fixed -- +- (General -> Históricos) Duplicidades eliminadas +- (Facturas -> Facturación global) Solucionados fallos que paran el proceso ## [2324.01] - 2023-06-15 From ef8e1c4169b1008098615c34ba2429623a0b8b0a Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 26 Jun 2023 10:57:12 +0200 Subject: [PATCH 39/39] refs #5531 refs #5753 fix travelConfig sql --- db/changes/232402/00-hotFix_travelConfig.sql | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 db/changes/232402/00-hotFix_travelConfig.sql diff --git a/db/changes/232402/00-hotFix_travelConfig.sql b/db/changes/232402/00-hotFix_travelConfig.sql new file mode 100644 index 000000000..65450a74d --- /dev/null +++ b/db/changes/232402/00-hotFix_travelConfig.sql @@ -0,0 +1,22 @@ +CREATE TABLE `vn`.`travelConfig` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `warehouseInFk` smallint(6) unsigned NOT NULL DEFAULT 8 COMMENT 'Warehouse de origen', + `warehouseOutFk` smallint(6) unsigned NOT NULL DEFAULT 60 COMMENT 'Warehouse destino', + `agencyFk` int(11) NOT NULL DEFAULT 1378 COMMENT 'Agencia por defecto', + `companyFk` int(10) unsigned NOT NULL DEFAULT 442 COMMENT 'Compañía por defecto', + PRIMARY KEY (`id`), + KEY `travelConfig_FK` (`warehouseInFk`), + KEY `travelConfig_FK_1` (`warehouseOutFk`), + KEY `travelConfig_FK_2` (`agencyFk`), + KEY `travelConfig_FK_3` (`companyFk`), + CONSTRAINT `travelConfig_FK` FOREIGN KEY (`warehouseInFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_1` FOREIGN KEY (`warehouseOutFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_2` FOREIGN KEY (`agencyFk`) REFERENCES `agencyMode` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_3` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Entry', 'addFromPackaging', 'WRITE', 'ALLOW', 'ROLE', 'production'), + ('Entry', 'addFromBuy', 'WRITE', 'ALLOW', 'ROLE', 'production'), + ('Supplier', 'getItemsPackaging', 'READ', 'ALLOW', 'ROLE', 'production');