diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 175b90ef0..171e376d2 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -111,9 +111,7 @@ "This phone already exists": "Este teléfono ya existe", "You cannot move a parent to its own sons": "No puedes mover un elemento padre a uno de sus hijos", "You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado", - "You cannot delete this ticket because is already invoiced, deleted or prepared": "No puedes eliminar este tiquet porque ya está facturado, eliminado o preparado", "You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada", "You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero", - "Has deleted the ticket id": "Ha eliminado el ticket id [#{{id}}]({{{url}}})", - "You cannot remove this ticket because is already invoiced, deleted or prepared": "You cannot remove this ticket because is already invoiced, deleted or prepared" + "Has deleted the ticket id": "Ha eliminado el ticket id [#{{id}}]({{{url}}})" } \ No newline at end of file diff --git a/modules/ticket/back/methods/sale/calculate.js b/modules/ticket/back/methods/sale/calculate.js new file mode 100644 index 000000000..5a2ac39b8 --- /dev/null +++ b/modules/ticket/back/methods/sale/calculate.js @@ -0,0 +1,34 @@ +const UserError = require('vn-loopback/util/user-error'); +module.exports = Self => { + Self.remoteMethodCtx('calculate', { + description: 'Calculates the price of a sale and its components', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + description: 'The sale id', + type: 'number', + required: true, + http: {source: 'path'} + }], + returns: { + type: 'Number', + root: true + }, + http: { + path: `/:id/calculate`, + verb: 'post' + } + }); + + Self.calculate = async(ctx, id) => { + const models = Self.app.models; + + const sale = await Self.findById(id); + const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk); + + if (!isEditable) + throw new UserError(`The sales of this ticket can't be modified`); + + return Self.rawSql('CALL vn.ticketCalculateSale(?)', [id]); + }; +}; diff --git a/modules/ticket/back/methods/sale/specs/calculate.spec.js b/modules/ticket/back/methods/sale/specs/calculate.spec.js new file mode 100644 index 000000000..48b14e972 --- /dev/null +++ b/modules/ticket/back/methods/sale/specs/calculate.spec.js @@ -0,0 +1,24 @@ +const app = require('vn-loopback/server/server'); + +describe('sale calculate()', () => { + 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); + + expect(response.affectedRows).toBeDefined(); + }); + + 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) + .catch(response => { + expect(response).toEqual(new Error(`The sales of this ticket can't be modified`)); + error = response; + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index b9ac03560..fb72c7dbc 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -27,7 +27,7 @@ module.exports = Self => { const $t = ctx.req.__; // $translate if (!isEditable) - throw new UserError('You cannot delete this ticket because is already invoiced, deleted or prepared'); + throw new UserError(`The sales of this ticket can't be modified`); // Check if has sales with shelving const sales = await models.Sale.find({ diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index b1fe82565..1b352bcff 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -5,6 +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); Self.validatesPresenceOf('concept', { message: `Concept cannot be blank` diff --git a/modules/ticket/front/basic-data/index.html b/modules/ticket/front/basic-data/index.html index f492283b0..46e2ad6a9 100644 --- a/modules/ticket/front/basic-data/index.html +++ b/modules/ticket/front/basic-data/index.html @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/modules/ticket/front/basic-data/step-one/index.html b/modules/ticket/front/basic-data/step-one/index.html index b98267a85..ccdf8a15f 100644 --- a/modules/ticket/front/basic-data/step-one/index.html +++ b/modules/ticket/front/basic-data/step-one/index.html @@ -5,7 +5,7 @@ auto-load="true">
- + - + { describe('onSubmit()', () => { it(`should return an error if the item doesn't have option property in the controller`, () => { - controller.ticket = {}; - + controller._ticket = {id: 1}; controller.onSubmit(); expect(vnApp.showError).toHaveBeenCalledWith('Choose an option'); diff --git a/modules/ticket/front/basic-data/step-two/index.html b/modules/ticket/front/basic-data/step-two/index.html index aa5a23212..ef5b09d4b 100644 --- a/modules/ticket/front/basic-data/step-two/index.html +++ b/modules/ticket/front/basic-data/step-two/index.html @@ -1,42 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ItemDescriptionQuantityPrice (PPU)New price (PPU)Price difference
{{("000000"+sale.itemFk).slice(-6)}} - - - {{::sale.quantity}}{{::sale.price | currency: 'EUR': 2}}{{::sale.component.newPrice | currency: 'EUR': 2}}{{::sale.component.difference | currency: 'EUR': 2}}
{{$ctrl.totalPrice | currency: 'EUR': 2}}{{$ctrl.totalNewPrice | currency: 'EUR': 2}}{{$ctrl.totalPriceDifference | currency: 'EUR': 2}}
-
+ + + + + Item + Description + Quantity + Price (PPU) + New price (PPU) + Price difference + + + + + {{("000000"+sale.itemFk).slice(-6)}} + + + + + {{::sale.quantity}} + {{::sale.price | currency: 'EUR': 2}} + {{::sale.component.newPrice | currency: 'EUR': 2}} + {{::sale.component.difference | currency: 'EUR': 2}} + + + + + + {{$ctrl.totalPrice | currency: 'EUR': 2}} + {{$ctrl.totalNewPrice | currency: 'EUR': 2}} + {{$ctrl.totalPriceDifference | currency: 'EUR': 2}} + + + diff --git a/modules/ticket/front/basic-data/step-two/index.js b/modules/ticket/front/basic-data/step-two/index.js index 1446ac171..a481afefc 100644 --- a/modules/ticket/front/basic-data/step-two/index.js +++ b/modules/ticket/front/basic-data/step-two/index.js @@ -7,6 +7,17 @@ class Controller { $onInit() { this.data.registerChild(this); + } + + get ticket() { + return this._ticket; + } + + set ticket(value) { + this._ticket = value; + + if (!value) return; + this.getTotalPrice(); this.getTotalNewPrice(); this.getTotalDifferenceOfPrice(); diff --git a/modules/ticket/front/descriptor/index.js b/modules/ticket/front/descriptor/index.js index d37d2d42b..9cb320a31 100644 --- a/modules/ticket/front/descriptor/index.js +++ b/modules/ticket/front/descriptor/index.js @@ -40,7 +40,7 @@ class Controller extends Component { showChangeShipped() { if (!this.isEditable) { - this.vnApp.showError(this.$translate.instant('This ticket can\'t be modified')); + this.vnApp.showError(this.$translate.instant(`This ticket can't be modified`)); return; } this.newShipped = this.ticket.shipped; diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index 6df328d88..fb940a8ef 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -32,6 +32,11 @@ class Controller { callback: this.createClaim, show: () => this.isEditable }, + { + name: 'Recalculate price', + callback: this.calculateSalePrice, + show: () => this.hasOneSaleSelected() + }, ]; this._sales = []; this.imagesPath = '//verdnatura.es/vn-image-data/catalog'; @@ -534,6 +539,21 @@ class Controller { this.isEditable = res.data; }); } + + hasOneSaleSelected() { + if (this.totalCheckedLines() === 1) + return true; + return false; + } + + calculateSalePrice() { + const sale = this.checkedLines()[0]; + const query = `Sales/${sale.id}/calculate`; + this.$http.post(query).then(res => { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + this.$scope.model.refresh(); + }); + } } Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate']; diff --git a/modules/ticket/front/sale/locale/es.yml b/modules/ticket/front/sale/locale/es.yml index 9deb3f06a..f381eb6e6 100644 --- a/modules/ticket/front/sale/locale/es.yml +++ b/modules/ticket/front/sale/locale/es.yml @@ -29,4 +29,5 @@ SMSAvailability: >- {{notAvailables}} no disponible/s. Disculpe las molestias. Continue anyway?: ¿Continuar de todas formas? This ticket is now empty: El ticket ha quedado vacio -Do you want to delete it?: ¿Quieres borrarlo? \ No newline at end of file +Do you want to delete it?: ¿Quieres borrarlo? +Recalculate price: Recalcular precio \ No newline at end of file diff --git a/modules/ticket/front/sale/specs/index.spec.js b/modules/ticket/front/sale/specs/index.spec.js index cd0e322a5..0606d81a4 100644 --- a/modules/ticket/front/sale/specs/index.spec.js +++ b/modules/ticket/front/sale/specs/index.spec.js @@ -1,5 +1,6 @@ import '../index.js'; import watcher from 'core/mocks/watcher'; +import crudModel from 'core/mocks/crud-model'; describe('Ticket', () => { describe('Component vnTicketSale', () => { @@ -40,6 +41,7 @@ describe('Ticket', () => { $scope.watcher = watcher; $scope.sms = {open: () => {}}; $scope.ticket = ticket; + $scope.model = crudModel; $httpBackend = _$httpBackend_; Object.defineProperties($state.params, { id: { @@ -334,5 +336,27 @@ describe('Ticket', () => { expect(window.open).toHaveBeenCalledWith('/somePath', '_blank'); }); }); + + describe('hasOneSaleSelected()', () => { + it('should return true if just one sale is selected', () => { + controller.sales[0].checked = true; + + expect(controller.hasOneSaleSelected()).toBeTruthy(); + }); + }); + + describe('calculateSalePrice()', () => { + it('should make an HTTP post query ', () => { + controller.sales[0].checked = true; + + $httpBackend.when('POST', `Sales/4/calculate`).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(); + + controller.calculateSalePrice(); + $httpBackend.flush(); + }); + }); }); });