From f4ff854a1feeca2e28bb77499124240ba935458b Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 9 Feb 2022 14:06:06 +0100 Subject: [PATCH 1/2] feat(notification): notify client metrics Refs: 3315 --- modules/client/front/index.js | 1 + modules/client/front/notification/index.html | 155 +++++++++++++++++++ modules/client/front/notification/index.js | 129 +++++++++++++++ modules/client/front/routes.json | 7 + print/methods/notify/consumption.js | 45 ++++++ print/methods/notify/index.js | 6 + print/methods/routes.js | 4 + 7 files changed, 347 insertions(+) create mode 100644 modules/client/front/notification/index.html create mode 100644 modules/client/front/notification/index.js create mode 100644 print/methods/notify/consumption.js create mode 100644 print/methods/notify/index.js 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..f4b23a754 --- /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}}
+
+
+
+ + + +
+
+

Client 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..56cf196a0 --- /dev/null +++ b/modules/client/front/notification/index.js @@ -0,0 +1,129 @@ +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 + }; + + // if (!this.dateParams) + 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; + } + } + + get clientConsumptionParams() { + const userParams = this.$.model.userParams; + return Object.assign({ + recipient: this.client.email, + recipientId: this.client.id + }, userParams); + } + + 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('Data saved!'))); + } + + 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/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..e828f3c5b --- /dev/null +++ b/print/methods/notify/consumption.js @@ -0,0 +1,45 @@ +const db = require('vn-print/core/database'); +const Email = require('vn-print/core/email'); + +module.exports = async function(request, response, next) { + 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' + }); + + for (const clientId of reqArgs.clientIds) { + const client = await db.findOne(` + SELECT + 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 = ? + AND it.isPackaging = FALSE + AND DATE(t.shipped) BETWEEN ? AND ? + GROUP BY c.id`, [clientId, reqArgs.from, reqArgs.to]); + + 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(); + } + } +}; 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') + } ]; From 5518fac357a4e145d6da9cc26300adb61ca4e554 Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 14 Feb 2022 08:44:22 +0100 Subject: [PATCH 2/2] Updated unit tests --- modules/client/front/notification/index.html | 6 +- modules/client/front/notification/index.js | 16 +-- .../client/front/notification/index.spec.js | 99 +++++++++++++++++++ .../client/front/notification/locale/es.yml | 2 + print/methods/notify/consumption.js | 79 ++++++++------- 5 files changed, 153 insertions(+), 49 deletions(-) create mode 100644 modules/client/front/notification/index.spec.js create mode 100644 modules/client/front/notification/locale/es.yml diff --git a/modules/client/front/notification/index.html b/modules/client/front/notification/index.html index f4b23a754..f503c95c8 100644 --- a/modules/client/front/notification/index.html +++ b/modules/client/front/notification/index.html @@ -23,7 +23,7 @@ + vn-tooltip="Campaign consumption"> @@ -87,7 +87,7 @@
-

Client consumption

+

Campaign consumption

- +
diff --git a/modules/client/front/notification/index.js b/modules/client/front/notification/index.js index 56cf196a0..12a1a4acb 100644 --- a/modules/client/front/notification/index.js +++ b/modules/client/front/notification/index.js @@ -35,7 +35,6 @@ export default class Controller extends Section { to: null }; - // if (!this.dateParams) this.getUpcomingCampaing(); } @@ -56,9 +55,8 @@ export default class Controller extends Section { } getUpcomingCampaing() { - this.$http.get('Campaigns/upcoming').then(res => { - this.campaign.id = res.data.id; - }); + this.$http.get('Campaigns/upcoming') + .then(res => this.campaign.id = res.data.id); } get campaignSelection() { @@ -77,14 +75,6 @@ export default class Controller extends Section { } } - get clientConsumptionParams() { - const userParams = this.$.model.userParams; - return Object.assign({ - recipient: this.client.email, - recipientId: this.client.id - }, userParams); - } - onSendClientConsumption() { const clientIds = this.checked.map(client => client.id); const params = Object.assign({ @@ -93,7 +83,7 @@ export default class Controller extends Section { this.$http.post('notify/consumption', params) .then(() => this.$.filters.hide()) - .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); + .then(() => this.vnApp.showSuccess(this.$t('Notifications sent!'))); } exprBuilder(param, value) { 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/print/methods/notify/consumption.js b/print/methods/notify/consumption.js index e828f3c5b..39d39105b 100644 --- a/print/methods/notify/consumption.js +++ b/print/methods/notify/consumption.js @@ -2,44 +2,57 @@ const db = require('vn-print/core/database'); const Email = require('vn-print/core/email'); module.exports = async function(request, response, next) { - const reqArgs = request.body; + 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'); + 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' - }); + response.status(200).json({ + message: 'Success' + }); - for (const clientId of reqArgs.clientIds) { - const client = await db.findOne(` - SELECT - 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 = ? - AND it.isPackaging = FALSE - AND DATE(t.shipped) BETWEEN ? AND ? - GROUP BY c.id`, [clientId, reqArgs.from, reqArgs.to]); + const clientIds = reqArgs.clientIds; - if (client) { - const args = Object.assign({ - recipientId: clientId, - recipient: client.email, - replyTo: client.salesPersonEmail - }, response.locals); + 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 email = new Email('campaign-metrics', args); - await email.send(); + 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); } };