diff --git a/db/changes/10390-constitution/00-sale_recalcComponent.sql b/db/changes/10390-constitution/00-sale_recalcComponent.sql new file mode 100644 index 000000000..c28115470 --- /dev/null +++ b/db/changes/10390-constitution/00-sale_recalcComponent.sql @@ -0,0 +1,120 @@ +DROP PROCEDURE IF EXISTS `vn`.`sale_recalcComponent`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_recalcComponent`(vOption INT) +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; + + IF vOption IS NULL THEN + SET vOption = IF(vIsEditable, 1, 6); + END IF; + + 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/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/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index b95ecbd7f..24b87b398 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -557,6 +557,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/back/methods/sale/recalculatePrice.js b/modules/ticket/back/methods/sale/recalculatePrice.js index 3ffc23c3b..8a390223d 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', + 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_recalcComponent(null); + 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.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 { + const sales = this.selectedValidSales(); + if (!sales) return; + + const query = `Sales/recalculatePrice`; + 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 d543c1b81..673e9a3f9 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -684,14 +684,15 @@ 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 selectedSale = controller.sales[0]; - selectedSale.checked = true; + controller.sales.forEach(sale => { + sale.checked = true; + }); - $httpBackend.expect('POST', `Sales/1/recalculatePrice`).respond(200); + $httpBackend.expect('POST', `Sales/recalculatePrice`).respond(200); controller.calculateSalePrice(); $httpBackend.flush();