Merge pull request '2663 - Drag & drop ticket to route index' (#488) from 2663-route_dragdrop into dev
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #488 Reviewed-by: Carlos Jimenez Ruiz <carlosjr@verdnatura.es>
This commit is contained in:
commit
e70b8a80e1
|
@ -163,5 +163,6 @@
|
||||||
"ASSIGN_ZONE_FIRST": "Asigna una zona primero",
|
"ASSIGN_ZONE_FIRST": "Asigna una zona primero",
|
||||||
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
|
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
|
||||||
"You can't upload images on the test environment": "No puedes subir imágenes en el entorno de pruebas",
|
"You can't upload images on the test environment": "No puedes subir imágenes en el entorno de pruebas",
|
||||||
|
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
|
||||||
"Sorts whole route": "Reordena ruta entera"
|
"Sorts whole route": "Reordena ruta entera"
|
||||||
}
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('ticketToRoute', {
|
Self.remoteMethod('insertTicket', {
|
||||||
description: 'Check if the ticket can be insert into the route and insert it',
|
description: 'Check if the ticket can be insert into the route and insert it',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'ticketId',
|
arg: 'routeId',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'ticketId ',
|
description: 'The route id',
|
||||||
http: {source: 'path'}
|
http: {source: 'path'}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'routeId',
|
arg: 'ticketId',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
|
@ -21,12 +21,12 @@ module.exports = Self => {
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
path: `/:ticketId/ticketToRoute`,
|
path: `/:routeId/insertTicket`,
|
||||||
verb: 'PATCH'
|
verb: 'PATCH'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.ticketToRoute = async(ticketId, routeId) => {
|
Self.insertTicket = async(routeId, ticketId) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const route = await models.Route.findById(routeId);
|
const route = await models.Route.findById(routeId);
|
||||||
|
@ -43,9 +43,10 @@ module.exports = Self => {
|
||||||
landed: {between: [minDate, maxDate]},
|
landed: {between: [minDate, maxDate]},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ticket)
|
if (!ticket)
|
||||||
throw new UserError('The selected ticket is not suitable for this route');
|
throw new UserError('The selected ticket is not suitable for this route');
|
||||||
|
|
||||||
return await ticket.updateAttribute('routeFk', route.id);
|
return ticket.updateAttribute('routeFk', route.id);
|
||||||
};
|
};
|
||||||
};
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
describe('route ticketToRoute()', () => {
|
describe('route insertTicket()', () => {
|
||||||
const deliveryId = 56;
|
const deliveryId = 56;
|
||||||
let originalTicket;
|
let originalTicket;
|
||||||
const routeId = 2;
|
const routeId = 2;
|
||||||
|
@ -30,7 +30,7 @@ describe('route ticketToRoute()', () => {
|
||||||
originalTicket = await app.models.Ticket.findById(14);
|
originalTicket = await app.models.Ticket.findById(14);
|
||||||
|
|
||||||
const ticketId = 14;
|
const ticketId = 14;
|
||||||
const result = await app.models.Route.ticketToRoute(ticketId, routeId);
|
const result = await app.models.Route.insertTicket(routeId, ticketId);
|
||||||
|
|
||||||
expect(result.routeFk).toEqual(2);
|
expect(result.routeFk).toEqual(2);
|
||||||
});
|
});
|
||||||
|
@ -40,7 +40,7 @@ describe('route ticketToRoute()', () => {
|
||||||
let error;
|
let error;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await app.models.Route.ticketToRoute(ticketId, routeId);
|
await app.models.Route.insertTicket(routeId, ticketId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e.message;
|
error = e.message;
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ module.exports = Self => {
|
||||||
require('../methods/route/guessPriority')(Self);
|
require('../methods/route/guessPriority')(Self);
|
||||||
require('../methods/route/updateVolume')(Self);
|
require('../methods/route/updateVolume')(Self);
|
||||||
require('../methods/route/getDeliveryPoint')(Self);
|
require('../methods/route/getDeliveryPoint')(Self);
|
||||||
require('../methods/route/ticketToRoute')(Self);
|
require('../methods/route/insertTicket')(Self);
|
||||||
|
|
||||||
Self.validate('kmStart', validateDistance, {
|
Self.validate('kmStart', validateDistance, {
|
||||||
message: 'Distance must be lesser than 1000'
|
message: 'Distance must be lesser than 1000'
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
<vn-tbody>
|
<vn-tbody>
|
||||||
<a ng-repeat="route in model.data"
|
<a ng-repeat="route in model.data"
|
||||||
class="clickable vn-tr search-result"
|
class="clickable vn-tr search-result"
|
||||||
ui-sref="route.card.summary({id: {{::route.id}}})">
|
ui-sref="route.card.summary({id: {{::route.id}}})"
|
||||||
|
ng-attr-id="{{::route.id}}" vn-droppable="$ctrl.onDrop($event)">
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-check
|
<vn-check
|
||||||
ng-model="route.checked"
|
ng-model="route.checked"
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default class Controller extends Section {
|
||||||
constructor($element, $, vnReport) {
|
constructor($element, $, vnReport) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
this.vnReport = vnReport;
|
this.vnReport = vnReport;
|
||||||
|
this.droppableElement = 'a.vn-tr';
|
||||||
}
|
}
|
||||||
|
|
||||||
preview(route) {
|
preview(route) {
|
||||||
|
@ -38,6 +39,41 @@ export default class Controller extends Section {
|
||||||
routeId: routesId
|
routeId: routesId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDrop($event) {
|
||||||
|
const target = $event.target;
|
||||||
|
const droppable = target.closest(this.droppableElement);
|
||||||
|
const ticketId = $event.dataTransfer.getData('Text');
|
||||||
|
const routeId = droppable.id;
|
||||||
|
|
||||||
|
if (isNaN(ticketId)) {
|
||||||
|
const regexp = new RegExp(/\/ticket\/([0-9]+)\//i);
|
||||||
|
const matches = ticketId.match(regexp);
|
||||||
|
|
||||||
|
if (matches && matches.length)
|
||||||
|
this.insert(routeId, matches[1]);
|
||||||
|
else
|
||||||
|
this.vnApp.showError(this.$t('Ticket not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNaN(ticketId))
|
||||||
|
this.insert(routeId, ticketId);
|
||||||
|
}
|
||||||
|
|
||||||
|
insert(routeId, ticketId) {
|
||||||
|
routeId = parseInt(routeId);
|
||||||
|
ticketId = parseInt(ticketId);
|
||||||
|
|
||||||
|
const query = `Routes/${routeId}/insertTicket`;
|
||||||
|
return this.$http.patch(query, {ticketId}).then(() => {
|
||||||
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||||
|
this.$.model.refresh();
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.status == 404)
|
||||||
|
return this.vnApp.showError(this.$t('Ticket not found'));
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$element', '$scope', 'vnReport'];
|
Controller.$inject = ['$element', '$scope', 'vnReport'];
|
||||||
|
|
|
@ -3,10 +3,12 @@ import crudModel from 'core/mocks/crud-model';
|
||||||
|
|
||||||
describe('Component vnRouteIndex', () => {
|
describe('Component vnRouteIndex', () => {
|
||||||
let controller;
|
let controller;
|
||||||
|
let $httpBackend;
|
||||||
|
|
||||||
beforeEach(ngModule('route'));
|
beforeEach(ngModule('route'));
|
||||||
|
|
||||||
beforeEach(inject($componentController => {
|
beforeEach(inject(($componentController, _$httpBackend_) => {
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
const $element = angular.element('<vn-route-index></vn-route-index>');
|
const $element = angular.element('<vn-route-index></vn-route-index>');
|
||||||
controller = $componentController('vnRouteIndex', {$element});
|
controller = $componentController('vnRouteIndex', {$element});
|
||||||
controller.$.model = crudModel;
|
controller.$.model = crudModel;
|
||||||
|
@ -57,4 +59,83 @@ describe('Component vnRouteIndex', () => {
|
||||||
expect(controller.vnReport.show).toHaveBeenCalledWith('driver-route', expectedParams);
|
expect(controller.vnReport.show).toHaveBeenCalledWith('driver-route', expectedParams);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('onDrop()', () => {
|
||||||
|
it('should call the insert method when dragging a ticket number', () => {
|
||||||
|
jest.spyOn(controller, 'insert');
|
||||||
|
|
||||||
|
const routeId = '1';
|
||||||
|
const expectedTicketId = '16';
|
||||||
|
const draggedElement = '16';
|
||||||
|
const droppable = document.createElement('a');
|
||||||
|
droppable.setAttribute('id', 1);
|
||||||
|
droppable.classList.add('vn-tr');
|
||||||
|
|
||||||
|
const $event = {
|
||||||
|
dataTransfer: {
|
||||||
|
getData: () => draggedElement
|
||||||
|
},
|
||||||
|
target: droppable
|
||||||
|
};
|
||||||
|
controller.onDrop($event);
|
||||||
|
|
||||||
|
expect(controller.insert).toHaveBeenCalledWith(routeId, expectedTicketId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the insert method when dragging a ticket link', () => {
|
||||||
|
jest.spyOn(controller, 'insert');
|
||||||
|
|
||||||
|
const routeId = '1';
|
||||||
|
const expectedTicketId = '11';
|
||||||
|
const draggedElement = 'http://arkamcity.com/#!/ticket/11/summary';
|
||||||
|
const droppable = document.createElement('a');
|
||||||
|
droppable.setAttribute('id', 1);
|
||||||
|
droppable.classList.add('vn-tr');
|
||||||
|
|
||||||
|
const $event = {
|
||||||
|
dataTransfer: {
|
||||||
|
getData: () => draggedElement
|
||||||
|
},
|
||||||
|
target: droppable
|
||||||
|
};
|
||||||
|
controller.onDrop($event);
|
||||||
|
|
||||||
|
expect(controller.insert).toHaveBeenCalledWith(routeId, expectedTicketId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when dragging an invalid ticket link', () => {
|
||||||
|
jest.spyOn(controller.vnApp, 'showError');
|
||||||
|
|
||||||
|
const draggedElement = 'http://arkamcity.com/#!/item/11/summary';
|
||||||
|
const droppable = document.createElement('a');
|
||||||
|
droppable.setAttribute('id', 1);
|
||||||
|
droppable.classList.add('vn-tr');
|
||||||
|
const $event = {
|
||||||
|
dataTransfer: {
|
||||||
|
getData: () => draggedElement
|
||||||
|
},
|
||||||
|
target: droppable
|
||||||
|
};
|
||||||
|
controller.onDrop($event);
|
||||||
|
|
||||||
|
expect(controller.vnApp.showError).toHaveBeenCalledWith('Ticket not found');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('insert()', () => {
|
||||||
|
it('should make a HTTP patch query and then call both refresh and showSuccess methods', () => {
|
||||||
|
jest.spyOn(controller.$.model, 'refresh').mockReturnThis();
|
||||||
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
|
const routeId = 1;
|
||||||
|
const ticketId = 11;
|
||||||
|
const data = {ticketId};
|
||||||
|
$httpBackend.expect('PATCH', `Routes/1/insertTicket`, data).respond();
|
||||||
|
controller.insert(routeId, ticketId);
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
|
expect(controller.$.model.refresh).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -161,11 +161,11 @@ class Controller extends Section {
|
||||||
this.insert(ticketId);
|
this.insert(ticketId);
|
||||||
}
|
}
|
||||||
|
|
||||||
insert(id) {
|
insert(ticketId) {
|
||||||
const params = {routeId: this.route.id};
|
ticketId = parseInt(ticketId);
|
||||||
const query = `Routes/${id}/ticketToRoute`;
|
|
||||||
|
|
||||||
return this.$http.patch(query, params).then(() => {
|
const query = `Routes/${this.route.id}/insertTicket`;
|
||||||
|
return this.$http.patch(query, {ticketId}).then(() => {
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||||
this.$.model.refresh();
|
this.$.model.refresh();
|
||||||
this.card.reload();
|
this.card.reload();
|
||||||
|
|
|
@ -309,8 +309,8 @@ describe('Route', () => {
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const data = {routeId: 1};
|
const data = {ticketId};
|
||||||
$httpBackend.expect('PATCH', `Routes/11/ticketToRoute`, data).respond();
|
$httpBackend.expect('PATCH', `Routes/1/insertTicket`, data).respond();
|
||||||
controller.insert(ticketId);
|
controller.insert(ticketId);
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue