#972 ticket.tracking

This commit is contained in:
Gerard 2019-01-22 10:04:42 +01:00
parent 5a53195574
commit 87a0a0bccc
12 changed files with 220 additions and 49 deletions

View File

@ -386,7 +386,8 @@ export default {
trackingButton: `vn-left-menu a[ui-sref="ticket.card.tracking.index"]`, trackingButton: `vn-left-menu a[ui-sref="ticket.card.tracking.index"]`,
createStateButton: `${components.vnFloatButton}`, createStateButton: `${components.vnFloatButton}`,
stateAutocomplete: 'vn-ticket-tracking-edit vn-autocomplete[field="$ctrl.ticket.stateFk"]', stateAutocomplete: 'vn-ticket-tracking-edit vn-autocomplete[field="$ctrl.ticket.stateFk"]',
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`,
cancelButton: `vn-ticket-tracking-edit vn-button[ui-sref="ticket.card.tracking.index"]`
}, },
ticketBasicData: { ticketBasicData: {
basicDataButton: `vn-left-menu a[ui-sref="ticket.card.data.stepOne"]`, basicDataButton: `vn-left-menu a[ui-sref="ticket.card.data.stepOne"]`,
@ -437,8 +438,8 @@ export default {
saveServiceButton: `${components.vnSubmit}` saveServiceButton: `${components.vnSubmit}`
}, },
createStateView: { createStateView: {
stateAutocomplete: `vn-autocomplete[field="$ctrl.ticket.stateFk"]`, stateAutocomplete: `vn-autocomplete[field="$ctrl.stateFk"]`,
clearStateInputButton: `vn-autocomplete[field="$ctrl.ticket.stateFk"] > div > div > div > vn-icon > i`, clearStateInputButton: `vn-autocomplete[field="$ctrl.stateFk"] > div > div > div > vn-icon > i`,
saveStateButton: `${components.vnSubmit}` saveStateButton: `${components.vnSubmit}`
}, },
claimsIndex: { claimsIndex: {

View File

@ -25,27 +25,19 @@ describe('Ticket Create new tracking state path', () => {
.click(selectors.createStateView.saveStateButton) .click(selectors.createStateView.saveStateButton)
.waitForLastSnackbar(); .waitForLastSnackbar();
expect(result).toEqual('No changes to save'); expect(result).toEqual('State cannot be blank');
}); });
it(`should attempt create a new state then clear and save it`, async() => { it(`should attempt create a new state then clear and save it`, async() => {
let result = await nightmare let result = await nightmare
.autocompleteSearch(selectors.createStateView.stateAutocomplete, '¿Fecha?') .autocompleteSearch(selectors.createStateView.stateAutocomplete, '¿Fecha?')
.waitToClick(selectors.createStateView.clearStateInputButton) .waitToClick(selectors.createStateView.clearStateInputButton)
.click(selectors.createStateView.saveStateButton) .waitToClick(selectors.createStateView.saveStateButton)
.waitForLastSnackbar(); .waitForLastSnackbar();
expect(result).toEqual('Data saved!'); expect(result).toEqual('State cannot be blank');
}); });
it('should again access to the create state view by clicking the create floating button', async() => {
let url = await nightmare
.click(selectors.ticketTracking.createStateButton)
.wait(selectors.createStateView.stateAutocomplete)
.parsedUrl();
expect(url.hash).toContain('tracking/edit');
});
it(`should create a new state`, async() => { it(`should create a new state`, async() => {
let result = await nightmare let result = await nightmare

View File

@ -18,6 +18,7 @@
"That payment method requires an IBAN": "El método de pago seleccionado requiere un IBAN", "That payment method requires an IBAN": "El método de pago seleccionado requiere un IBAN",
"That payment method requires a BIC": "El método de pago seleccionado requiere un BIC", "That payment method requires a BIC": "El método de pago seleccionado requiere un BIC",
"State cannot be blank": "El estado no puede estar en blanco", "State cannot be blank": "El estado no puede estar en blanco",
"Worker cannot be blank": "El trabajador no puede estar en blanco",
"Cannot change the payment method if no salesperson": "No se puede cambiar la forma de pago si no hay comercial asignado", "Cannot change the payment method if no salesperson": "No se puede cambiar la forma de pago si no hay comercial asignado",
"can't be blank": "El campo no puede estar vacío", "can't be blank": "El campo no puede estar vacío",
"Observation type cannot be blank": "El tipo de observación no puede estar en blanco", "Observation type cannot be blank": "El tipo de observación no puede estar en blanco",

View File

@ -25,18 +25,25 @@ module.exports = Self => {
let models = Self.app.models; let models = Self.app.models;
let isProduction; let isProduction;
let isEditable = await Self.app.models.Ticket.isEditable(params.ticketFk); let isEditable = await Self.app.models.Ticket.isEditable(params.ticketFk);
let assignedState = await Self.app.models.State.findOne({where: {code: 'PICKER_DESIGNED'}});
let isAssigned = assignedState.id === params.stateFk;
let currentUserId;
if (ctx.req.accessToken) { if (ctx.req.accessToken) {
let token = ctx.req.accessToken; let token = ctx.req.accessToken;
let currentUserId = token && token.userId; currentUserId = token && token.userId;
isProduction = await models.Account.hasRole(currentUserId, 'Production'); isProduction = await models.Account.hasRole(currentUserId, 'production');
isSalesperson = await models.Account.hasRole(currentUserId, 'salesPerson');
}
if ((!isEditable && !isProduction) || (isEditable && !isAssigned && isSalesperson) || (!isSalesperson && !isProduction))
throw new UserError(`You don't have enough privileges to change the state of this ticket`);
if (!isAssigned) {
let worker = await models.Worker.findOne({where: {userFk: currentUserId}}); let worker = await models.Worker.findOne({where: {userFk: currentUserId}});
params.workerFk = worker.id; params.workerFk = worker.id;
} }
if (!isEditable && !isProduction)
throw new UserError(`You don't have enough privileges to change the state of this ticket`);
return await Self.app.models.TicketTracking.create(params); return await Self.app.models.TicketTracking.create(params);
}; };
}; };

View File

@ -3,18 +3,18 @@ const app = require(`${serviceRoot}/server/server`);
describe('ticket changeState()', () => { describe('ticket changeState()', () => {
let ticket; let ticket;
beforeAll(async () => { beforeAll(async() => {
let originalTicket = await app.models.Ticket.findOne({where: {id: 16}}); let originalTicket = await app.models.Ticket.findOne({where: {id: 16}});
originalTicket.id = null; originalTicket.id = null;
ticket = await app.models.Ticket.create(originalTicket); ticket = await app.models.Ticket.create(originalTicket);
}); });
afterAll(async () => { afterAll(async() => {
await app.models.Ticket.destroyById(ticket.id); await app.models.Ticket.destroyById(ticket.id);
}); });
it('should throw an error if the ticket is not editable and the user isnt production', async () => { it('should throw an error if the ticket is not editable and the user isnt production', async() => {
let ctx = {req: {accessToken: {userId: 110}}}; let ctx = {req: {accessToken: {userId: 18}}};
let params = {ticketFk: 2, stateFk: 3}; let params = {ticketFk: 2, stateFk: 3};
let error; let error;
try { try {
@ -26,26 +26,66 @@ describe('ticket changeState()', () => {
expect(error).toEqual(new Error(`You don't have enough privileges to change the state of this ticket`)); expect(error).toEqual(new Error(`You don't have enough privileges to change the state of this ticket`));
}); });
it('should be able to create a ticket tracking line for a not editable ticket if the user has the production role', async () => { it('should throw an error if the state is assigned and theres not worker in params', async() => {
let ctx = {req: {accessToken: {userId: 50}}}; let ctx = {req: {accessToken: {userId: 18}}};
let params = {ticketFk: 20, stateFk: 3}; let assignedState = await app.models.State.findOne({where: {code: 'PICKER_DESIGNED'}});
let params = {ticketFk: 11, stateFk: assignedState.id};
let error;
try {
await app.models.TicketTracking.changeState(ctx, params);
} catch (e) {
error = e;
}
expect(error.message).toEqual('La instancia `TicketTracking` no es válida. Detalles: `workerFk` Worker cannot be blank (value: undefined).');
});
it('should throw an error if a worker thats not production tries to change the state to one thats not assigned', async() => {
let ctx = {req: {accessToken: {userId: 110}}};
let params = {ticketFk: 11, stateFk: 3};
let error;
try {
await app.models.TicketTracking.changeState(ctx, params);
} catch (e) {
error = e;
}
expect(error.message).toEqual(`You don't have enough privileges to change the state of this ticket`);
});
it('should be able to create a ticket tracking line for a not editable ticket if the user has the production role', async() => {
let ctx = {req: {accessToken: {userId: 49}}};
let params = {ticketFk: ticket.id, stateFk: 3};
let res = await app.models.TicketTracking.changeState(ctx, params); let res = await app.models.TicketTracking.changeState(ctx, params);
expect(res.__data.ticketFk).toBe(params.ticketFk); expect(res.__data.ticketFk).toBe(params.ticketFk);
expect(res.__data.stateFk).toBe(params.stateFk); expect(res.__data.stateFk).toBe(params.stateFk);
expect(res.__data.workerFk).toBe(50); expect(res.__data.workerFk).toBe(49);
expect(res.__data.id).toBeDefined(); expect(res.__data.id).toBeDefined();
}); });
it('return an array with the created ticket tracking line', async () => { it('return an array with the created ticket tracking line', async() => {
let ctx = {req: {accessToken: {userId: 108}}}; let ctx = {req: {accessToken: {userId: 49}}};
let params = {ticketFk: ticket.id, stateFk: 3}; let params = {ticketFk: ticket.id, stateFk: 3};
let res = await app.models.TicketTracking.changeState(ctx, params); let res = await app.models.TicketTracking.changeState(ctx, params);
expect(res.__data.ticketFk).toBe(params.ticketFk); expect(res.__data.ticketFk).toBe(params.ticketFk);
expect(res.__data.stateFk).toBe(params.stateFk); expect(res.__data.stateFk).toBe(params.stateFk);
expect(res.__data.workerFk).toBe(110); expect(res.__data.workerFk).toBe(49);
expect(res.__data.id).toBeDefined();
});
it('return an array with the created ticket tracking line when the user is salesperson, uses the state assigned and thes a workerFk given', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let assignedState = await app.models.State.findOne({where: {code: 'PICKER_DESIGNED'}});
let params = {ticketFk: ticket.id, stateFk: assignedState.id, workerFk: 1};
let res = await app.models.TicketTracking.changeState(ctx, params);
expect(res.__data.ticketFk).toBe(params.ticketFk);
expect(res.__data.stateFk).toBe(params.stateFk);
expect(res.__data.workerFk).toBe(params.workerFk);
expect(res.__data.workerFk).toBe(1);
expect(res.__data.id).toBeDefined(); expect(res.__data.id).toBeDefined();
}); });
}); });

View File

@ -2,4 +2,5 @@ module.exports = function(Self) {
require('../methods/ticket-tracking/changeState')(Self); require('../methods/ticket-tracking/changeState')(Self);
Self.validatesPresenceOf('stateFk', {message: 'State cannot be blank'}); Self.validatesPresenceOf('stateFk', {message: 'State cannot be blank'});
Self.validatesPresenceOf('workerFk', {message: 'Worker cannot be blank'});
}; };

View File

@ -142,7 +142,7 @@
"params": { "params": {
"ticket": "$ctrl.ticket" "ticket": "$ctrl.ticket"
}, },
"acl": ["production", "administrative"] "acl": ["production", "administrative", "salesPerson"]
}, },
{ {
"url" : "/sale-checked", "url" : "/sale-checked",

View File

@ -1,9 +1,8 @@
<mg-ajax path="/ticket/api/TicketTrackings/changeState" options="vnPost"></mg-ajax> <mg-ajax path="/api/TicketTrackings/changeState" options="vnPost"></mg-ajax>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="$ctrl.ticket" data="$ctrl.params"
form="form" form="form">
save="post">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" compact> <form name="form" ng-submit="$ctrl.onSubmit()" compact>
<vn-card pad-large> <vn-card pad-large>
@ -11,11 +10,23 @@
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
field="$ctrl.ticket.stateFk" field="$ctrl.stateFk"
url="/ticket/api/States" url="/ticket/api/States"
label="State" label="State"
vn-focus> vn-focus>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete
vn-one
ng-if="$ctrl.isPickerDesignedState"
field="$ctrl.workerFk"
url="/client/api/Clients/activeWorkersWithRole"
show-field="firstName"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Worker">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>

View File

@ -1,7 +1,8 @@
import ngModule from '../../module'; import ngModule from '../../module';
class Controller { class Controller {
constructor($scope, $state, vnApp, $translate) { constructor($scope, $state, vnApp, $translate, $http) {
this.$http = $http;
this.$ = $scope; this.$ = $scope;
this.$state = $state; this.$state = $state;
this.vnApp = vnApp; this.vnApp = vnApp;
@ -9,17 +10,60 @@ class Controller {
this.ticket = { this.ticket = {
ticketFk: $state.params.id ticketFk: $state.params.id
}; };
this.params = {ticketFk: $state.params.id};
} }
$onInit() {
this.getPickerDesignedState();
}
set stateFk(value) {
this.params.stateFk = value;
this.isPickerDesignedState = this.getIsPickerDesignedState(value);
}
get stateFk() {
return this.params.stateFk;
}
set workerFk(value) {
this.params.workerFk = value;
}
get workerFk() {
return this.params.workerFk;
}
getPickerDesignedState() {
let filter = {
where: {
code: 'PICKER_DESIGNED'
}
};
let json = encodeURIComponent(JSON.stringify(filter));
this.$http.get(`/api/States?filter=${json}`).then(res => {
if (res && res.data)
this.pickerDesignedState = res.data[0].id;
});
}
getIsPickerDesignedState(value) {
if (value == this.pickerDesignedState)
return true;
return false;
}
onSubmit() { onSubmit() {
this.$.watcher.submit().then( this.$http.post(`/api/TicketTrackings/changeState`, this.params).then(() => {
() => { this.$.watcher.updateOriginalData();
this.card.reload(); this.card.reload();
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$state.go('ticket.card.tracking.index'); this.$state.go('ticket.card.tracking.index');
} });
);
} }
} }
Controller.$inject = ['$scope', '$state', 'vnApp', '$translate']; Controller.$inject = ['$scope', '$state', 'vnApp', '$translate', '$http'];
ngModule.component('vnTicketTrackingEdit', { ngModule.component('vnTicketTrackingEdit', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -0,0 +1,74 @@
import './index';
describe('Ticket', () => {
describe('Component vnTicketTrackingEdit', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('ticket'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $translate, vnApp) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnTicketTrackingEdit');
controller.ticket = {id: 1};
controller.$ = {watcher: {updateOriginalData: () => {}}};
controller.card = {reload: () => {}};
controller.vnApp = {showSuccess: () => {}};
controller.$translate = $translate;
controller.vnApp = vnApp;
}));
describe('stateFk setter/getter', () => {
it('should set params.stateFk and set isPickerDesignedState', () => {
let stateFk = {id: 1};
controller.stateFk = stateFk;
expect(controller.params.stateFk).toEqual(stateFk);
expect(controller.isPickerDesignedState).toEqual(false);
});
});
describe('workerFk setter', () => {
it('should set params.workerFk', () => {
controller.workerFk = 1;
expect(controller.params.workerFk).toEqual(1);
});
});
describe('getPickerDesignedState()', () => {
it('should get the state that has the code PICKER_DESIGNED', () => {
let filter = {
where: {
code: 'PICKER_DESIGNED'
}
};
let json = encodeURIComponent(JSON.stringify(filter));
$httpBackend.expectGET(`/api/States?filter=${json}`).respond([{id: 22}]);
controller.getPickerDesignedState();
$httpBackend.flush();
expect(controller.pickerDesignedState).toEqual(22);
});
});
describe('onSubmit()', () => {
it('should POST the data, call updateOriginalData, reload, showSuccess and go functions', () => {
controller.params = {stateFk: 22, workerFk: 101};
spyOn(controller.card, 'reload');
spyOn(controller.$.watcher, 'updateOriginalData');
spyOn(controller.vnApp, 'showSuccess');
spyOn(controller.$state, 'go');
$httpBackend.expectPOST(`/api/TicketTrackings/changeState`, controller.params).respond({});
controller.onSubmit();
$httpBackend.flush();
expect(controller.card.reload).toHaveBeenCalledWith();
expect(controller.$.watcher.updateOriginalData).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith(controller.$translate.instant('Data saved!'));
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.tracking.index');
});
});
});
});

View File

@ -34,6 +34,6 @@
<vn-pagination model="model"></vn-pagination> <vn-pagination model="model"></vn-pagination>
</vn-card> </vn-card>
</vn-vertical> </vn-vertical>
<a ui-sref="ticket.card.tracking.edit" vn-bind="+" vn-visible-by="production, administrative" fixed-bottom-right> <a ui-sref="ticket.card.tracking.edit" vn-bind="+" vn-visible-by="production, administrative, salesperson" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button> <vn-float-button icon="add"></vn-float-button>
</a> </a>

View File

@ -6,15 +6,15 @@ class Controller {
this.filter = { this.filter = {
include: [ include: [
{ {
relation: "worker", relation: 'worker',
scope: { scope: {
fields: ["firstName", "name"] fields: ['firstName', 'name']
} }
}, },
{ {
relation: "state", relation: 'state',
scope: { scope: {
fields: ["name"] fields: ['name']
} }
} }
] ]