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', {