From d6ef5352a2bf26035ee0229163a3266a041aef32 Mon Sep 17 00:00:00 2001 From: carlosjr Date: Wed, 26 May 2021 09:14:18 +0200 Subject: [PATCH 1/2] sale module now has canEdit endpoint to check arrays of sales --- loopback/locale/es.json | 1 + modules/ticket/back/methods/sale/canEdit.js | 40 +++++++++++++++++++ .../ticket/back/methods/sale/deleteSales.js | 6 +++ .../back/methods/sale/recalculatePrice.js | 5 +++ modules/ticket/back/methods/sale/reserve.js | 5 +++ .../back/methods/sale/specs/canEdit.spec.js | 36 +++++++++++++++++ .../methods/sale/specs/updateConcept.spec.js | 5 ++- .../methods/sale/specs/updateQuantity.spec.js | 8 ++-- .../ticket/back/methods/sale/updateConcept.js | 12 ++++-- .../ticket/back/methods/sale/updatePrice.js | 5 +++ .../back/methods/sale/updateQuantity.js | 13 ++++-- modules/ticket/back/models/sale.js | 1 + 12 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 modules/ticket/back/methods/sale/canEdit.js create mode 100644 modules/ticket/back/methods/sale/specs/canEdit.spec.js diff --git a/loopback/locale/es.json b/loopback/locale/es.json index fdc1bd977..89e301fca 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -86,6 +86,7 @@ "The current ticket can't be modified": "El ticket actual no puede ser modificado", "The current claim can't be modified": "La reclamación actual no puede ser modificada", "The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas", + "Sale(s) blocked, contact production": "Linea(s) bloqueada(s), contacte con produccion", "Please select at least one sale": "Por favor selecciona al menos una linea", "All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket", "NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", diff --git a/modules/ticket/back/methods/sale/canEdit.js b/modules/ticket/back/methods/sale/canEdit.js new file mode 100644 index 000000000..4e0fc5f8b --- /dev/null +++ b/modules/ticket/back/methods/sale/canEdit.js @@ -0,0 +1,40 @@ +module.exports = Self => { + Self.remoteMethodCtx('canEdit', { + description: 'Check if all the received sales are aditable', + accessType: 'READ', + accepts: [{ + arg: 'sales', + type: ['object'], + required: true + }], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/isEditable`, + verb: 'get' + } + }); + + Self.canEdit = async(ctx, sales, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const idsCollection = sales.map(sale => sale.id); + + const saleTracking = await models.SaleTracking.find({where: {saleFk: {inq: idsCollection}}}, myOptions); + + const hasSaleTracking = saleTracking.length; + + const isProductionRole = await models.Account.hasRole(userId, 'production', myOptions); + + const canEdit = (isProductionRole || !hasSaleTracking); + + return canEdit; + }; +}; diff --git a/modules/ticket/back/methods/sale/deleteSales.js b/modules/ticket/back/methods/sale/deleteSales.js index d11223d3b..31899a501 100644 --- a/modules/ticket/back/methods/sale/deleteSales.js +++ b/modules/ticket/back/methods/sale/deleteSales.js @@ -28,10 +28,16 @@ module.exports = Self => { Self.deleteSales = async(ctx, sales, ticketId) => { const models = Self.app.models; + + const canEditSales = await models.Sale.canEdit(ctx, sales); + const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId); if (!isTicketEditable) throw new UserError(`The sales of this ticket can't be modified`); + if (!canEditSales) + throw new UserError(`Sale(s) blocked, please contact production`); + const promises = []; for (let sale of sales) { const deletedSale = models.Sale.destroyById(sale.id); diff --git a/modules/ticket/back/methods/sale/recalculatePrice.js b/modules/ticket/back/methods/sale/recalculatePrice.js index 9ad7e68e7..9e902d393 100644 --- a/modules/ticket/back/methods/sale/recalculatePrice.js +++ b/modules/ticket/back/methods/sale/recalculatePrice.js @@ -29,6 +29,11 @@ module.exports = Self => { if (!isEditable) throw new UserError(`The sales of this ticket can't be modified`); + const canEditSale = await models.Sale.canEdit(ctx, [id]); + + if (!canEditSale) + throw new UserError(`Sale(s) blocked, please contact production`); + return Self.rawSql('CALL vn.sale_calculateComponent(?, null)', [id]); }; }; diff --git a/modules/ticket/back/methods/sale/reserve.js b/modules/ticket/back/methods/sale/reserve.js index e054e1ec8..96c794e7c 100644 --- a/modules/ticket/back/methods/sale/reserve.js +++ b/modules/ticket/back/methods/sale/reserve.js @@ -37,6 +37,11 @@ module.exports = Self => { if (!isTicketEditable) throw new UserError(`The sales of this ticket can't be modified`); + const canEditSale = await models.Sale.canEdit(ctx, sales); + + if (!canEditSale) + throw new UserError(`Sale(s) blocked, please contact production`); + const promises = []; for (let sale of sales) { const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved}); diff --git a/modules/ticket/back/methods/sale/specs/canEdit.spec.js b/modules/ticket/back/methods/sale/specs/canEdit.spec.js new file mode 100644 index 000000000..fb1e1ab87 --- /dev/null +++ b/modules/ticket/back/methods/sale/specs/canEdit.spec.js @@ -0,0 +1,36 @@ +const app = require('vn-loopback/server/server'); + +describe('sale canEdit()', () => { + it('should return true if the role is production regardless of the saleTrackings', async() => { + const productionUserID = 49; + let ctx = {req: {accessToken: {userId: productionUserID}}}; + + const sales = [{id: 3}]; + + const result = await app.models.Sale.canEdit(ctx, sales); + + expect(result).toEqual(true); + }); + + it('should return true if the role is not production and none of the sales has saleTracking', async() => { + const salesPersonUserID = 18; + let ctx = {req: {accessToken: {userId: salesPersonUserID}}}; + + const sales = [{id: 10}]; + + const result = await app.models.Sale.canEdit(ctx, sales); + + expect(result).toEqual(true); + }); + + it('should return false if any of the sales has a saleTracking record', async() => { + const salesPersonUserID = 18; + let ctx = {req: {accessToken: {userId: salesPersonUserID}}}; + + const sales = [{id: 3}]; + + const result = await app.models.Sale.canEdit(ctx, sales); + + expect(result).toEqual(false); + }); +}); diff --git a/modules/ticket/back/methods/sale/specs/updateConcept.spec.js b/modules/ticket/back/methods/sale/specs/updateConcept.spec.js index d383cc948..428bac390 100644 --- a/modules/ticket/back/methods/sale/specs/updateConcept.spec.js +++ b/modules/ticket/back/methods/sale/specs/updateConcept.spec.js @@ -1,6 +1,7 @@ const app = require('vn-loopback/server/server'); describe('sale updateConcept()', () => { + const ctx = {req: {accessToken: {userId: 9}}}; const saleId = 1; let originalSale; @@ -21,7 +22,7 @@ describe('sale updateConcept()', () => { const newConcept = 'I am he new concept'; try { - await app.models.Sale.updateConcept(undefined, newConcept); + await app.models.Sale.updateConcept(ctx, undefined, newConcept); } catch (e) { err = e; } @@ -32,7 +33,7 @@ describe('sale updateConcept()', () => { it('should update the sale concept', async() => { const newConcept = 'I am the new concept'; - let response = await app.models.Sale.updateConcept(saleId, newConcept); + let response = await app.models.Sale.updateConcept(ctx, saleId, newConcept); expect(response.concept).toEqual(newConcept); }); diff --git a/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js b/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js index ccb73d779..16221b55c 100644 --- a/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js +++ b/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js @@ -1,10 +1,12 @@ const app = require('vn-loopback/server/server'); describe('sale updateQuantity()', () => { + const ctx = {req: {accessToken: {userId: 9}}}; + it('should throw an error if the quantity is not a number', async() => { let error; - await app.models.Sale.updateQuantity(1, 'wrong quantity!') + await app.models.Sale.updateQuantity(ctx, 1, 'wrong quantity!') .catch(response => { expect(response).toEqual(new Error('The value should be a number')); error = response; @@ -16,7 +18,7 @@ describe('sale updateQuantity()', () => { it('should throw an error if the quantity is greater than it should be', async() => { let error; - await app.models.Sale.updateQuantity(1, 99) + await app.models.Sale.updateQuantity(ctx, 1, 99) .catch(response => { expect(response).toEqual(new Error('The new quantity should be smaller than the old one')); error = response; @@ -30,7 +32,7 @@ describe('sale updateQuantity()', () => { expect(originalLineData.quantity).toEqual(5); - await app.models.Sale.updateQuantity(1, 4); + await app.models.Sale.updateQuantity(ctx, 1, 4); let modifiedLineData = await app.models.Sale.findOne({where: {id: 1}, fields: ['quantity']}); diff --git a/modules/ticket/back/methods/sale/updateConcept.js b/modules/ticket/back/methods/sale/updateConcept.js index d95cf1202..6a5f3375f 100644 --- a/modules/ticket/back/methods/sale/updateConcept.js +++ b/modules/ticket/back/methods/sale/updateConcept.js @@ -1,5 +1,5 @@ module.exports = Self => { - Self.remoteMethod('updateConcept', { + Self.remoteMethodCtx('updateConcept', { description: 'Updates the concept of a sale', accessType: 'WRITE', accepts: [{ @@ -24,8 +24,14 @@ module.exports = Self => { } }); - Self.updateConcept = async(id, newConcept) => { - let currentLine = await Self.app.models.Sale.findById(id); + Self.updateConcept = async(ctx, id, newConcept) => { + const models = Self.app.models; + const currentLine = await models.Sale.findById(id); + + const canEditSale = await models.Sale.canEdit(ctx, [id]); + + if (!canEditSale) + throw new UserError(`Sale(s) blocked, please contact production`); return await currentLine.updateAttributes({concept: newConcept}); }; diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index 200eeb444..2195c2b7b 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -55,6 +55,11 @@ module.exports = Self => { if (!isEditable) throw new UserError(`The sales of this ticket can't be modified`); + const canEditSale = await models.Sale.canEdit(ctx, [id]); + + if (!canEditSale) + throw new UserError(`Sale(s) blocked, please contact production`); + const userId = ctx.req.accessToken.userId; let usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options); diff --git a/modules/ticket/back/methods/sale/updateQuantity.js b/modules/ticket/back/methods/sale/updateQuantity.js index c25aa9007..00df49b9f 100644 --- a/modules/ticket/back/methods/sale/updateQuantity.js +++ b/modules/ticket/back/methods/sale/updateQuantity.js @@ -1,7 +1,7 @@ let UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethod('updateQuantity', { + Self.remoteMethodCtx('updateQuantity', { description: 'Changes the quantity of a sale', accessType: 'WRITE', accepts: [{ @@ -26,11 +26,18 @@ module.exports = Self => { } }); - Self.updateQuantity = async(id, quantity) => { + Self.updateQuantity = async(ctx, id, quantity) => { + const models = Self.app.models; + + const canEditSale = await models.Sale.canEdit(ctx, [id]); + + if (!canEditSale) + throw new UserError(`Sale(s) blocked, please contact production`); + if (isNaN(quantity)) throw new UserError(`The value should be a number`); - let currentLine = await Self.app.models.Sale.findOne({where: {id: id}}); + let currentLine = await models.Sale.findOne({where: {id: id}}); if (quantity > currentLine.quantity) throw new UserError('The new quantity should be smaller than the old one'); diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index 4885071fd..545e054dc 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -6,6 +6,7 @@ module.exports = Self => { require('../methods/sale/updateQuantity')(Self); require('../methods/sale/updateConcept')(Self); require('../methods/sale/recalculatePrice')(Self); + require('../methods/sale/canEdit')(Self); Self.validatesPresenceOf('concept', { message: `Concept cannot be blank` From c533973398f76edd7e06f5739ee273de468a7d4a Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 27 May 2021 10:49:19 +0200 Subject: [PATCH 2/2] HOTFIX: Invoice regeneration error --- print/templates/reports/invoice/invoice.js | 14 +--- .../reports/invoice/sql/invoiceTickets.sql | 20 ----- print/templates/reports/invoice/sql/sales.sql | 73 +++++++++++-------- 3 files changed, 45 insertions(+), 62 deletions(-) delete mode 100644 print/templates/reports/invoice/sql/invoiceTickets.sql diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index 54df45c36..a6e37d591 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -3,7 +3,6 @@ const Report = require(`${appPath}/core/report`); const reportHeader = new Component('report-header'); const reportFooter = new Component('report-footer'); const invoiceIncoterms = new Report('invoice-incoterms'); -const db = require(`${appPath}/core/database`); module.exports = { name: 'invoice', @@ -72,17 +71,8 @@ module.exports = { fetchTickets(invoiceId) { return this.rawSqlFromDef('tickets', [invoiceId]); }, - async fetchSales(invoiceId) { - const connection = await db.pool.getConnection(); - await this.rawSql(`DROP TEMPORARY TABLE IF EXISTS tmp.invoiceTickets`, connection); - await this.rawSqlFromDef('invoiceTickets', [invoiceId], connection); - - const sales = this.rawSqlFromDef('sales', connection); - - await this.rawSql(`DROP TEMPORARY TABLE tmp.invoiceTickets`, connection); - await connection.release(); - - return sales; + fetchSales(invoiceId) { + return this.rawSqlFromDef('sales', [invoiceId, invoiceId]); }, fetchTaxes(invoiceId) { return this.rawSqlFromDef(`taxes`, [invoiceId]); diff --git a/print/templates/reports/invoice/sql/invoiceTickets.sql b/print/templates/reports/invoice/sql/invoiceTickets.sql deleted file mode 100644 index 089911a63..000000000 --- a/print/templates/reports/invoice/sql/invoiceTickets.sql +++ /dev/null @@ -1,20 +0,0 @@ -CREATE TEMPORARY TABLE tmp.invoiceTickets - ENGINE = MEMORY - SELECT - t.id AS ticketFk, - t.clientFk, - t.shipped, - t.nickname, - io.ref, - c.socialName, - sa.iban, - pm.name AS payMethod, - su.countryFk AS supplierCountryFk - FROM vn.invoiceOut io - JOIN vn.supplier su ON su.id = io.companyFk - JOIN vn.ticket t ON t.refFk = io.ref - JOIN vn.client c ON c.id = t.clientFk - JOIN vn.payMethod pm ON pm.id = c.payMethodFk - JOIN vn.company co ON co.id = io.companyFk - JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk - WHERE io.id = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/sales.sql b/print/templates/reports/invoice/sql/sales.sql index 0665fc0ff..cff8794db 100644 --- a/print/templates/reports/invoice/sql/sales.sql +++ b/print/templates/reports/invoice/sql/sales.sql @@ -1,11 +1,11 @@ SELECT - it.ref, - it.socialName, - it.iban, - it.payMethod, - it.clientFk, - it.shipped, - it.nickname, + io.ref, + c.socialName, + sa.iban, + pm.name AS payMethod, + t.clientFk, + t.shipped, + t.nickname, s.ticketFk, s.itemFk, s.concept, @@ -20,27 +20,34 @@ SELECT i.value7, tc.code AS vatType, ib.ediBotanic botanical - FROM tmp.invoiceTickets it - JOIN vn.sale s ON s.ticketFk = it.ticketFk - JOIN item i ON i.id = s.itemFk - LEFT JOIN itemType it ON it.id = i.typeFk - LEFT JOIN itemCategory ic ON ic.id = it.categoryFk - LEFT JOIN itemBotanicalWithGenus ib ON ib.itemFk = i.id - AND ic.code = 'plant' - AND ib.ediBotanic IS NOT NULL - JOIN vn.itemTaxCountry itc ON itc.countryFk = it.supplierCountryFk - AND itc.itemFk = s.itemFk - JOIN vn.taxClass tc ON tc.id = itc.taxClassFk -UNION ALL + FROM vn.invoiceOut io + JOIN vn.ticket t ON t.refFk = io.ref + JOIN vn.supplier su ON su.id = io.companyFk + JOIN vn.client c ON c.id = t.clientFk + JOIN vn.payMethod pm ON pm.id = c.payMethodFk + JOIN vn.company co ON co.id = io.companyFk + JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk + JOIN vn.sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + LEFT JOIN itemType it ON it.id = i.typeFk + LEFT JOIN itemCategory ic ON ic.id = it.categoryFk + LEFT JOIN itemBotanicalWithGenus ib ON ib.itemFk = i.id + AND ic.code = 'plant' + AND ib.ediBotanic IS NOT NULL + JOIN vn.itemTaxCountry itc ON itc.countryFk = su.countryFk + AND itc.itemFk = s.itemFk + JOIN vn.taxClass tc ON tc.id = itc.taxClassFk + WHERE io.id = ? + UNION ALL SELECT - it.ref, - it.socialName, - it.iban, - it.payMethod, - it.clientFk, - it.shipped, - it.nickname, - it.ticketFk, + io.ref, + c.socialName, + sa.iban, + pm.name AS payMethod, + t.clientFk, + t.shipped, + t.nickname, + t.id AS ticketFk, '', ts.description concept, ts.quantity, @@ -54,6 +61,12 @@ SELECT NULL AS value7, tc.code AS vatType, NULL AS botanical - FROM tmp.invoiceTickets it - JOIN vn.ticketService ts ON ts.ticketFk = it.ticketFk - JOIN vn.taxClass tc ON tc.id = ts.taxClassFk \ No newline at end of file + FROM vn.invoiceOut io + JOIN vn.ticket t ON t.refFk = io.ref + JOIN vn.ticketService ts ON ts.ticketFk = t.id + JOIN vn.client c ON c.id = t.clientFk + JOIN vn.payMethod pm ON pm.id = c.payMethodFk + JOIN vn.company co ON co.id = io.companyFk + JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk + JOIN vn.taxClass tc ON tc.id = ts.taxClassFk + WHERE io.id = ? \ No newline at end of file