#1713 route.tickets boton mas bajo derecha
gitea/salix/dev This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2019-10-23 09:25:28 +02:00
parent d1e8f5fb1c
commit c84625b0d5
8 changed files with 556 additions and 126 deletions

View File

@ -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()),

View File

@ -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}`);

View File

@ -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: {

View File

@ -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,
},
]
`;

View File

@ -1,104 +1,105 @@
<vn-crud-model
vn-id="model"
url="/api/Routes/{{$ctrl.$stateParams.id}}/getTickets"
url="api/Routes/{{$ctrl.$stateParams.id}}/getTickets"
order="priority ASC"
data="$ctrl.tickets"
auto-load="true">
</vn-crud-model>
<form name="form">
<vn-card class="vn-pa-lg">
<vn-tool-bar class="vn-mb-sm">
<vn-button
icon="icon-wand"
ng-click="$ctrl.guessPriority()"
vn-tooltip="Sort routes">
</vn-button>
<vn-button
disabled="!$ctrl.isChecked"
ng-click="$ctrl.goToBuscaman()"
vn-tooltip="Open buscaman"
tooltip-position="up"
icon="icon-buscaman">
</vn-button>
</vn-tool-bar>
<vn-icon-button
vn-tooltip="Load more"
ng-click="$ctrl.goToBuscaman()">
</vn-icon-button>
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th>Order</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Client</vn-th>
<vn-th number shrink>Packages</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink>Warehouse</vn-th>
<vn-th shrink>PC</vn-th>
<vn-th>Street</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="ticket in $ctrl.tickets">
<vn-td shrink>
<vn-check
ng-model="ticket.checked">
</vn-check>
</vn-td>
<vn-td>
<vn-input-number
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
ng-model="ticket.priority"
rule="Ticket">
</vn-input-number>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showTicketDescriptor($event, ticket.id)"
class="link">
{{ticket.id}}
</span>
</vn-td>
<vn-td>
<span
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
class="link">
{{ticket.nickname}}
</span>
</vn-td>
<vn-td number shrink>{{ticket.packages}}</vn-td>
<vn-td shrink>{{ticket.volume}}</vn-td>
<vn-td shrink>{{ticket.warehouse.name}}</vn-td>
<vn-td shrink>{{ticket.client.postcode}}</vn-td>
<vn-td expand title="{{ticket.client.street}}">{{ticket.client.street}}</vn-td>
<vn-td shrink>
<vn-icon-button
ng-if="ticket.notes.length"
title="::{{ticket.notes[0].description}}"
icon="insert_drive_file">
</vn-icon-button>
</vn-td>
<vn-td>
<vn-icon-button
translate-attr="::{title: 'Remove ticket'}"
icon="delete"
ng-click="$ctrl.showDeleteConfirm(ticket.id)"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</form>
<vn-data-viewer model="model">
<form name="form">
<vn-card class="vn-pa-lg">
<vn-tool-bar class="vn-mb-sm">
<vn-button
icon="icon-wand"
ng-click="$ctrl.guessPriority()"
vn-tooltip="Sort routes">
</vn-button>
<vn-button
disabled="!$ctrl.isChecked"
ng-click="$ctrl.goToBuscaman()"
vn-tooltip="Open buscaman"
tooltip-position="up"
icon="icon-buscaman">
</vn-button>
</vn-tool-bar>
<vn-icon-button
vn-tooltip="Load more"
ng-click="$ctrl.goToBuscaman()">
</vn-icon-button>
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th>Order</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Client</vn-th>
<vn-th number shrink>Packages</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink>Warehouse</vn-th>
<vn-th expand>Postcode</vn-th>
<vn-th>Street</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="ticket in $ctrl.tickets">
<vn-td shrink>
<vn-check
ng-model="ticket.checked">
</vn-check>
</vn-td>
<vn-td>
<vn-input-number
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
ng-model="ticket.priority"
rule="Ticket">
</vn-input-number>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showTicketDescriptor($event, ticket.id)"
class="link">
{{ticket.id}}
</span>
</vn-td>
<vn-td>
<span
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
class="link">
{{ticket.nickname}}
</span>
</vn-td>
<vn-td number shrink>{{ticket.packages}}</vn-td>
<vn-td shrink>{{ticket.volume}}</vn-td>
<vn-td shrink>{{ticket.warehouse.name}}</vn-td>
<vn-td number shrink>{{ticket.address.postalCode}}</vn-td>
<vn-td expand title="{{ticket.address.street}}">{{ticket.address.street}}</vn-td>
<vn-td shrink>
<vn-icon-button
ng-if="ticket.notes.length"
title="::{{ticket.notes[0].description}}"
icon="insert_drive_file">
</vn-icon-button>
</vn-td>
<vn-td>
<vn-icon-button
translate-attr="::{title: 'Remove ticket'}"
icon="delete"
ng-click="$ctrl.showDeleteConfirm(ticket.id)"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</form>
</vn-data-viewer>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>
@ -109,4 +110,72 @@
vn-id="confirm"
question="Delete ticket from route?"
on-response="$ctrl.removeTicketFromRoute(response)">
</vn-confirm>
</vn-confirm>
<vn-crud-model
vn-id="possibleTicketsModel"
url="api/Tickets"
filter="$ctrl.possibleTicketsFilter"
data="$ctrl.possibleTickets">
</vn-crud-model>
<vn-dialog
vn-id="possibleTicketsDialog"
on-response="$ctrl.setTicketsRoute(response)">
<tpl-body>
<section class="header vn-pa-md">
<h5><span translate>Tickets to add</span></h5>
</section>
<vn-data-viewer class="vn-pa-md" model="possibleTicketsModel">
<vn-table model="possibleTicketsModel" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="possibleTicketsModel">
</vn-multi-check>
</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Client</vn-th>
<vn-th number shrink>Packages</vn-th>
<vn-th shrink>Warehouse</vn-th>
<vn-th expand>Postcode</vn-th>
<vn-th>Address</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="ticket in $ctrl.possibleTickets">
<vn-td shrink>
<vn-check
ng-model="ticket.checked">
</vn-check>
</vn-td>
<vn-td number>{{ticket.id}}</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
class="link">
{{ticket.nickname}}
</span>
</vn-td>
<vn-td number shrink>{{ticket.packages}}</vn-td>
<vn-td expand>{{ticket.warehouse.name}}</vn-td>
<vn-td number shrink>{{ticket.address.postalCode}}</vn-td>
<vn-td expand title="{{ticket.address.street}}">{{ticket.address.street}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-data-viewer>
</tpl-body>
<tpl-buttons>
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
<button response="ACCEPT" translate>Add</button>
</tpl-buttons>
</vn-dialog>
<vn-float-button
icon="add"
ng-click="$ctrl.openPossibleTicketsDialog()"
vn-tooltip="Add ticket"
vn-acl="delivery"
vn-acl-action="remove"
vn-bind="+"
fixed-bottom-right>
</vn-float-button>

View File

@ -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: '<'
}
});

View File

@ -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));
});
});
});

View File

@ -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
Sort routes: Ordenar rutas
Add ticket: Añadir ticket
Tickets to add: Tickets a añadir