diff --git a/db/changes/10110-postCampaign/00-ticket_recalcComponents.sql b/db/changes/10110-postCampaign/00-ticket_recalcComponents.sql new file mode 100644 index 000000000..885e2998a --- /dev/null +++ b/db/changes/10110-postCampaign/00-ticket_recalcComponents.sql @@ -0,0 +1,77 @@ +USE `vn`; +DROP procedure IF EXISTS `ticket_recalcComponents`; + +DELIMITER $$ +USE `vn`$$ +CREATE DEFINER=`root`@`%` PROCEDURE `ticket_recalcComponents`(IN vTicketFk BIGINT ) +proc: BEGIN + +/** + * Este procedimiento trata de "rebionizar" un ticket, + * eliminando los componentes existentes e insertandolos de nuevo + * + * @param vTicketFk Id del ticket + * @return tmp.buyUltimate + */ + DECLARE vShipped DATE; + DECLARE vWarehouseFk SMALLINT; + DECLARE vAgencyModeFk INT; + DECLARE vAddressFk INT; + DECLARE vLanded DATE; + DECLARE vIsTicketEditable BOOLEAN; + DECLARE vZoneFk INTEGER; + + SELECT (IFNULL(ts.alertLevel,0) >0 or IFNULL(t.refFk,"") != "") = FALSE, t.zoneFk + INTO vIsTicketEditable, vZoneFk + FROM ticket t LEFT JOIN ticketState ts ON t.id = ts.ticket + WHERE id = vTicketFk; + + SELECT warehouseFk, date(shipped), addressFk, agencyModeFk, landed + INTO vWarehouseFk, vShipped, vAddressFk, vAgencyModeFk, vLanded + FROM ticket + WHERE id = vTicketFk; + + CALL zoneGetShippedWarehouse(vLanded, vAddressFk , vAgencyModeFk); + + CALL vn.buyUltimate (vWarehouseFk, vShipped); -- rellena la tabla buyUltimate con la ultima compra + + DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot; + CREATE TEMPORARY TABLE tmp.ticketLot + SELECT vWarehouseFk warehouseFk, NULL available, + s.itemFk, bu.buyFk + FROM sale s + LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk + WHERE s.ticketFk = vTicketFk + GROUP BY s.itemFk; + + CALL vn.catalog_componentCalculate(vZoneFk, vAddressFk, vShipped); + + DROP TEMPORARY TABLE IF EXISTS tmp.sale; + CREATE TEMPORARY TABLE tmp.sale + (PRIMARY KEY (saleFk)) ENGINE = MEMORY + SELECT id saleFk, vWarehouseFk warehouseFk + FROM sale s + WHERE s.ticketFk = vTicketFk; + + CALL vn.ticketComponentUpdateSale(IF(vIsTicketEditable,1,6)); -- si el ticket esta facturado, respeta los precios + + IF vLanded IS NULL THEN + + CALL zoneGetLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk); + + UPDATE vn2008.Tickets t + SET t.landing = (SELECT landed FROM tmp.zoneGetLanded) + WHERE Id_Ticket = vTicketFk; + + DROP TEMPORARY TABLE tmp.zoneGetLanded; + + END IF; + + DROP TEMPORARY TABLE tmp.buyUltimate; + DROP TEMPORARY TABLE tmp.ticketComponentPrice; + DROP TEMPORARY TABLE tmp.ticketComponent; + DROP TEMPORARY TABLE tmp.sale; +END$$ + +DELIMITER ; + diff --git a/db/changes/10110-postCampaign/00-ticket_recalcComponentsForcePrice.sql b/db/changes/10110-postCampaign/00-ticket_recalcComponentsForcePrice.sql new file mode 100644 index 000000000..90d65fcc1 --- /dev/null +++ b/db/changes/10110-postCampaign/00-ticket_recalcComponentsForcePrice.sql @@ -0,0 +1,77 @@ +USE `vn`; +DROP procedure IF EXISTS `ticket_recalcComponentsForcePrice`; + +DELIMITER $$ +USE `vn`$$ +CREATE DEFINER=`root`@`%` PROCEDURE `ticket_recalcComponentsForcePrice`(IN vTicketFk BIGINT, vIsTicketEditable BOOLEAN ) +proc: BEGIN + +/** + * Este procedimiento trata de "rebionizar" un ticket, + * eliminando los componentes existentes e insertandolos de nuevo + * + * @param vTicketFk Id del ticket + * @return tmp.buyUltimate + */ + DECLARE vShipped DATE; + DECLARE vWarehouseFk SMALLINT; + DECLARE vAgencyModeFk INT; + DECLARE vAddressFk INT; + DECLARE vLanded DATE; + DECLARE vZoneFk INTEGER; + + IF vIsTicketEditable IS NULL THEN + SELECT (IFNULL(ts.alertLevel,0) >0 or IFNULL(t.refFk,"") != "") = FALSE, t.zoneFk + INTO vIsTicketEditable, vZoneFk + FROM ticket t LEFT JOIN ticketState ts ON t.id = ts.ticket + WHERE id = vTicketFk; + END IF; + SELECT warehouseFk, date(shipped), addressFk, agencyModeFk, landed + INTO vWarehouseFk, vShipped, vAddressFk, vAgencyModeFk, vLanded + FROM ticket + WHERE id = vTicketFk; + + CALL zoneGetShippedWarehouse(vLanded, vAddressFk , vAgencyModeFk); + + CALL vn.buyUltimate (vWarehouseFk, vShipped); -- rellena la tabla buyUltimate con la ultima compra + + DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot; + CREATE TEMPORARY TABLE tmp.ticketLot + SELECT vWarehouseFk warehouseFk, NULL available, + s.itemFk, bu.buyFk + FROM sale s + LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk + WHERE s.ticketFk = vTicketFk + GROUP BY s.itemFk; + + CALL vn.catalog_componentCalculate(vZoneFk, vAddressFk, vShipped); + + DROP TEMPORARY TABLE IF EXISTS tmp.sale; + CREATE TEMPORARY TABLE tmp.sale + (PRIMARY KEY (saleFk)) ENGINE = MEMORY + SELECT id saleFk, vWarehouseFk warehouseFk + FROM sale s + WHERE s.ticketFk = vTicketFk; + + CALL vn.ticketComponentUpdateSale(IF(vIsTicketEditable,1,6)); -- si el ticket esta facturado, respeta los precios + + IF vLanded IS NULL THEN + + CALL zoneGetLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk); + + UPDATE vn2008.Tickets t + SET t.landing = (SELECT landed FROM tmp.zoneGetLanded) + WHERE Id_Ticket = vTicketFk; + + DROP TEMPORARY TABLE tmp.zoneGetLanded; + + END IF; + + DROP TEMPORARY TABLE tmp.buyUltimate; + DROP TEMPORARY TABLE tmp.ticketComponentPrice; + DROP TEMPORARY TABLE tmp.ticketComponent; + DROP TEMPORARY TABLE tmp.sale; +END$$ + +DELIMITER ; + diff --git a/front/core/components/confirm/confirm.js b/front/core/components/confirm/confirm.js index 9d53df798..f187a3cb4 100644 --- a/front/core/components/confirm/confirm.js +++ b/front/core/components/confirm/confirm.js @@ -1,6 +1,7 @@ import ngModule from '../../module'; import Dialog from '../dialog'; import template from './confirm.html'; +import './style.scss'; export default class Confirm extends Dialog { constructor($element, $, $transclude) { diff --git a/front/core/components/confirm/style.scss b/front/core/components/confirm/style.scss new file mode 100644 index 000000000..d3cea6cb1 --- /dev/null +++ b/front/core/components/confirm/style.scss @@ -0,0 +1,3 @@ +.vn-confirm .window { + max-width: 30em +} \ No newline at end of file diff --git a/modules/ticket/back/methods/sale/calculate.js b/modules/ticket/back/methods/sale/recalculatePrice.js similarity index 86% rename from modules/ticket/back/methods/sale/calculate.js rename to modules/ticket/back/methods/sale/recalculatePrice.js index 5a2ac39b8..1bd754f6a 100644 --- a/modules/ticket/back/methods/sale/calculate.js +++ b/modules/ticket/back/methods/sale/recalculatePrice.js @@ -1,6 +1,6 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethodCtx('calculate', { + Self.remoteMethodCtx('recalculatePrice', { description: 'Calculates the price of a sale and its components', accessType: 'WRITE', accepts: [{ @@ -15,12 +15,12 @@ module.exports = Self => { root: true }, http: { - path: `/:id/calculate`, + path: `/:id/recalculatePrice`, verb: 'post' } }); - Self.calculate = async(ctx, id) => { + Self.recalculatePrice = async(ctx, id) => { const models = Self.app.models; const sale = await Self.findById(id); diff --git a/modules/ticket/back/methods/sale/specs/calculate.spec.js b/modules/ticket/back/methods/sale/specs/recalculatePrice.spec.js similarity index 77% rename from modules/ticket/back/methods/sale/specs/calculate.spec.js rename to modules/ticket/back/methods/sale/specs/recalculatePrice.spec.js index 48b14e972..23d7d721b 100644 --- a/modules/ticket/back/methods/sale/specs/calculate.spec.js +++ b/modules/ticket/back/methods/sale/specs/recalculatePrice.spec.js @@ -1,11 +1,11 @@ const app = require('vn-loopback/server/server'); -describe('sale calculate()', () => { +describe('sale recalculatePrice()', () => { const saleId = 7; it('should update the sale price', async() => { const ctx = {req: {accessToken: {userId: 9}}}; - const response = await app.models.Sale.calculate(ctx, saleId); + const response = await app.models.Sale.recalculatePrice(ctx, saleId); expect(response.affectedRows).toBeDefined(); }); @@ -13,7 +13,7 @@ describe('sale calculate()', () => { it('should throw an error if the ticket is not editable', async() => { const ctx = {req: {accessToken: {userId: 9}}}; const immutableSaleId = 1; - await app.models.Sale.calculate(ctx, immutableSaleId) + await app.models.Sale.recalculatePrice(ctx, immutableSaleId) .catch(response => { expect(response).toEqual(new Error(`The sales of this ticket can't be modified`)); error = response; diff --git a/modules/ticket/back/methods/ticket/recalculateComponents.js b/modules/ticket/back/methods/ticket/recalculateComponents.js new file mode 100644 index 000000000..81fee4d70 --- /dev/null +++ b/modules/ticket/back/methods/ticket/recalculateComponents.js @@ -0,0 +1,31 @@ +const UserError = require('vn-loopback/util/user-error'); +module.exports = Self => { + Self.remoteMethodCtx('recalculateComponents', { + description: 'Calculates the price of a sale and its components', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + description: 'The ticket id', + type: 'number', + required: true, + http: {source: 'path'} + }], + returns: { + type: 'Number', + root: true + }, + http: { + path: `/:id/recalculateComponents`, + verb: 'POST' + } + }); + + Self.recalculateComponents = async(ctx, id) => { + const isEditable = await Self.isEditable(ctx, id); + + if (!isEditable) + throw new UserError(`The current ticket can't be modified`); + + return Self.rawSql('CALL vn.ticket_recalcComponents(?)', [id]); + }; +}; diff --git a/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js new file mode 100644 index 000000000..72be4c3b4 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js @@ -0,0 +1,24 @@ +const app = require('vn-loopback/server/server'); + +describe('ticket recalculateComponents()', () => { + const ticketId = 11; + + it('should update the ticket components', async() => { + const ctx = {req: {accessToken: {userId: 9}}}; + const response = await app.models.Ticket.recalculateComponents(ctx, ticketId); + + expect(response.affectedRows).toBeDefined(); + }); + + it('should throw an error if the ticket is not editable', async() => { + const ctx = {req: {accessToken: {userId: 9}}}; + const immutableTicketId = 1; + await app.models.Ticket.recalculateComponents(ctx, immutableTicketId) + .catch(response => { + expect(response).toEqual(new Error(`The current ticket can't be modified`)); + error = response; + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index 1b352bcff..4ffde08b3 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -5,7 +5,7 @@ module.exports = Self => { require('../methods/sale/updatePrice')(Self); require('../methods/sale/updateQuantity')(Self); require('../methods/sale/updateConcept')(Self); - require('../methods/sale/calculate')(Self); + require('../methods/sale/recalculatePrice')(Self); Self.validatesPresenceOf('concept', { message: `Concept cannot be blank` diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index ed3a85da2..c42df62ef 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -26,6 +26,7 @@ module.exports = Self => { require('../methods/ticket/addSale')(Self); require('../methods/ticket/transferSales')(Self); require('../methods/ticket/canHaveStowaway')(Self); + require('../methods/ticket/recalculateComponents')(Self); Self.observe('before save', async function(ctx) { if (ctx.isNewInstance) return; diff --git a/modules/ticket/front/descriptor/index.html b/modules/ticket/front/descriptor/index.html index 12bd5105d..decc0b6d4 100644 --- a/modules/ticket/front/descriptor/index.html +++ b/modules/ticket/front/descriptor/index.html @@ -198,6 +198,13 @@ + question="Are you sure you want to send it?" + message="Send Delivery Note"> + + + \ No newline at end of file diff --git a/modules/ticket/front/descriptor/index.js b/modules/ticket/front/descriptor/index.js index 9cb320a31..9dac78406 100644 --- a/modules/ticket/front/descriptor/index.js +++ b/modules/ticket/front/descriptor/index.js @@ -35,6 +35,11 @@ class Controller extends Component { callback: this.showRegenerateInvoiceDialog, show: () => this.hasInvoice() }, + { + name: 'Recalculate components', + callback: this.comfirmRecalculateComponents, + show: () => this.isEditable + }, ]; } @@ -287,9 +292,26 @@ class Controller extends Component { return this.ticket.refFk !== null; } + /** + * Shows a delivery-note send confirmation + */ confirmDeliveryNote() { this.$.confirmDeliveryNote.show(); } + + /** + * Shows an invoice confirmation + */ + comfirmRecalculateComponents() { + this.$.recalculateComponentsConfirmation.show(); + } + + recalculateComponents() { + const query = `Tickets/${this.ticket.id}/recalculateComponents`; + this.$http.post(query).then(res => { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + }); + } } Controller.$inject = ['$element', '$scope', 'aclService', '$httpParamSerializer']; diff --git a/modules/ticket/front/descriptor/index.spec.js b/modules/ticket/front/descriptor/index.spec.js index 62aefa6cf..df0e95c02 100644 --- a/modules/ticket/front/descriptor/index.spec.js +++ b/modules/ticket/front/descriptor/index.spec.js @@ -1,6 +1,6 @@ import './index.js'; -describe('Ticket Component vnTicketDescriptor', () => { +fdescribe('Ticket Component vnTicketDescriptor', () => { let $httpParamSerializer; let $httpBackend; let controller; @@ -210,4 +210,17 @@ describe('Ticket Component vnTicketDescriptor', () => { expect(controller.isTicketModule).toHaveBeenCalledWith(); }); }); + + describe('recalculateComponents()', () => { + it('should make a query and show a success snackbar', () => { + spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.when('POST', 'Tickets/2/recalculateComponents').respond(); + $httpBackend.expect('POST', 'Tickets/2/recalculateComponents').respond(); + controller.recalculateComponents(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + }); + }); }); diff --git a/modules/ticket/front/descriptor/locale/es.yml b/modules/ticket/front/descriptor/locale/es.yml index 1703c5ffb..2f72e226e 100644 --- a/modules/ticket/front/descriptor/locale/es.yml +++ b/modules/ticket/front/descriptor/locale/es.yml @@ -25,4 +25,6 @@ You are going to regenerate the invoice: Vas a regenerar la factura Are you sure you want to regenerate the invoice?: ¿Seguro que quieres regenerar la factura? Invoice sent for a regeneration, will be available in a few minutes: La factura ha sido enviada para ser regenerada, estará disponible en unos minutos Shipped hour updated: Hora de envio modificada -Deleted ticket: Ticket eliminado \ No newline at end of file +Deleted ticket: Ticket eliminado +Recalculate components: Recalcular componentes +Are you sure you want to recalculate the components?: ¿Seguro que quieres recalcular los componentes? \ No newline at end of file diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index fb940a8ef..7113732e1 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -548,7 +548,7 @@ class Controller { calculateSalePrice() { const sale = this.checkedLines()[0]; - const query = `Sales/${sale.id}/calculate`; + const query = `Sales/${sale.id}/recalculatePrice`; this.$http.post(query).then(res => { this.vnApp.showSuccess(this.$translate.instant('Data saved!')); this.$scope.model.refresh(); diff --git a/modules/ticket/front/sale/specs/index.spec.js b/modules/ticket/front/sale/specs/index.spec.js index 0606d81a4..a23c6f204 100644 --- a/modules/ticket/front/sale/specs/index.spec.js +++ b/modules/ticket/front/sale/specs/index.spec.js @@ -349,7 +349,7 @@ describe('Ticket', () => { it('should make an HTTP post query ', () => { controller.sales[0].checked = true; - $httpBackend.when('POST', `Sales/4/calculate`).respond(200); + $httpBackend.when('POST', `Sales/4/recalculatePrice`).respond(200); $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); $httpBackend.whenGET(`Tickets/1/isEditable`).respond();