From 8ea330723051865bf0150e3b2d9543580c005b41 Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 29 Nov 2021 08:30:36 +0100 Subject: [PATCH 1/4] feat(ticket_sale): allow multi-check for recalculatePrice --- e2e/helpers/selectors.js | 1 + e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js | 11 +++++++++++ modules/ticket/front/sale/index.html | 2 +- modules/ticket/front/sale/index.js | 11 +++++------ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index d8ebaa069..84f5cdd0f 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -556,6 +556,7 @@ export default { moreMenuReserve: 'vn-item[name="reserve"]', moreMenuUnmarkReseved: 'vn-item[name="unreserve"]', moreMenuUpdateDiscount: 'vn-item[name="discount"]', + moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]', moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input', transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text', transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable', diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js index 55bbdf9ff..dfda4dcfb 100644 --- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js @@ -195,6 +195,17 @@ describe('Ticket Edit sale path', () => { expect(result).toContain('22.50'); }); + it('should recalculate price of sales', async() => { + await page.waitToClick(selectors.ticketSales.firstSaleCheckbox); + await page.waitToClick(selectors.ticketSales.secondSaleCheckbox); + + await page.waitToClick(selectors.ticketSales.moreMenu); + await page.waitToClick(selectors.ticketSales.moreMenuRecalculatePrice); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); + it('should select the third sale and create a claim of it', async() => { await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.moreMenu); diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 996135c1e..f7a279d9a 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -464,7 +464,7 @@ + ng-if="$ctrl.isEditable"> Recalculate price { - this.vnApp.showSuccess(this.$t('Data saved!')); - this.$.model.refresh(); + const sales = this.selectedValidSales(); + sales.forEach(sale => { + this.$http.post(`Sales/${sale.id}/recalculatePrice`); }); + this.vnApp.showSuccess(this.$t('Data saved!')); + this.$.model.refresh(); } itemSearchFunc($search) { -- 2.40.1 From d5c0be5a9d8b2a819dd0f5836138334b9e23a943 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 30 Nov 2021 13:47:16 +0100 Subject: [PATCH 2/4] refactor(ticket_sale): logic and tests --- .../00-sale_calculateComponent2.sql | 118 ++++++++++++++++++ .../back/methods/sale/recalculatePrice.js | 39 ++++-- .../sale/specs/recalculatePrice.spec.js | 16 +-- modules/ticket/front/sale/index.js | 11 +- modules/ticket/front/sale/index.spec.js | 8 +- 5 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 db/changes/10390-constitution/00-sale_calculateComponent2.sql diff --git a/db/changes/10390-constitution/00-sale_calculateComponent2.sql b/db/changes/10390-constitution/00-sale_calculateComponent2.sql new file mode 100644 index 000000000..72a6b9fc1 --- /dev/null +++ b/db/changes/10390-constitution/00-sale_calculateComponent2.sql @@ -0,0 +1,118 @@ +DROP PROCEDURE IF EXISTS `vn`.`sale_calculateComponentSalix`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_calculateComponentSalix`() +proc: BEGIN +/** + * Actualiza los componentes + * + * @table tmp.recalculateSales + */ + DECLARE vShipped DATE; + DECLARE vWarehouseFk SMALLINT; + DECLARE vAgencyModeFk INT; + DECLARE vAddressFk INT; + DECLARE vTicketFk BIGINT; + DECLARE vItemFk BIGINT; + DECLARE vLanded DATE; + DECLARE vIsEditable BOOLEAN; + DECLARE vZoneFk INTEGER; + DECLARE vOption INTEGER; + + DECLARE vSale INTEGER; + DECLARE vDone BOOL DEFAULT FALSE; + + DECLARE vCur CURSOR FOR + SELECT id from tmp.recalculateSales; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + OPEN vCur; + + l: LOOP + SET vDone = FALSE; + FETCH vCur INTO vSale; + + IF vDone THEN + LEAVE l; + END IF; + + SELECT t.refFk IS NULL AND (IFNULL(ts.alertLevel, 0) = 0 OR s.price = 0), + s.ticketFk, + s.itemFk , + t.zoneFk, + t.warehouseFk, + t.shipped, + t.addressFk, + t.agencyModeFk, + t.landed + INTO vIsEditable, + vTicketFk, + vItemFk, + vZoneFk, + vWarehouseFk, + vShipped, + vAddressFk, + vAgencyModeFk, + vLanded + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + LEFT JOIN ticketState ts ON ts.ticketFk = t.id + WHERE s.id = vSale; + + CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk, TRUE); + + IF (SELECT COUNT(*) FROM tmp.zoneGetLanded LIMIT 1) = 0 THEN + CALL util.throw('There is no zone for these parameters'); + END IF; + + IF vLanded IS NULL OR vZoneFk IS NULL THEN + + UPDATE ticket t + SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1) + WHERE t.id = vTicketFk AND t.landed IS NULL; + + IF vZoneFk IS NULL THEN + SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1; + UPDATE ticket t + SET t.zoneFk = vZoneFk + WHERE t.id = vTicketFk AND t.zoneFk IS NULL; + END IF; + + END IF; + + DROP TEMPORARY TABLE tmp.zoneGetLanded; + + -- rellena la tabla buyUltimate con la ultima compra + CALL buyUltimate (vWarehouseFk, vShipped); + + DELETE FROM tmp.buyUltimate WHERE itemFk != vItemFk; + + DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot; + CREATE TEMPORARY TABLE tmp.ticketLot + SELECT vWarehouseFk warehouseFk, NULL available, vItemFk itemFk, buyFk, vZoneFk zoneFk + FROM tmp.buyUltimate + WHERE itemFk = vItemFk; + + CALL catalog_componentPrepare(); + CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped, vWarehouseFk); + + DROP TEMPORARY TABLE IF EXISTS tmp.sale; + CREATE TEMPORARY TABLE tmp.sale + (PRIMARY KEY (saleFk)) ENGINE = MEMORY + SELECT vSale saleFk,vWarehouseFk warehouseFk; + + SET vOption = IF(vIsEditable, 1, 6); + + CALL ticketComponentUpdateSale(vOption); + CALL catalog_componentPurge(); + + DROP TEMPORARY TABLE tmp.buyUltimate; + DROP TEMPORARY TABLE tmp.sale; + + END LOOP; + CLOSE vCur; + +END$$ +DELIMITER ; diff --git a/modules/ticket/back/methods/sale/recalculatePrice.js b/modules/ticket/back/methods/sale/recalculatePrice.js index 3ffc23c3b..7b8a0e157 100644 --- a/modules/ticket/back/methods/sale/recalculatePrice.js +++ b/modules/ticket/back/methods/sale/recalculatePrice.js @@ -1,26 +1,26 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('recalculatePrice', { - description: 'Calculates the price of a sale and its components', + description: 'Calculates the price of sales and its components', accessType: 'WRITE', accepts: [{ - arg: 'id', - description: 'The sale id', - type: 'number', + arg: 'sales', + description: 'The sales', + type: ['object'], required: true, - http: {source: 'path'} + http: {source: 'body'} }], returns: { type: 'Number', root: true }, http: { - path: `/:id/recalculatePrice`, + path: `/recalculatePrice`, verb: 'post' } }); - Self.recalculatePrice = async(ctx, id, options) => { + Self.recalculatePrice = async(ctx, sales, options) => { const models = Self.app.models; const myOptions = {}; let tx; @@ -34,18 +34,33 @@ module.exports = Self => { } try { - const sale = await Self.findById(id, null, myOptions); - const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions); + const salesIds = []; + const params = []; + sales.forEach(sale => { + salesIds.push(sale.id); + params.push('?'); + }); + const isEditable = await models.Ticket.isEditable(ctx, sales[0].ticketFk, myOptions); if (!isEditable) throw new UserError(`The sales of this ticket can't be modified`); - const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions); - + const canEditSale = await models.Sale.canEdit(ctx, sales, myOptions); if (!canEditSale) throw new UserError(`Sale(s) blocked, please contact production`); - const recalculation = await Self.rawSql('CALL vn.sale_calculateComponent(?, null)', [id], myOptions); + const paramsString = params.join(); + + const query = ` + DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales; + CREATE TEMPORARY TABLE tmp.recalculateSales + SELECT s.id + FROM sale s + WHERE s.id IN (${paramsString}); + CALL vn.sale_calculateComponentSalix(); + DROP TEMPORARY TABLE tmp.recalculateSales;`; + + const recalculation = await Self.rawSql(query, salesIds, myOptions); if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/sale/specs/recalculatePrice.spec.js b/modules/ticket/back/methods/sale/specs/recalculatePrice.spec.js index 4f6579d32..0fc68dee7 100644 --- a/modules/ticket/back/methods/sale/specs/recalculatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/recalculatePrice.spec.js @@ -1,18 +1,20 @@ const models = require('vn-loopback/server/server').models; describe('sale recalculatePrice()', () => { - const saleId = 7; - it('should update the sale price', async() => { const tx = await models.Sale.beginTransaction({}); - + const sales = [ + {id: 7, ticketFk: 11}, + {id: 8, ticketFk: 11} + ]; try { const options = {transaction: tx}; const ctx = {req: {accessToken: {userId: 9}}}; - const response = await models.Sale.recalculatePrice(ctx, saleId, options); + const response = await models.Sale.recalculatePrice(ctx, sales, options); - expect(response.affectedRows).toBeDefined(); + expect(response[0].affectedRows).toBeDefined(); + expect(response[1].affectedRows).toBeDefined(); await tx.rollback(); } catch (e) { @@ -29,8 +31,8 @@ describe('sale recalculatePrice()', () => { const options = {transaction: tx}; const ctx = {req: {accessToken: {userId: 9}}}; - const immutableSaleId = 1; - await models.Sale.recalculatePrice(ctx, immutableSaleId, options); + const immutableSale = [{id: 1, ticketFk: 1}]; + await models.Sale.recalculatePrice(ctx, immutableSale, options); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index d9d7221bd..bbf33417c 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -451,11 +451,14 @@ class Controller extends Section { calculateSalePrice() { const sales = this.selectedValidSales(); - sales.forEach(sale => { - this.$http.post(`Sales/${sale.id}/recalculatePrice`); + if (!sales) return; + + const params = sales; + const query = `Sales/recalculatePrice`; + this.$http.post(query, params).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + this.$.model.refresh(); }); - this.vnApp.showSuccess(this.$t('Data saved!')); - this.$.model.refresh(); } itemSearchFunc($search) { diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index d543c1b81..f04d8ea54 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -688,10 +688,12 @@ describe('Ticket', () => { jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.$.model, 'refresh').mockReturnThis(); - const selectedSale = controller.sales[0]; - selectedSale.checked = true; + const selectedSaleOne = controller.sales[0]; + const selectedSaleTwo = controller.sales[1]; + selectedSaleOne.checked = true; + selectedSaleTwo.checked = true; - $httpBackend.expect('POST', `Sales/1/recalculatePrice`).respond(200); + $httpBackend.expect('POST', `Sales/recalculatePrice`).respond(200); controller.calculateSalePrice(); $httpBackend.flush(); -- 2.40.1 From decd4b5a9521390a240f8f00272a5ae75b3bd0d1 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 30 Nov 2021 15:13:20 +0100 Subject: [PATCH 3/4] separate sql procedures --- ...onent2.sql => 00-sale_recalcComponent.sql} | 8 ++++--- .../01-sale_calculateComponent.sql | 23 +++++++++++++++++++ .../back/methods/sale/recalculatePrice.js | 2 +- 3 files changed, 29 insertions(+), 4 deletions(-) rename db/changes/10390-constitution/{00-sale_calculateComponent2.sql => 00-sale_recalcComponent.sql} (93%) create mode 100644 db/changes/10390-constitution/01-sale_calculateComponent.sql diff --git a/db/changes/10390-constitution/00-sale_calculateComponent2.sql b/db/changes/10390-constitution/00-sale_recalcComponent.sql similarity index 93% rename from db/changes/10390-constitution/00-sale_calculateComponent2.sql rename to db/changes/10390-constitution/00-sale_recalcComponent.sql index 72a6b9fc1..c28115470 100644 --- a/db/changes/10390-constitution/00-sale_calculateComponent2.sql +++ b/db/changes/10390-constitution/00-sale_recalcComponent.sql @@ -1,8 +1,8 @@ -DROP PROCEDURE IF EXISTS `vn`.`sale_calculateComponentSalix`; +DROP PROCEDURE IF EXISTS `vn`.`sale_recalcComponent`; DELIMITER $$ $$ -CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_calculateComponentSalix`() +CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_recalcComponent`(vOption INT) proc: BEGIN /** * Actualiza los componentes @@ -103,7 +103,9 @@ proc: BEGIN (PRIMARY KEY (saleFk)) ENGINE = MEMORY SELECT vSale saleFk,vWarehouseFk warehouseFk; - SET vOption = IF(vIsEditable, 1, 6); + IF vOption IS NULL THEN + SET vOption = IF(vIsEditable, 1, 6); + END IF; CALL ticketComponentUpdateSale(vOption); CALL catalog_componentPurge(); diff --git a/db/changes/10390-constitution/01-sale_calculateComponent.sql b/db/changes/10390-constitution/01-sale_calculateComponent.sql new file mode 100644 index 000000000..97a154362 --- /dev/null +++ b/db/changes/10390-constitution/01-sale_calculateComponent.sql @@ -0,0 +1,23 @@ +DROP PROCEDURE IF EXISTS `vn`.`sale_calculateComponent`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_calculateComponent`(vSale INT, vOption INT) +proc: BEGIN +/** + * Crea tabla temporal para vn.sale_recalcComponent() para recalcular los componentes + * + * @param vSale Id de la venta + * @param vOption indica en que componente pone el descuadre, NULL en casos habituales + */ + DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales; + CREATE TEMPORARY TABLE tmp.recalculateSales + SELECT s.id + FROM sale s + WHERE s.id = vSale; + + CALL vn.sale_recalcComponent(vOption); + + DROP TEMPORARY TABLE tmp.recalculateSales; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/modules/ticket/back/methods/sale/recalculatePrice.js b/modules/ticket/back/methods/sale/recalculatePrice.js index 7b8a0e157..7a92ba08b 100644 --- a/modules/ticket/back/methods/sale/recalculatePrice.js +++ b/modules/ticket/back/methods/sale/recalculatePrice.js @@ -57,7 +57,7 @@ module.exports = Self => { SELECT s.id FROM sale s WHERE s.id IN (${paramsString}); - CALL vn.sale_calculateComponentSalix(); + CALL vn.sale_recalcComponent(null); DROP TEMPORARY TABLE tmp.recalculateSales;`; const recalculation = await Self.rawSql(query, salesIds, myOptions); -- 2.40.1 From 75e32feb16713b68380563203ae343d8bfa32e4a Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 1 Dec 2021 11:39:48 +0100 Subject: [PATCH 4/4] refactor ticket_sale --- modules/ticket/back/methods/sale/recalculatePrice.js | 2 +- modules/ticket/front/sale/index.js | 3 +-- modules/ticket/front/sale/index.spec.js | 9 ++++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/ticket/back/methods/sale/recalculatePrice.js b/modules/ticket/back/methods/sale/recalculatePrice.js index 7a92ba08b..8a390223d 100644 --- a/modules/ticket/back/methods/sale/recalculatePrice.js +++ b/modules/ticket/back/methods/sale/recalculatePrice.js @@ -11,7 +11,7 @@ module.exports = Self => { http: {source: 'body'} }], returns: { - type: 'Number', + type: 'number', root: true }, http: { diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index bbf33417c..752ed23ba 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -453,9 +453,8 @@ class Controller extends Section { const sales = this.selectedValidSales(); if (!sales) return; - const params = sales; const query = `Sales/recalculatePrice`; - this.$http.post(query, params).then(() => { + this.$http.post(query, sales).then(() => { this.vnApp.showSuccess(this.$t('Data saved!')); this.$.model.refresh(); }); diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index f04d8ea54..673e9a3f9 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -684,14 +684,13 @@ describe('Ticket', () => { }); describe('calculateSalePrice()', () => { - it('should make an HTTP post query ', () => { + it('should make an HTTP post query', () => { jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.$.model, 'refresh').mockReturnThis(); - const selectedSaleOne = controller.sales[0]; - const selectedSaleTwo = controller.sales[1]; - selectedSaleOne.checked = true; - selectedSaleTwo.checked = true; + controller.sales.forEach(sale => { + sale.checked = true; + }); $httpBackend.expect('POST', `Sales/recalculatePrice`).respond(200); controller.calculateSalePrice(); -- 2.40.1