diff --git a/modules/client/front/index.js b/modules/client/front/index.js index 6b35d392a..d9f3a8a17 100644 --- a/modules/client/front/index.js +++ b/modules/client/front/index.js @@ -45,3 +45,4 @@ import './dms/edit'; import './consumption'; import './consumption-search-panel'; import './defaulter'; +import './notification'; diff --git a/modules/client/front/notification/index.html b/modules/client/front/notification/index.html new file mode 100644 index 000000000..f503c95c8 --- /dev/null +++ b/modules/client/front/notification/index.html @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Identifier + + Social name + + City + + Phone + + Email +
+ + + + + {{::client.id}} + + {{::client.socialName}}{{::client.city}}{{::client.phone}}{{::client.email}}
+
+
+
+ + + +
+
+

Campaign consumption

+ + + + {{code}} {{dated | date: 'yyyy'}} + + + + + + + + + + + + +
+
+
+ + + + Filter by selection + + + Exclude selection + + + Remove filter + + + Remove all filters + + + Copy value + + + diff --git a/modules/client/front/notification/index.js b/modules/client/front/notification/index.js new file mode 100644 index 000000000..12a1a4acb --- /dev/null +++ b/modules/client/front/notification/index.js @@ -0,0 +1,119 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.$checkAll = false; + this.smartTableOptions = { + activeButtons: { + search: true + }, + columns: [ + { + field: 'socialName', + autocomplete: { + url: 'Clients', + showField: 'socialName', + valueField: 'socialName' + } + }, + { + field: 'city', + autocomplete: { + url: 'Towns', + valueField: 'name', + showField: 'name' + } + } + ] + }; + + this.campaign = { + id: null, + from: null, + to: null + }; + + this.getUpcomingCampaing(); + } + + get checked() { + const clients = this.$.model.data || []; + const checkedClients = []; + for (const buy of clients) { + if (buy.$checked) + checkedClients.push(buy); + } + + return checkedClients; + } + + onChangeDate(value) { + if (value) + this.campaign.id = null; + } + + getUpcomingCampaing() { + this.$http.get('Campaigns/upcoming') + .then(res => this.campaign.id = res.data.id); + } + + get campaignSelection() { + return this._campaignSelection; + } + + set campaignSelection(value) { + this._campaignSelection = value; + + if (value) { + const from = new Date(value.dated); + from.setDate(from.getDate() - value.scopeDays); + + this.campaign.to = value.dated; + this.campaign.from = from; + } + } + + onSendClientConsumption() { + const clientIds = this.checked.map(client => client.id); + const params = Object.assign({ + clientIds: clientIds + }, this.campaign); + + this.$http.post('notify/consumption', params) + .then(() => this.$.filters.hide()) + .then(() => this.vnApp.showSuccess(this.$t('Notifications sent!'))); + } + + exprBuilder(param, value) { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {id: value} + : {or: [{name: {like: `%${value}%`}}, {socialName: {like: `%${value}%`}}]}; + case 'phone': + return { + or: [ + {phone: value}, + {mobile: value} + ] + }; + case 'name': + case 'socialName': + case 'city': + case 'email': + return {[param]: {like: `%${value}%`}}; + case 'id': + case 'fi': + case 'postcode': + case 'salesPersonFk': + return {[param]: value}; + } + } +} + +ngModule.vnComponent('vnClientNotification', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/client/front/notification/index.spec.js b/modules/client/front/notification/index.spec.js new file mode 100644 index 000000000..13c6bc513 --- /dev/null +++ b/modules/client/front/notification/index.spec.js @@ -0,0 +1,99 @@ +import './index'; +import crudModel from 'core/mocks/crud-model'; + +describe('Client notification', () => { + describe('Component vnClientNotification', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('client')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + const $element = angular.element(''); + controller = $componentController('vnClientNotification', {$element}); + controller.$.model = crudModel; + controller.$.model.data = [ + {id: 1101}, + {id: 1102}, + {id: 1103} + ]; + $httpBackend.expect('GET', `Campaigns/upcoming`).respond(200, {id: 1}); + })); + + describe('checked() getter', () => { + it('should return the checked lines', () => { + const data = controller.$.model.data; + data[1].$checked = true; + data[2].$checked = true; + + const checkedRows = controller.checked; + + const firstCheckedRow = checkedRows[0]; + const secondCheckedRow = checkedRows[1]; + + expect(firstCheckedRow.id).toEqual(1102); + expect(secondCheckedRow.id).toEqual(1103); + }); + }); + + describe('campaignSelection() setter', () => { + it('should set the campaign from and to properties', () => { + const dated = new Date(); + controller.campaignSelection = { + dated: dated, + scopeDays: 14 + }; + + const expectedDateTo = new Date(dated); + expectedDateTo.setDate(expectedDateTo.getDate() - 14); + + const campaign = controller.campaign; + + expect(campaign.from).toEqual(expectedDateTo); + expect(campaign.to).toEqual(dated); + }); + }); + + describe('onSendClientConsumption()', () => { + it('should return saved message', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.$.filters = {hide: () => {}}; + controller.campaign = { + id: 1, + from: new Date(), + to: new Date() + }; + + const data = controller.$.model.data; + data[0].$checked = true; + data[1].$checked = true; + + const params = Object.assign({ + clientIds: [1101, 1102] + }, controller.campaign); + + $httpBackend.expect('POST', `notify/consumption`, params).respond(200, params); + controller.onSendClientConsumption(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Notifications sent!'); + }); + }); + + describe('exprBuilder()', () => { + it('should search by sales person', () => { + let expr = controller.exprBuilder('salesPersonFk', '5'); + + expect(expr).toEqual({'salesPersonFk': '5'}); + }); + + it('should search by client social name', () => { + let expr = controller.exprBuilder('socialName', '1foo'); + + expect(expr).toEqual({'socialName': {like: '%1foo%'}}); + }); + }); + }); +}); diff --git a/modules/client/front/notification/locale/es.yml b/modules/client/front/notification/locale/es.yml new file mode 100644 index 000000000..dd89d7c6e --- /dev/null +++ b/modules/client/front/notification/locale/es.yml @@ -0,0 +1,2 @@ +Campaign consumption: Consumo campaƱa +Send: Enviar \ No newline at end of file diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json index 753948f8e..d6f8a70bd 100644 --- a/modules/client/front/routes.json +++ b/modules/client/front/routes.json @@ -7,6 +7,7 @@ "menus": { "main": [ {"state": "client.index", "icon": "person"}, + {"state": "client.notification", "icon": "campaign"}, {"state": "client.defaulter.index", "icon": "icon-defaulter"} ], "card": [ @@ -373,6 +374,12 @@ "state": "client.defaulter.index", "component": "vn-client-defaulter-index", "description": "Defaulter" + }, + { + "url" : "/notification", + "state": "client.notification", + "component": "vn-client-notification", + "description": "Notifications" } ] } diff --git a/print/methods/notify/consumption.js b/print/methods/notify/consumption.js new file mode 100644 index 000000000..39d39105b --- /dev/null +++ b/print/methods/notify/consumption.js @@ -0,0 +1,58 @@ +const db = require('vn-print/core/database'); +const Email = require('vn-print/core/email'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.body; + + if (!reqArgs.clientIds) + throw new Error('The argument clientIds is required'); + if (!reqArgs.from) + throw new Error('The argument from is required'); + if (!reqArgs.to) + throw new Error('The argument to is required'); + + response.status(200).json({ + message: 'Success' + }); + + const clientIds = reqArgs.clientIds; + + const clients = await db.rawSql(` + SELECT + c.id, + c.email, + eu.email salesPersonEmail + FROM client c + JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk + JOIN ticket t ON t.clientFk = c.id + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN itemType it ON it.id = i.typeFk + WHERE c.id IN(?) + AND it.isPackaging = FALSE + AND DATE(t.shipped) BETWEEN ? AND ? + GROUP BY c.id`, [clientIds, reqArgs.from, reqArgs.to]); + + const clientData = new Map(); + for (const client of clients) + clientData.set(client.id, client); + + for (const clientId of reqArgs.clientIds) { + const client = clientData.get(clientId); + + if (client) { + const args = Object.assign({ + recipientId: clientId, + recipient: client.email, + replyTo: client.salesPersonEmail + }, response.locals); + + const email = new Email('campaign-metrics', args); + await email.send(); + } + } + } catch (error) { + next(error); + } +}; diff --git a/print/methods/notify/index.js b/print/methods/notify/index.js new file mode 100644 index 000000000..df4705d1e --- /dev/null +++ b/print/methods/notify/index.js @@ -0,0 +1,6 @@ +const express = require('express'); +const router = new express.Router(); + +router.post('/consumption', require('./consumption')); + +module.exports = router; diff --git a/print/methods/routes.js b/print/methods/routes.js index 0c452028e..ea40e0743 100644 --- a/print/methods/routes.js +++ b/print/methods/routes.js @@ -15,4 +15,8 @@ module.exports = [ url: '/api/closure', cb: require('./closure') }, + { + url: '/api/notify', + cb: require('./notify') + } ];