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

Reviewed-on: #488
Reviewed-by: Carlos Jimenez Ruiz <carlosjr@verdnatura.es>
This commit is contained in:
Carlos Jimenez Ruiz 2020-12-21 14:06:02 +00:00
commit e70b8a80e1
9 changed files with 139 additions and 19 deletions

View File

@ -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"
} }

View File

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

View File

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

View File

@ -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'

View File

@ -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"

View File

@ -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'];

View File

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

View File

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

View File

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