3888-ticket.expedition_moveExpedition #1097
|
@ -596,7 +596,14 @@ export default {
|
|||
submitNotesButton: 'button[type=submit]'
|
||||
},
|
||||
ticketExpedition: {
|
||||
thirdExpeditionRemoveButton: 'vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(3) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]',
|
||||
firstSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(1) vn-check[ng-model="expedition.checked"]',
|
||||
thirdSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(3) vn-check[ng-model="expedition.checked"]',
|
||||
deleteExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="delete"]',
|
||||
moveExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="keyboard_arrow_down"]',
|
||||
moreMenuWithoutRoute: 'vn-item[name="withoutRoute"]',
|
||||
moreMenuWithRoute: 'vn-item[name="withRoute"]',
|
||||
newRouteId: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.newRoute"]',
|
||||
saveButton: '.vn-dialog.shown [response="accept"]',
|
||||
expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr'
|
||||
},
|
||||
ticketPackages: {
|
||||
|
|
|
@ -18,7 +18,8 @@ describe('Ticket expeditions and log path', () => {
|
|||
});
|
||||
|
||||
it(`should delete a former expedition and confirm the remaining expedition are the expected ones`, async() => {
|
||||
await page.waitToClick(selectors.ticketExpedition.thirdExpeditionRemoveButton);
|
||||
await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox);
|
||||
await page.waitToClick(selectors.ticketExpedition.deleteExpeditionButton);
|
||||
await page.waitToClick(selectors.globalItems.acceptButton);
|
||||
await page.reloadSection('ticket.card.expedition');
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('Ticket expeditions', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
page = browser.page;
|
||||
await page.loginAndModule('production', 'ticket');
|
||||
await page.accessToSearchResult('1');
|
||||
await page.accessToSection('ticket.card.expedition');
|
||||
});
|
||||
|
||||
afterAll(async() => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it(`should move one expedition to new ticket withoute route`, async() => {
|
||||
await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox);
|
||||
await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton);
|
||||
await page.waitToClick(selectors.ticketExpedition.moreMenuWithoutRoute);
|
||||
await page.waitToClick(selectors.ticketExpedition.saveButton);
|
||||
await page.waitForState('ticket.card.summary');
|
||||
await page.accessToSection('ticket.card.expedition');
|
||||
|
||||
await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {});
|
||||
const result = await page
|
||||
.countElement(selectors.ticketExpedition.expeditionRow);
|
||||
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
|
||||
it(`should move one expedition to new ticket with route`, async() => {
|
||||
await page.waitToClick(selectors.ticketExpedition.firstSaleCheckbox);
|
||||
await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton);
|
||||
await page.waitToClick(selectors.ticketExpedition.moreMenuWithRoute);
|
||||
await page.write(selectors.ticketExpedition.newRouteId, '1');
|
||||
await page.waitToClick(selectors.ticketExpedition.saveButton);
|
||||
await page.waitForState('ticket.card.summary');
|
||||
await page.accessToSection('ticket.card.expedition');
|
||||
|
||||
await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {});
|
||||
const result = await page
|
||||
.countElement(selectors.ticketExpedition.expeditionRow);
|
||||
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -236,6 +236,7 @@
|
|||
"Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador",
|
||||
"Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador",
|
||||
"Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente",
|
||||
"This route does not exists": "Esta ruta no existe",
|
||||
"Claim pickup order sent": "Reclamación Orden de recogida enviada [({{claimId}})]({{{claimUrl}}}) al cliente *{{clientName}}*",
|
||||
"You don't have grant privilege": "No tienes privilegios para dar privilegios",
|
||||
"You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('deleteExpeditions', {
|
||||
description: 'Delete the selected expeditions',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'expeditionIds',
|
||||
type: ['number'],
|
||||
required: true,
|
||||
description: 'The expeditions ids to delete'
|
||||
}],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/deleteExpeditions`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.deleteExpeditions = async(expeditionIds, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
let tx;
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const promises = [];
|
||||
for (let expeditionId of expeditionIds) {
|
||||
const deletedExpedition = models.Expedition.destroyById(expeditionId, myOptions);
|
||||
promises.push(deletedExpedition);
|
||||
}
|
||||
|
||||
const deletedExpeditions = await Promise.all(promises);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return deletedExpeditions;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('moveExpeditions', {
|
||||
description: 'Move the selected expeditions to another ticket',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'clientId',
|
||||
type: 'number',
|
||||
description: `The client id`,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'landed',
|
||||
type: 'date',
|
||||
description: `The landing date`
|
||||
},
|
||||
{
|
||||
arg: 'warehouseId',
|
||||
type: 'number',
|
||||
description: `The warehouse id`,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'addressId',
|
||||
type: 'number',
|
||||
description: `The address id`,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'agencyModeId',
|
||||
type: 'any',
|
||||
description: `The agencyMode id`
|
||||
},
|
||||
{
|
||||
arg: 'routeId',
|
||||
type: 'any',
|
||||
description: `The route id`
|
||||
},
|
||||
{
|
||||
arg: 'expeditionIds',
|
||||
type: ['number'],
|
||||
required: true,
|
||||
description: 'The expeditions ids to move'
|
||||
}],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/moveExpeditions`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.moveExpeditions = async(ctx, options) => {
|
||||
const models = Self.app.models;
|
||||
const args = ctx.args;
|
||||
const myOptions = {};
|
||||
let tx;
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
if (args.routeId) {
|
||||
const route = await models.Route.findById(args.routeId, null, myOptions);
|
||||
if (!route) throw new UserError('This route does not exists');
|
||||
}
|
||||
const ticket = await models.Ticket.new(ctx, myOptions);
|
||||
const promises = [];
|
||||
for (let expeditionsId of args.expeditionIds) {
|
||||
const expeditionToUpdate = await models.Expedition.findById(expeditionsId, null, myOptions);
|
||||
const expeditionUpdated = expeditionToUpdate.updateAttribute('ticketFk', ticket.id, myOptions);
|
||||
promises.push(expeditionUpdated);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return ticket;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('ticket deleteExpeditions()', () => {
|
||||
it('should delete the selected expeditions', async() => {
|
||||
const tx = await models.Expedition.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const expeditionIds = [12, 13];
|
||||
const result = await models.Expedition.deleteExpeditions(expeditionIds, options);
|
||||
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('ticket moveExpeditions()', () => {
|
||||
it('should move the selected expeditions to new ticket', async() => {
|
||||
const tx = await models.Expedition.beginTransaction({});
|
||||
const ctx = {
|
||||
req: {accessToken: {userId: 9}},
|
||||
args: {},
|
||||
params: {}
|
||||
};
|
||||
const myCtx = Object.assign({}, ctx);
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
myCtx.args = {
|
||||
clientId: 1101,
|
||||
landed: new Date(),
|
||||
warehouseId: 1,
|
||||
addressId: 121,
|
||||
agencyModeId: 1,
|
||||
routeId: null,
|
||||
expeditionIds: [1, 2]
|
||||
|
||||
};
|
||||
|
||||
const ticket = await models.Expedition.moveExpeditions(myCtx, options);
|
||||
|
||||
const newestTicketIdInFixtures = 27;
|
||||
|
||||
expect(ticket.id).toBeGreaterThan(newestTicketIdInFixtures);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
module.exports = function(Self) {
|
||||
require('../methods/expedition/filter')(Self);
|
||||
require('../methods/expedition/deleteExpeditions')(Self);
|
||||
require('../methods/expedition/moveExpeditions')(Self);
|
||||
};
|
||||
|
|
|
@ -8,11 +8,35 @@
|
|||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-data-viewer model="model">
|
||||
<vn-card class="vn-w-xl">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal class="header">
|
||||
<vn-tool-bar class="vn-mb-md">
|
||||
<vn-button icon="keyboard_arrow_down"
|
||||
label="Move"
|
||||
ng-click="moreOptions.show($event)"
|
||||
disabled="!$ctrl.totalChecked">
|
||||
</vn-button>
|
||||
<vn-button
|
||||
disabled="!$ctrl.checked.length"
|
||||
ng-click="removeConfirm.show()"
|
||||
icon="delete"
|
||||
vn-tooltip="Delete expedition">
|
||||
</vn-button>
|
||||
</vn-tool-bar>
|
||||
<vn-one class="taxes" ng-if="$ctrl.sales.length > 0">
|
||||
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
|
||||
<p><vn-label translate>VAT</vn-label> {{$ctrl.ticket.totalWithVat - $ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
|
||||
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.ticket.totalWithVat | currency: 'EUR':2}}</strong></p>
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th></vn-th>
|
||||
<vn-th shrink>
|
||||
<vn-multi-check
|
||||
model="model">
|
||||
</vn-multi-check>
|
||||
</vn-th>
|
||||
<vn-th field="itemFk" number>Expedition</vn-th>
|
||||
<vn-th field="itemFk" number>Item</vn-th>
|
||||
<vn-th field="packageItemName">Name</vn-th>
|
||||
|
@ -26,11 +50,10 @@
|
|||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="expedition in expeditions">
|
||||
<vn-td class="vn-px-md" style="width:30px; color:#FFA410;">
|
||||
<vn-icon-button icon="delete"
|
||||
ng-click="deleteExpedition.show(expedition.id)"
|
||||
vn-tooltip="Delete expedition">
|
||||
</vn-icon-button>
|
||||
<vn-td shrink>
|
||||
<vn-check tabindex="-1"
|
||||
ng-model="expedition.checked">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td number expand>{{expedition.id | zeroFill:6}}</vn-td>
|
||||
<vn-td number>
|
||||
|
@ -67,24 +90,24 @@
|
|||
vn-id="worker-descriptor">
|
||||
</vn-worker-descriptor-popover>
|
||||
<vn-confirm
|
||||
vn-id="delete-expedition"
|
||||
on-accept="$ctrl.onDialogAccept($data)"
|
||||
vn-id="removeConfirm"
|
||||
message="Are you sure you want to delete this expedition?"
|
||||
question="Delete expedition"
|
||||
message="Are you sure you want to delete this expedition?">
|
||||
on-accept="$ctrl.onRemove()">
|
||||
</vn-confirm>
|
||||
|
||||
<vn-popup vn-id="statusLog">
|
||||
<vn-crud-model
|
||||
vn-id="model"
|
||||
vn-id="modelExpeditionStates"
|
||||
url="ExpeditionStates/filter"
|
||||
link="{expeditionFk: $ctrl.expedition.id}"
|
||||
data="expeditionStates"
|
||||
order="created DESC"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-data-viewer model="model">
|
||||
<vn-data-viewer model="modelExpeditionStates">
|
||||
<vn-card class="vn-w-md">
|
||||
<vn-table model="model">
|
||||
<vn-table model="modelExpeditionStates">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="state">State</vn-th>
|
||||
|
@ -112,3 +135,36 @@
|
|||
vn-id="workerDescriptor">
|
||||
</vn-worker-descriptor-popover>
|
||||
</vn-popup>
|
||||
|
||||
<vn-menu vn-id="moreOptions">
|
||||
<vn-item translate
|
||||
name="withoutRoute"
|
||||
ng-click="selectLanded.show('withoutRoute')">
|
||||
New ticket without route
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
name="withRoute"
|
||||
ng-click="selectLanded.show('withRoute')">
|
||||
New ticket with route
|
||||
</vn-item>
|
||||
</vn-menu>
|
||||
|
||||
<vn-dialog
|
||||
vn-id="selectLanded"
|
||||
on-accept="$ctrl.createTicket($ctrl.landed, $ctrl.newRoute)">
|
||||
<tpl-body>
|
||||
<vn-date-picker
|
||||
label="Landed"
|
||||
ng-model="$ctrl.landed">
|
||||
</vn-date-picker>
|
||||
<vn-textfield
|
||||
ng-show="selectLanded.data == 'withRoute'"
|
||||
label="Route id"
|
||||
ng-model="$ctrl.newRoute">
|
||||
</vn-textfield>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="accept" translate>Accept</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
|
@ -2,6 +2,27 @@ import ngModule from '../module';
|
|||
import Section from 'salix/components/section';
|
||||
|
||||
class Controller extends Section {
|
||||
constructor($element, $scope) {
|
||||
super($element, $scope);
|
||||
this.landed = new Date();
|
||||
this.newRoute = null;
|
||||
}
|
||||
|
||||
get checked() {
|
||||
const rows = this.$.model.data || [];
|
||||
const checkedRows = [];
|
||||
for (let row of rows) {
|
||||
if (row.checked)
|
||||
checkedRows.push(row.id);
|
||||
}
|
||||
|
||||
return checkedRows;
|
||||
}
|
||||
|
||||
get totalChecked() {
|
||||
return this.checked.length;
|
||||
}
|
||||
|
||||
onDialogAccept(id) {
|
||||
return this.$http.delete(`Expeditions/${id}`)
|
||||
.then(() => this.$.model.refresh());
|
||||
|
@ -11,6 +32,33 @@ class Controller extends Section {
|
|||
this.expedition = expedition;
|
||||
this.$.statusLog.show();
|
||||
}
|
||||
|
||||
onRemove() {
|
||||
const params = {expeditionIds: this.checked};
|
||||
const query = `Expeditions/deleteExpeditions`;
|
||||
this.$http.post(query, params)
|
||||
.then(() => {
|
||||
this.vnApp.showSuccess(this.$t('Expedition removed'));
|
||||
this.$.model.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
createTicket(landed, routeFk) {
|
||||
const params = {
|
||||
clientId: this.ticket.clientFk,
|
||||
landed: landed,
|
||||
warehouseId: this.ticket.warehouseFk,
|
||||
addressId: this.ticket.addressFk,
|
||||
agencyModeId: this.ticket.agencyModeFk,
|
||||
routeId: routeFk,
|
||||
expeditionIds: this.checked
|
||||
};
|
||||
const query = `Expeditions/moveExpeditions`;
|
||||
this.$http.post(query, params).then(res => {
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
this.$state.go('ticket.card.summary', {id: res.data.id});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.vnComponent('vnTicketExpedition', {
|
||||
|
|
|
@ -17,6 +17,14 @@ describe('Ticket', () => {
|
|||
refresh: () => {}
|
||||
};
|
||||
controller = $componentController('vnTicketExpedition', {$element: null, $scope});
|
||||
controller.$.model.data = [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
];
|
||||
const modelData = controller.$.model.data;
|
||||
modelData[0].checked = true;
|
||||
modelData[1].checked = true;
|
||||
}));
|
||||
|
||||
describe('onDialogAccept()', () => {
|
||||
|
@ -50,5 +58,51 @@ describe('Ticket', () => {
|
|||
expect(controller.$.statusLog.show).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onRemove()', () => {
|
||||
it('should make a query and then call to the model refresh() method', () => {
|
||||
jest.spyOn($scope.model, 'refresh');
|
||||
|
||||
const expectedParams = {expeditionIds: [1, 2]};
|
||||
$httpBackend.expect('POST', 'Expeditions/deleteExpeditions', expectedParams).respond(200);
|
||||
controller.onRemove();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect($scope.model.refresh).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTicket()', () => {
|
||||
it('should make a query and then call to the $state go() method', () => {
|
||||
jest.spyOn(controller.$state, 'go').mockReturnThis();
|
||||
|
||||
const ticket = {
|
||||
clientFk: 1101,
|
||||
landed: new Date(),
|
||||
addressFk: 121,
|
||||
agencyModeFk: 1,
|
||||
warehouseFk: 1
|
||||
};
|
||||
const routeId = null;
|
||||
controller.ticket = ticket;
|
||||
|
||||
const ticketToTransfer = {id: 28};
|
||||
|
||||
const expectedParams = {
|
||||
clientId: 1101,
|
||||
landed: new Date(),
|
||||
warehouseId: 1,
|
||||
addressId: 121,
|
||||
agencyModeId: 1,
|
||||
routeId: null,
|
||||
expeditionIds: [1, 2]
|
||||
};
|
||||
$httpBackend.expect('POST', 'Expeditions/moveExpeditions', expectedParams).respond(ticketToTransfer);
|
||||
controller.createTicket(ticket.landed, routeId);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.summary', {id: ticketToTransfer.id});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1 +1,6 @@
|
|||
Status log: Hitorial de estados
|
||||
Expedition removed: Expedición eliminada
|
||||
Move: Mover
|
||||
New ticket without route: Nuevo ticket sin ruta
|
||||
New ticket with route: Nuevo ticket con ruta
|
||||
Route id: Id ruta
|
Loading…
Reference in New Issue