diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index cd6d39795..dabb4c0cd 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -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: {
diff --git a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js
index dd2525f43..f970247e5 100644
--- a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js
+++ b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js
@@ -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');
diff --git a/e2e/paths/05-ticket/20_moveExpedition.spec.js b/e2e/paths/05-ticket/20_moveExpedition.spec.js
new file mode 100644
index 000000000..cf1c5ded3
--- /dev/null
+++ b/e2e/paths/05-ticket/20_moveExpedition.spec.js
@@ -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);
+ });
+});
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index 626e35b5b..8c6f2cac8 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -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"
diff --git a/modules/ticket/back/methods/expedition/deleteExpeditions.js b/modules/ticket/back/methods/expedition/deleteExpeditions.js
new file mode 100644
index 000000000..2419d3a5e
--- /dev/null
+++ b/modules/ticket/back/methods/expedition/deleteExpeditions.js
@@ -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;
+ }
+ };
+};
diff --git a/modules/ticket/back/methods/expedition/moveExpeditions.js b/modules/ticket/back/methods/expedition/moveExpeditions.js
new file mode 100644
index 000000000..cef35ab86
--- /dev/null
+++ b/modules/ticket/back/methods/expedition/moveExpeditions.js
@@ -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;
+ }
+ };
+};
diff --git a/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js
new file mode 100644
index 000000000..14bdf7aea
--- /dev/null
+++ b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js
@@ -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;
+ }
+ });
+});
+
diff --git a/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js
new file mode 100644
index 000000000..67919e76c
--- /dev/null
+++ b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js
@@ -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;
+ }
+ });
+});
+
diff --git a/modules/ticket/back/models/expedition.js b/modules/ticket/back/models/expedition.js
index 9d6564373..46cde6890 100644
--- a/modules/ticket/back/models/expedition.js
+++ b/modules/ticket/back/models/expedition.js
@@ -1,3 +1,5 @@
module.exports = function(Self) {
require('../methods/expedition/filter')(Self);
+ require('../methods/expedition/deleteExpeditions')(Self);
+ require('../methods/expedition/moveExpeditions')(Self);
};
diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html
index a41d368f6..ec6dc7ee2 100644
--- a/modules/ticket/front/expedition/index.html
+++ b/modules/ticket/front/expedition/index.html
@@ -8,54 +8,77 @@
auto-load="true">
-
-
-
-
-
- Expedition
- Item
- Name
- Package type
- Counter
- externalId
- Created
- State
-
-
-
-
-
-
-
-
-
- {{expedition.id | zeroFill:6}}
-
-
- {{expedition.packagingFk}}
-
-
- {{::expedition.packageItemName}}
- {{::expedition.freightItemName}}
- {{::expedition.counter}}
- {{::expedition.externalId}}
- {{::expedition.created | date:'dd/MM/yyyy HH:mm'}}
- {{::expedition.state}}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Expedition
+ Item
+ Name
+ Package type
+ Counter
+ externalId
+ Created
+ State
+
+
+
+
+
+
+
+
+
+ {{expedition.id | zeroFill:6}}
+
+
+ {{expedition.packagingFk}}
+
+
+ {{::expedition.packageItemName}}
+ {{::expedition.freightItemName}}
+ {{::expedition.counter}}
+ {{::expedition.externalId}}
+ {{::expedition.created | date:'dd/MM/yyyy HH:mm'}}
+ {{::expedition.state}}
+
+
+
+
+
+
+
-
+ on-accept="$ctrl.onRemove()">
-
+
-
+
State
@@ -111,4 +134,37 @@
-
\ No newline at end of file
+
+
+
+
+ New ticket without route
+
+
+ New ticket with route
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js
index 120d89bb2..7ffe2fe5e 100644
--- a/modules/ticket/front/expedition/index.js
+++ b/modules/ticket/front/expedition/index.js
@@ -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', {
diff --git a/modules/ticket/front/expedition/index.spec.js b/modules/ticket/front/expedition/index.spec.js
index 586ef2109..b95d64fa3 100644
--- a/modules/ticket/front/expedition/index.spec.js
+++ b/modules/ticket/front/expedition/index.spec.js
@@ -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});
+ });
+ });
});
});
diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml
index d23cf25af..278dcc8f2 100644
--- a/modules/ticket/front/expedition/locale/es.yml
+++ b/modules/ticket/front/expedition/locale/es.yml
@@ -1 +1,6 @@
-Status log: Hitorial de estados
\ No newline at end of file
+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
\ No newline at end of file