diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 2d3353f15..7a7083313 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -471,7 +471,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF (11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()), (12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()), (13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, CURDATE()), - (14, 1, 2, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()), + (14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()), (15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()), (16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), (17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), diff --git a/e2e/paths/05-ticket-module/16_summary.spec.js b/e2e/paths/05-ticket-module/16_summary.spec.js index 0fd3b8b7c..2832da428 100644 --- a/e2e/paths/05-ticket-module/16_summary.spec.js +++ b/e2e/paths/05-ticket-module/16_summary.spec.js @@ -17,7 +17,7 @@ describe('Ticket Summary path', () => { it(`should display details from the ticket and it's client on the top of the header`, async() => { let result = await nightmare - .waitForSpinnerLoad() + .waitForTextInElement(selectors.ticketSummary.header, 'Bruce Banner') .waitToGetProperty(selectors.ticketSummary.header, 'innerText'); expect(result).toContain(`Ticket #${ticketId}`); diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js index cbe7db9f8..a0014a60d 100644 --- a/modules/route/back/methods/route/getTickets.js +++ b/modules/route/back/methods/route/getTickets.js @@ -28,12 +28,6 @@ module.exports = Self => { fields: ['id', 'packages', 'warehouseFk', 'nickname', 'clientFk', 'priority', 'addressFk'], order: 'priority', include: [ - { - relation: 'client', - scope: { - fields: ['id', 'street', 'postcode'], - } - }, { relation: 'state', scope: { diff --git a/modules/route/front/tickets/__snapshots__/index.spec.js.snap b/modules/route/front/tickets/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..9476a8e09 --- /dev/null +++ b/modules/route/front/tickets/__snapshots__/index.spec.js.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Route getSelectedItems() should return the selected items 1`] = ` +Array [ + Object { + "checked": true, + "id": 1, + }, + Object { + "checked": true, + "id": 3, + }, + Object { + "checked": true, + "id": 5, + }, +] +`; diff --git a/modules/route/front/tickets/index.html b/modules/route/front/tickets/index.html index 3df0bc346..16b67ed75 100644 --- a/modules/route/front/tickets/index.html +++ b/modules/route/front/tickets/index.html @@ -1,104 +1,105 @@ -
- - - - - - - - - - - - - - - - - Order - Ticket - Client - Packages - - Warehouse - PC - Street - - - - - - - - - - - - - - - - - {{ticket.id}} - - - - - {{ticket.nickname}} - - - {{ticket.packages}} - {{ticket.volume}} - {{ticket.warehouse.name}} - {{ticket.client.postcode}} - {{ticket.client.street}} - - - - - - - - - - - - -
- + +
+ + + + + + + + + + + + + + + + + Order + Ticket + Client + Packages + + Warehouse + Postcode + Street + + + + + + + + + + + + + + + + + {{ticket.id}} + + + + + {{ticket.nickname}} + + + {{ticket.packages}} + {{ticket.volume}} + {{ticket.warehouse.name}} + {{ticket.address.postalCode}} + {{ticket.address.street}} + + + + + + + + + + + + +
+
@@ -109,4 +110,72 @@ vn-id="confirm" question="Delete ticket from route?" on-response="$ctrl.removeTicketFromRoute(response)"> - \ No newline at end of file + + + + + +
+
Tickets to add
+
+ + + + + + + + + Ticket + Client + Packages + Warehouse + Postcode + Address + + + + + + + + + {{ticket.id}} + + + {{ticket.nickname}} + + + {{ticket.packages}} + {{ticket.warehouse.name}} + {{ticket.address.postalCode}} + {{ticket.address.street}} + + + + +
+ + + + +
+ + \ No newline at end of file diff --git a/modules/route/front/tickets/index.js b/modules/route/front/tickets/index.js index ce12d88ac..9b9a49222 100644 --- a/modules/route/front/tickets/index.js +++ b/modules/route/front/tickets/index.js @@ -2,12 +2,23 @@ import ngModule from '../module'; import './style.scss'; class Controller { - constructor($stateParams, $, $translate, $http, vnApp) { + constructor($stateParams, $scope, $translate, $http, vnApp, $filter) { this.$translate = $translate; this.$stateParams = $stateParams; - this.$ = $; + this.$ = $scope; this.$http = $http; this.vnApp = vnApp; + this.$filter = $filter; + } + + set route(value) { + this._route = value; + if (value) + this.buildPossibleTicketsFilter(); + } + + get route() { + return this._route; } get isChecked() { @@ -19,13 +30,37 @@ class Controller { return false; } + buildPossibleTicketsFilter() { + let minDate = new Date(this.route.finished); + minDate.setHours(0, 0, 0, 0); + + let maxDate = new Date(this.route.finished); + maxDate.setHours(23, 59, 59, 59); + + this.possibleTicketsFilter = { + where: { + zoneFk: this.route.zoneFk, + routeFk: null, + landed: {between: [minDate, maxDate]}, + }, + include: [ + { + relation: 'warehouse', + scope: { + fields: ['name'] + }, + }, { + relation: 'address' + } + ] + }; + } + getHighestPriority() { - let max = 0; - this.$.model.data.forEach(tag => { - if (tag.priority > max) - max = tag.priority; - }); - return max + 1; + let highestPriority = Math.max(...this.$.model.data.map(tag => { + return tag.priority; + })); + return highestPriority + 1; } setPriority(id, priority) { @@ -37,16 +72,16 @@ class Controller { }); } - getCheckedLines() { - let lines = []; - let data = this.tickets; - if (data) { - for (let i = 0; i < data.length; i++) { - if (data[i].checked) - lines.push(data[i]); + getSelectedItems(items) { + const selectedItems = []; + + if (items) { + for (let i = 0; i < items.length; i++) { + if (items[i].checked) + selectedItems.push(items[i]); } } - return lines; + return selectedItems; } goToBuscaman() { @@ -54,7 +89,7 @@ class Controller { let firstAddress = `46460 Av Espioca 100-46460 Silla`; let addresses = firstAddress; - let lines = this.getCheckedLines(); + let lines = this.getSelectedItems(this.tickets); let url = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr='; lines.forEach(line => { @@ -64,8 +99,8 @@ class Controller { window.open(url + addresses, '_blank'); } - showDeleteConfirm(ticket) { - this.selectedTicket = ticket; + showDeleteConfirm(id) { + this.selectedTicket = id; this.$.confirm.show(); } @@ -109,14 +144,38 @@ class Controller { this.$.clientDescriptor.show(); event.preventDefault(); } + + openPossibleTicketsDialog() { + this.$.possibleTicketsModel.refresh(); + this.$.possibleTicketsDialog.show(); + } + + setTicketsRoute(response) { + if (response === 'ACCEPT') { + let tickets = this.getSelectedItems(this.possibleTickets); + + for (let i = 0; i < tickets.length; i++) { + delete tickets[i].checked; + tickets[i].routeFk = this.route.id; + } + + return this.$.possibleTicketsModel.save().then(() => { + this.$.model.data = this.$.model.data.concat(tickets); + }); + } + return Promise.resolve(); + } } -Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp']; +Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp', '$filter']; ngModule.component('vnRouteTickets', { template: require('./index.html'), + controller: Controller, require: { card: '^vnRouteCard' }, - controller: Controller + bindings: { + route: '<' + } }); diff --git a/modules/route/front/tickets/index.spec.js b/modules/route/front/tickets/index.spec.js new file mode 100644 index 000000000..96a9c4b45 --- /dev/null +++ b/modules/route/front/tickets/index.spec.js @@ -0,0 +1,288 @@ +import './index.js'; + +describe('Route', () => { + let controller; + let $httpBackend; + + beforeEach(angular.mock.module('route', $translateProvider => { + $translateProvider.translations('en', {}); + })); + + beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnRouteTickets'); + })); + + describe('route setter/getter', () => { + it('should return the route id', () => { + controller.route = 2; + + expect(controller.route).toEqual(2); + }); + }); + + describe('isChecked getter', () => { + it('should return false if none of the tickets is checked or there are no tickets', () => { + expect(controller.isChecked).toBeFalsy(); + }); + + it('should return true if any of the tickets is checked', () => { + controller.tickets = [{checked: true}]; + + expect(controller.isChecked).toBeTruthy(); + }); + }); + + describe('buildPossibleTicketsFilter()', () => { + it('should build the possible tickets filter', () => { + let expectedFilter = { + include: [ + { + relation: 'warehouse', + scope: { + fields: ['name'] + } + }, { + relation: 'address' + } + ], + where: { + landed: { + between: [ + jasmine.any(Date), + jasmine.any(Date) + ] + }, + routeFk: null, + zoneFk: 67 + } + }; + controller.route = { + finished: new Date(), + routeFk: null, + zoneFk: 67 + }; + + controller.buildPossibleTicketsFilter(); + + expect(controller.possibleTicketsFilter).toEqual(expectedFilter); + }); + }); + + describe('getHighestPriority()', () => { + it('should return the highest value found in priorities plus 1', () => { + controller.$.model = {data: [ + {priority: 99}, + {priority: 1}, + {priority: 2}, + {priority: 3}, + {priority: 4}, + {priority: 5}, + ]}; + + let result = controller.getHighestPriority(); + + expect(result).toEqual(100); + }); + }); + + describe('setPriority()', () => { + it('should set a ticket priority', () => { + controller.$.model = {refresh: () => {}}; + spyOn(controller.$.model, 'refresh'); + spyOn(controller.vnApp, 'showSuccess'); + const ticketId = 1; + const priority = 999; + + $httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok'); + controller.setPriority(ticketId, priority); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + }); + }); + + describe('getSelectedItems()', () => { + it('should return the selected items', () => { + let items = [ + {id: 1, checked: true}, + {id: 2, checked: false}, + {id: 3, checked: true}, + {id: 4, checked: false}, + {id: 5, checked: true}, + ]; + + let selectedItems = controller.getSelectedItems(items); + + expect(selectedItems).toMatchSnapshot(); + }); + }); + + describe('goToBuscaman()', () => { + it('should open buscaman with the given arguments', () => { + spyOn(window, 'open'); + const expectedUrl = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=46460 Av Espioca 100-46460 Silla+to:n19 my street-n19 London'; + controller.tickets = [ + { + id: 1, + checked: true, + address: { + street: 'my street', + postalCode: 'n19', + city: 'London' + } + }, + ]; + + controller.goToBuscaman(); + + expect(window.open).toHaveBeenCalledWith(expectedUrl, '_blank'); + }); + }); + + describe('showDeleteConfirm()', () => { + it('should open a confirm dialog after setting the selected ticket into the controller', () => { + controller.$.confirm = {show: () => {}}; + spyOn(controller.$.confirm, 'show'); + let ticketId = 1; + + controller.showDeleteConfirm(ticketId); + + expect(controller.selectedTicket).toEqual(ticketId); + expect(controller.$.confirm.show).toHaveBeenCalledWith(); + }); + }); + + describe('removeTicketFromRoute()', () => { + it('should perform a patch query then call showSuccess and updateVolume methods', () => { + spyOn(controller, 'updateVolume'); + spyOn(controller.vnApp, 'showSuccess'); + let ticketId = 1; + controller.selectedTicket = ticketId; + + $httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok'); + controller.removeTicketFromRoute('ACCEPT'); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Ticket removed from route'); + expect(controller.updateVolume).toHaveBeenCalledWith(); + }); + }); + + describe('updateVolume()', () => { + it('should perform a POST query then call both reload and refresh methods', () => { + controller.$.model = {refresh: () => {}}; + controller.card = {reload: () => {}}; + controller.$stateParamds = {id: 999}; + spyOn(controller.$.model, 'refresh'); + spyOn(controller.card, 'reload'); + + let ticketId = 1; + controller.selectedTicket = ticketId; + + const url = `/route/api/Routes/${controller.$stateParams.id}/updateVolume`; + $httpBackend.expectPOST(url).respond('ok'); + controller.updateVolume(); + $httpBackend.flush(); + + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + expect(controller.card.reload).toHaveBeenCalledWith(); + }); + }); + + describe('guessPriority()', () => { + it('should perform a GET query then call both refresh and showSuccess methods', () => { + controller.$.model = {refresh: () => {}}; + spyOn(controller.$.model, 'refresh'); + spyOn(controller.vnApp, 'showSuccess'); + controller.$stateParamds = {id: 999}; + + const url = `/api/Routes/${controller.$stateParams.id}/guessPriority/`; + $httpBackend.expectGET(url).respond('ok'); + controller.guessPriority(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Order changed'); + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + }); + }); + + describe('showTicketDescriptor()', () => { + it('should call the descriptor show function after setting the parent and the ticket id', () => { + controller.$.ticketDescriptor = {show: () => {}}; + spyOn(controller.$.ticketDescriptor, 'show'); + const event = {target: {}, preventDefault: () => {}}; + spyOn(event, 'preventDefault'); + const ticketId = 999; + controller.showTicketDescriptor(event, ticketId); + + expect(controller.$.ticketDescriptor.ticketFk).toEqual(ticketId); + expect(controller.$.ticketDescriptor.show).toHaveBeenCalledWith(); + expect(event.preventDefault).toHaveBeenCalledWith(); + }); + }); + + describe('showClientDescriptor()', () => { + it('should call the descriptor show method after setting the parent and the client id', () => { + controller.$.clientDescriptor = {show: () => {}}; + spyOn(controller.$.clientDescriptor, 'show'); + const event = {target: {}, preventDefault: () => {}}; + spyOn(event, 'preventDefault'); + const clientId = 999; + controller.showClientDescriptor(event, clientId); + + expect(controller.$.clientDescriptor.clientFk).toEqual(clientId); + expect(controller.$.clientDescriptor.show).toHaveBeenCalledWith(); + expect(event.preventDefault).toHaveBeenCalledWith(); + }); + }); + + describe('openPossibleTicketsDialog()', () => { + it('should call both refresh and show methods in posible tickets model and dialog', () => { + controller.$.possibleTicketsModel = {refresh: () => {}}; + spyOn(controller.$.possibleTicketsModel, 'refresh'); + controller.$.possibleTicketsDialog = {show: () => {}}; + spyOn(controller.$.possibleTicketsDialog, 'show'); + + controller.openPossibleTicketsDialog(); + + expect(controller.$.possibleTicketsModel.refresh).toHaveBeenCalledWith(); + expect(controller.$.possibleTicketsDialog.show).toHaveBeenCalledWith(); + }); + }); + + describe('setTicketsRoute()', () => { + it('should perform a POST query to add tickets to the route', done => { + controller.$.possibleTicketsModel = {save: () => {}}; + spyOn(controller.$.possibleTicketsModel, 'save').and.returnValue(Promise.resolve()); + controller.$.model = {data: [ + {id: 1, checked: false} + ]}; + + controller.route = {id: 111}; + + controller.possibleTickets = [ + {id: 2, checked: false}, + {id: 3, checked: true}, + {id: 4, checked: false}, + {id: 5, checked: true}, + ]; + + let expectedResult = [ + {checked: false, id: 1}, + {id: 3, routeFk: 111}, + {id: 5, routeFk: 111} + ]; + + controller.setTicketsRoute('ACCEPT').then(() => { + expect(controller.$.model.data).toEqual(expectedResult); + done(); + }).catch(done.fail); + }); + + it('should just return a promise', () => { + expect(controller.setTicketsRoute('CANCEL')).toEqual(jasmine.any(Promise)); + }); + }); +}); diff --git a/modules/route/front/tickets/locale/es.yml b/modules/route/front/tickets/locale/es.yml index d668e0a8a..7edeff17b 100644 --- a/modules/route/front/tickets/locale/es.yml +++ b/modules/route/front/tickets/locale/es.yml @@ -3,4 +3,6 @@ Open buscaman: Abrir buscaman Ticket removed from route: Ticket borrado de la ruta Order changed: Orden cambiado Delete ticket from route?: ¿Borrar ticket de la ruta? -Sort routes: Ordenar rutas \ No newline at end of file +Sort routes: Ordenar rutas +Add ticket: Añadir ticket +Tickets to add: Tickets a añadir \ No newline at end of file