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 66df3e9e6..f970247e5 100644
--- a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js
+++ b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js
@@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
-fdescribe('Ticket expeditions and log path', () => {
+describe('Ticket expeditions and log path', () => {
let browser;
let page;
diff --git a/e2e/paths/05-ticket/06_basic_data_steps.spec.js b/e2e/paths/05-ticket/06_basic_data_steps.spec.js
index 46cbf29b8..fa901e325 100644
--- a/e2e/paths/05-ticket/06_basic_data_steps.spec.js
+++ b/e2e/paths/05-ticket/06_basic_data_steps.spec.js
@@ -104,7 +104,6 @@ describe('Ticket Edit basic data path', () => {
await page.waitToClick(selectors.ticketBasicData.nextStepButton);
- await page.waitToClick(selectors.ticketBasicData.withoutNegatives);
await page.waitToClick(selectors.ticketBasicData.finalizeButton);
await page.waitForState('ticket.card.summary');
diff --git a/modules/item/back/methods/item/new.js b/modules/item/back/methods/item/new.js
index 0771b6b14..fae37836f 100644
--- a/modules/item/back/methods/item/new.js
+++ b/modules/item/back/methods/item/new.js
@@ -49,6 +49,10 @@ module.exports = Self => {
const provisionalName = params.provisionalName;
delete params.provisionalName;
+ const itemType = await models.ItemType.findById(params.typeFk, myOptions);
+
+ params.isLaid = itemType.isLaid;
+
const item = await models.Item.create(params, myOptions);
const typeTags = await models.ItemTypeTag.find({
diff --git a/modules/item/back/methods/item/specs/new.spec.js b/modules/item/back/methods/item/specs/new.spec.js
index 049ad0ff2..7364faa7d 100644
--- a/modules/item/back/methods/item/specs/new.spec.js
+++ b/modules/item/back/methods/item/specs/new.spec.js
@@ -15,6 +15,11 @@ describe('item new()', () => {
};
let item = await models.Item.new(itemParams, options);
+
+ let itemType = await models.ItemType.findById(item.typeFk, options);
+
+ item.isLaid = itemType.isLaid;
+
const temporalNameTag = await models.Tag.findOne({where: {name: 'Nombre temporal'}}, options);
const temporalName = await models.ItemTag.findOne({
@@ -26,9 +31,14 @@ describe('item new()', () => {
item = await models.Item.findById(item.id, null, options);
+ itemType = await models.ItemType.findById(item.typeFk, options);
+
+ item.isLaid = itemType.isLaid;
+
expect(item.intrastatFk).toEqual(5080000);
expect(item.originFk).toEqual(1);
expect(item.typeFk).toEqual(2);
+ expect(item.isLaid).toBeFalse();
expect(item.name).toEqual('planta');
expect(temporalName.value).toEqual('planta');
diff --git a/modules/item/back/models/item-type.json b/modules/item/back/models/item-type.json
index 74cdf3fc8..c5c920b2f 100644
--- a/modules/item/back/models/item-type.json
+++ b/modules/item/back/models/item-type.json
@@ -26,6 +26,9 @@
},
"isUnconventionalSize": {
"type": "number"
+ },
+ "isLaid": {
+ "type": "boolean"
}
},
"relations": {
diff --git a/modules/item/back/models/item.json b/modules/item/back/models/item.json
index 704c97434..2f58c30a9 100644
--- a/modules/item/back/models/item.json
+++ b/modules/item/back/models/item.json
@@ -143,6 +143,9 @@
},
"packingShelve": {
"type": "number"
+ },
+ "isLaid": {
+ "type": "boolean"
}
},
"relations": {
diff --git a/modules/route/back/methods/route/sendSms.js b/modules/route/back/methods/route/sendSms.js
new file mode 100644
index 000000000..d1c3efa35
--- /dev/null
+++ b/modules/route/back/methods/route/sendSms.js
@@ -0,0 +1,39 @@
+
+module.exports = Self => {
+ Self.remoteMethodCtx('sendSms', {
+ description: 'Sends a SMS to each client of the routes, each client only recieves the SMS once',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'destination',
+ type: 'string',
+ description: 'A comma separated string of destinations',
+ required: true,
+ },
+ {
+ arg: 'message',
+ type: 'string',
+ required: true,
+ }],
+ returns: {
+ type: 'object',
+ root: true
+ },
+ http: {
+ path: `/sendSms`,
+ verb: 'POST'
+ }
+ });
+
+ Self.sendSms = async(ctx, destination, message) => {
+ const targetClients = destination.split(',');
+
+ const allSms = [];
+ for (let client of targetClients) {
+ let sms = await Self.app.models.Sms.send(ctx, client, message);
+ allSms.push(sms);
+ }
+
+ return allSms;
+ };
+};
diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js
index f5406728a..08cabd30e 100644
--- a/modules/route/back/models/route.js
+++ b/modules/route/back/models/route.js
@@ -12,6 +12,7 @@ module.exports = Self => {
require('../methods/route/updateWorkCenter')(Self);
require('../methods/route/driverRoutePdf')(Self);
require('../methods/route/driverRouteEmail')(Self);
+ require('../methods/route/sendSms')(Self);
Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 1000'
diff --git a/modules/route/front/index.js b/modules/route/front/index.js
index 55cb745e1..c43048df5 100644
--- a/modules/route/front/index.js
+++ b/modules/route/front/index.js
@@ -15,3 +15,4 @@ import './agency-term/index';
import './agency-term/createInvoiceIn';
import './agency-term-search-panel';
import './ticket-popup';
+import './sms';
diff --git a/modules/route/front/index/locale/es.yml b/modules/route/front/index/locale/es.yml
index e4c5c8c55..3db84d81e 100644
--- a/modules/route/front/index/locale/es.yml
+++ b/modules/route/front/index/locale/es.yml
@@ -8,4 +8,6 @@ Hour finished: Hora fin
Go to route: Ir a la ruta
You must select a valid time: Debe seleccionar una hora válida
You must select a valid date: Debe seleccionar una fecha válida
-Mark as served: Marcar como servidas
\ No newline at end of file
+Mark as served: Marcar como servidas
+Retrieving data from the routes: Recuperando datos de las rutas
+Send SMS to all clients: Mandar sms a todos los clientes de las rutas
\ No newline at end of file
diff --git a/modules/route/front/sms/index.html b/modules/route/front/sms/index.html
new file mode 100644
index 000000000..0d7dd7c11
--- /dev/null
+++ b/modules/route/front/sms/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+ {{'Characters remaining' | translate}}:
+
+ {{$ctrl.charactersRemaining()}}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/route/front/sms/index.js b/modules/route/front/sms/index.js
new file mode 100644
index 000000000..d8b1fc134
--- /dev/null
+++ b/modules/route/front/sms/index.js
@@ -0,0 +1,47 @@
+import ngModule from '../module';
+import Component from 'core/lib/component';
+import './style.scss';
+
+class Controller extends Component {
+ open() {
+ this.$.SMSDialog.show();
+ }
+
+ charactersRemaining() {
+ const element = this.$.message;
+ const value = element.input.value;
+
+ const maxLength = 160;
+ const textAreaLength = new Blob([value]).size;
+ return maxLength - textAreaLength;
+ }
+
+ onResponse() {
+ try {
+ if (!this.sms.destination)
+ throw new Error(`The destination can't be empty`);
+ if (!this.sms.message)
+ throw new Error(`The message can't be empty`);
+ if (this.charactersRemaining() < 0)
+ throw new Error(`The message it's too long`);
+
+ this.$http.post(`Routes/sendSms`, this.sms).then(res => {
+ this.vnApp.showMessage(this.$t('SMS sent!'));
+
+ if (res.data) this.emit('send', {response: res.data});
+ });
+ } catch (e) {
+ this.vnApp.showError(this.$t(e.message));
+ return false;
+ }
+ return true;
+ }
+}
+
+ngModule.vnComponent('vnRouteSms', {
+ template: require('./index.html'),
+ controller: Controller,
+ bindings: {
+ sms: '<',
+ }
+});
diff --git a/modules/route/front/sms/index.spec.js b/modules/route/front/sms/index.spec.js
new file mode 100644
index 000000000..42bf30931
--- /dev/null
+++ b/modules/route/front/sms/index.spec.js
@@ -0,0 +1,71 @@
+import './index';
+
+describe('Route', () => {
+ describe('Component vnRouteSms', () => {
+ let controller;
+ let $httpBackend;
+
+ beforeEach(ngModule('route'));
+
+ beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
+ $httpBackend = _$httpBackend_;
+ let $scope = $rootScope.$new();
+ const $element = angular.element('');
+ controller = $componentController('vnRouteSms', {$element, $scope});
+ controller.$.message = {
+ input: {
+ value: 'My SMS'
+ }
+ };
+ }));
+
+ describe('onResponse()', () => {
+ it('should perform a POST query and show a success snackbar', () => {
+ let params = {destinationFk: 1101, destination: 111111111, message: 'My SMS'};
+ controller.sms = {destinationFk: 1101, destination: 111111111, message: 'My SMS'};
+
+ jest.spyOn(controller.vnApp, 'showMessage');
+ $httpBackend.expect('POST', `Routes/sendSms`, params).respond(200, params);
+
+ controller.onResponse();
+ $httpBackend.flush();
+
+ expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent!');
+ });
+
+ it('should call onResponse without the destination and show an error snackbar', () => {
+ controller.sms = {destinationFk: 1101, message: 'My SMS'};
+
+ jest.spyOn(controller.vnApp, 'showError');
+
+ controller.onResponse();
+
+ expect(controller.vnApp.showError).toHaveBeenCalledWith(`The destination can't be empty`);
+ });
+
+ it('should call onResponse without the message and show an error snackbar', () => {
+ controller.sms = {destinationFk: 1101, destination: 222222222};
+
+ jest.spyOn(controller.vnApp, 'showError');
+
+ controller.onResponse();
+
+ expect(controller.vnApp.showError).toHaveBeenCalledWith(`The message can't be empty`);
+ });
+ });
+
+ describe('charactersRemaining()', () => {
+ it('should return the characters remaining in a element', () => {
+ controller.$.message = {
+ input: {
+ value: 'My message 0€'
+ }
+ };
+
+ let result = controller.charactersRemaining();
+
+ expect(result).toEqual(145);
+ });
+ });
+ });
+});
diff --git a/modules/route/front/sms/locale/es.yml b/modules/route/front/sms/locale/es.yml
new file mode 100644
index 000000000..0168a6eb6
--- /dev/null
+++ b/modules/route/front/sms/locale/es.yml
@@ -0,0 +1,9 @@
+Send SMS to the selected tickets: Enviar SMS a los tickets seleccionados
+Routes to notify: Rutas a notificar
+Message: Mensaje
+SMS sent!: ¡SMS enviado!
+Characters remaining: Carácteres restantes
+The destination can't be empty: El destinatario no puede estar vacio
+The message can't be empty: El mensaje no puede estar vacio
+The message it's too long: El mensaje es demasiado largo
+Special characters like accents counts as a multiple: Carácteres especiales como los acentos cuentan como varios
\ No newline at end of file
diff --git a/modules/route/front/sms/style.scss b/modules/route/front/sms/style.scss
new file mode 100644
index 000000000..84571a5f4
--- /dev/null
+++ b/modules/route/front/sms/style.scss
@@ -0,0 +1,5 @@
+@import "variables";
+
+.SMSDialog {
+ min-width: 400px
+}
\ No newline at end of file
diff --git a/modules/route/front/tickets/index.html b/modules/route/front/tickets/index.html
index 1f91276e7..dae894ac7 100644
--- a/modules/route/front/tickets/index.html
+++ b/modules/route/front/tickets/index.html
@@ -29,13 +29,21 @@
disabled="!$ctrl.isChecked"
ng-click="$ctrl.deletePriority()"
vn-tooltip="Delete priority"
- icon="filter_alt_off">
+ icon="filter_alt">
+
+
@@ -149,19 +157,29 @@
route="$ctrl.$params"
parent-reload="$ctrl.$.model.refresh()">
-
-
-
+
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file
diff --git a/modules/route/front/tickets/index.js b/modules/route/front/tickets/index.js
index e78d9b8b7..80f8ad4f4 100644
--- a/modules/route/front/tickets/index.js
+++ b/modules/route/front/tickets/index.js
@@ -161,6 +161,37 @@ class Controller extends Section {
throw error;
});
}
+
+ async sendSms() {
+ try {
+ const clientsFk = [];
+ const clientsName = [];
+ const clients = [];
+
+ const selectedTickets = this.getSelectedItems(this.$.$ctrl.tickets);
+
+ for (let ticket of selectedTickets) {
+ clientsFk.push(ticket.clientFk);
+ let userContact = await this.$http.get(`Clients/${ticket.clientFk}`);
+ clientsName.push(userContact.data.name);
+ clients.push(userContact.data.phone);
+ }
+
+ const destinationFk = String(clientsFk);
+ const destination = String(clients);
+
+ this.newSMS = Object.assign({
+ destinationFk: destinationFk,
+ destination: destination
+ });
+
+ this.$.sms.open();
+ return true;
+ } catch (e) {
+ this.vnApp.showError(this.$t(e.message));
+ return false;
+ }
+ }
}
ngModule.vnComponent('vnRouteTickets', {