From cdde4d95d77455c7668dbb636963aaa121dafe45 Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 5 Oct 2020 07:53:03 +0200 Subject: [PATCH 1/4] Added filter --- back/methods/campaign/latest.js | 41 +++++++++++++++++++ back/methods/campaign/upcoming.js | 30 ++++++++++++++ back/model-config.json | 3 ++ back/models/campaign.js | 4 ++ back/models/campaign.json | 33 +++++++++++++++ db/changes/10230-oktoberFest/00-campaign.sql | 37 +++++++++++++++++ front/core/components/autocomplete/index.js | 24 +++++++++-- .../front/consumption-search-panel/index.html | 16 ++++++++ .../front/consumption-search-panel/index.js | 31 +++++++++++++- .../consumption-search-panel/locale/en.yml | 3 ++ .../consumption-search-panel/locale/es.yml | 6 ++- 11 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 back/methods/campaign/latest.js create mode 100644 back/methods/campaign/upcoming.js create mode 100644 back/models/campaign.js create mode 100644 back/models/campaign.json create mode 100644 db/changes/10230-oktoberFest/00-campaign.sql create mode 100644 modules/client/front/consumption-search-panel/locale/en.yml diff --git a/back/methods/campaign/latest.js b/back/methods/campaign/latest.js new file mode 100644 index 000000000..f1449dfec --- /dev/null +++ b/back/methods/campaign/latest.js @@ -0,0 +1,41 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethod('latest', { + description: 'Returns the lastest campaigns', + accessType: 'READ', + accepts: [{ + arg: 'filter', + type: 'Object', + description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string` + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/latest`, + verb: 'GET' + } + }); + + Self.latest = async filter => { + const conn = Self.dataSource.connector; + const minDate = new Date(); + minDate.setFullYear(minDate.getFullYear() - 1); + minDate.setMonth(0); + minDate.setDate(1); + + const where = {dated: {gte: minDate}}; + filter = mergeFilters(filter, {where}); + + const stmt = new ParameterizedSQL( + `SELECT * FROM campaign`); + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge('GROUP BY code'); + stmt.merge(conn.makePagination(filter)); + + return conn.executeStmt(stmt); + }; +}; diff --git a/back/methods/campaign/upcoming.js b/back/methods/campaign/upcoming.js new file mode 100644 index 000000000..cc9f7f910 --- /dev/null +++ b/back/methods/campaign/upcoming.js @@ -0,0 +1,30 @@ +module.exports = Self => { + Self.remoteMethod('upcoming', { + description: 'Returns the lastest campaigns', + accessType: 'READ', + accepts: [], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/upcoming`, + verb: 'GET' + } + }); + + Self.upcoming = async() => { + const minDate = new Date(); + minDate.setMonth(0); + minDate.setDate(1); + + return Self.findOne({ + where: { + dated: { + lt: minDate + } + }, + order: 'dated DESC' + }); + }; +}; diff --git a/back/model-config.json b/back/model-config.json index b8a8f04ac..7a59aaf9a 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -8,6 +8,9 @@ "Bank": { "dataSource": "vn" }, + "Campaign": { + "dataSource": "vn" + }, "Country": { "dataSource": "vn" }, diff --git a/back/models/campaign.js b/back/models/campaign.js new file mode 100644 index 000000000..db5f2e828 --- /dev/null +++ b/back/models/campaign.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/campaign/latest')(Self); + require('../methods/campaign/upcoming')(Self); +}; diff --git a/back/models/campaign.json b/back/models/campaign.json new file mode 100644 index 000000000..e99e8d819 --- /dev/null +++ b/back/models/campaign.json @@ -0,0 +1,33 @@ +{ + "name": "Campaign", + "base": "VnModel", + "options": { + "mysql": { + "table": "campaign" + } + }, + "properties": { + "id": { + "type": "number", + "required": true + }, + "code": { + "type": "string", + "required": true + }, + "dated": { + "type": "date" + }, + "scopeDays": { + "type": "number" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} diff --git a/db/changes/10230-oktoberFest/00-campaign.sql b/db/changes/10230-oktoberFest/00-campaign.sql new file mode 100644 index 000000000..0e5b91170 --- /dev/null +++ b/db/changes/10230-oktoberFest/00-campaign.sql @@ -0,0 +1,37 @@ +CREATE TABLE `vn`.campaign +( + id INT AUTO_INCREMENT, + code ENUM('mothersDay', 'allSaints', 'valentinesDay') NOT NULL, + dated DATE DEFAULT CURDATE() NOT NULL, + scopeDays INT NOT NULL DEFAULT '15', + CONSTRAINT campaign_pk + PRIMARY KEY (id) +); + +CREATE UNIQUE INDEX campaign_dated_uindex + ON `vn`.campaign (dated); + +-- TODOS SANTOS +INSERT INTO `vn`.campaign(code, dated) +SELECT 'allSaints' AS code, dated + FROM `vn`.time +WHERE dated >= CONCAT(YEAR(CURDATE()) - 1, '-01-01') + AND month = 11 + AND day = 1; + +-- SAN VALENTIN +INSERT INTO `vn`.campaign(code, dated) +SELECT 'valentinesDay' AS code, dated + FROM `vn`.time +WHERE dated >= CONCAT(YEAR(CURDATE()) - 1, '-01-01') + AND month = 2 + AND day = 14; + +-- DIA DE LA MADRE +INSERT INTO `vn`.campaign(code, dated) +SELECT 'mothersDay' AS code, dated + FROM `vn`.time +WHERE dated >= CONCAT(YEAR(CURDATE()) - 1, '-01-01') + AND month = 5 + AND WEEK(dated, 5) - WEEK(DATE_SUB(dated, INTERVAL DAYOFMONTH(dated) - 1 DAY), 5) + 1 = 1 -- WEEK OF MONTH + AND DAYOFWEEK(dated) = 1; diff --git a/front/core/components/autocomplete/index.js b/front/core/components/autocomplete/index.js index b335d266f..742ad6f2a 100755 --- a/front/core/components/autocomplete/index.js +++ b/front/core/components/autocomplete/index.js @@ -190,9 +190,27 @@ export default class Autocomplete extends Field { this.input.value = display; - if (this.translateFields) { - if (this.translateFields.indexOf(this.showField) > -1) - this.input.value = this.$t(display); + if (this.translateFields && this.selection) { + const translations = []; + for (let field of this.translateFields) { + const fieldValue = this._selection[field]; + translations.push({ + original: fieldValue, + value: this.$t(fieldValue) + }); + } + + for (let translation of translations) { + const orgValue = translation.original; + const value = translation.value; + + display = display.replace(orgValue, value); + } + + this.input.value = display; + + /* if (this.translateFields.indexOf(this.showField) > -1) + this.input.value = this.$t(display); */ } } diff --git a/modules/client/front/consumption-search-panel/index.html b/modules/client/front/consumption-search-panel/index.html index e957c891b..0edb0b34a 100644 --- a/modules/client/front/consumption-search-panel/index.html +++ b/modules/client/front/consumption-search-panel/index.html @@ -49,6 +49,22 @@ ng-model="filter.categoryId"> + + + + {{code}} {{dated | date: 'yyyy'}} + + + { + const filter = this.$.filter; + filter.campaign = res.data.id; + console.log(res.data); + }); + } + + get campaignSelection() { + return this._campaignSelection; + } + + set campaignSelection(value) { + this._campaignSelection = value; + + if (!value) return; + + const filter = this.$.filter; + const from = new Date(value.dated); + from.setDate(from.getDate() - value.scopeDays); + + filter.to = value.dated; + filter.from = from; + } +} + ngModule.vnComponent('vnConsumptionSearchPanel', { template: require('./index.html'), - controller: SearchPanel + controller: Controller }); diff --git a/modules/client/front/consumption-search-panel/locale/en.yml b/modules/client/front/consumption-search-panel/locale/en.yml new file mode 100644 index 000000000..03364d7cf --- /dev/null +++ b/modules/client/front/consumption-search-panel/locale/en.yml @@ -0,0 +1,3 @@ +allSaints: All Saints Day +valentinesDay: Valentine's Day +mothersDay: Mother's day \ No newline at end of file diff --git a/modules/client/front/consumption-search-panel/locale/es.yml b/modules/client/front/consumption-search-panel/locale/es.yml index 68de42b23..f136283f8 100644 --- a/modules/client/front/consumption-search-panel/locale/es.yml +++ b/modules/client/front/consumption-search-panel/locale/es.yml @@ -1,3 +1,7 @@ Item id: Id artículo From: Desde -To: Hasta \ No newline at end of file +To: Hasta +Campaign: Campaña +allSaints: Día de todos los Santos +valentinesDay: Día de San Valentín +mothersDay: Día de la madre \ No newline at end of file From e3b14405b71ab749bdd91ea90d10898fe85792ff Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 5 Oct 2020 08:50:07 +0200 Subject: [PATCH 2/4] Added unit tests --- back/methods/campaign/spec/latest.spec.js | 18 +++++++++++ back/methods/campaign/spec/upcoming.spec.js | 16 ++++++++++ .../front/consumption-search-panel/index.js | 5 ++- .../consumption-search-panel/index.spec.js | 31 +++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 back/methods/campaign/spec/latest.spec.js create mode 100644 back/methods/campaign/spec/upcoming.spec.js create mode 100644 modules/client/front/consumption-search-panel/index.spec.js diff --git a/back/methods/campaign/spec/latest.spec.js b/back/methods/campaign/spec/latest.spec.js new file mode 100644 index 000000000..c76231ab2 --- /dev/null +++ b/back/methods/campaign/spec/latest.spec.js @@ -0,0 +1,18 @@ +const app = require('vn-loopback/server/server'); + +describe('campaign latest()', () => { + it('should return the campaigns from the last year', async() => { + let response = await app.models.Campaign.latest(); + + const lastYearDate = new Date(); + lastYearDate.setFullYear(lastYearDate.getFullYear() - 1); + const lastYear = lastYearDate.getFullYear(); + + const randomIndex = Math.floor(Math.random() * 3); + const campaignDated = response[randomIndex].dated; + const campaignYear = campaignDated.getFullYear(); + + expect(response.length).toEqual(3); + expect(campaignYear).toEqual(lastYear); + }); +}); diff --git a/back/methods/campaign/spec/upcoming.spec.js b/back/methods/campaign/spec/upcoming.spec.js new file mode 100644 index 000000000..953683e7a --- /dev/null +++ b/back/methods/campaign/spec/upcoming.spec.js @@ -0,0 +1,16 @@ +const app = require('vn-loopback/server/server'); + +describe('campaign upcoming()', () => { + it('should return the upcoming campaign but from the last year', async() => { + let response = await app.models.Campaign.upcoming(); + + const lastYearDate = new Date(); + lastYearDate.setFullYear(lastYearDate.getFullYear() - 1); + const lastYear = lastYearDate.getFullYear(); + + const campaignDated = response.dated; + const campaignYear = campaignDated.getFullYear(); + + expect(campaignYear).toEqual(lastYear); + }); +}); diff --git a/modules/client/front/consumption-search-panel/index.js b/modules/client/front/consumption-search-panel/index.js index 53596c8f3..099564efc 100644 --- a/modules/client/front/consumption-search-panel/index.js +++ b/modules/client/front/consumption-search-panel/index.js @@ -5,10 +5,13 @@ class Controller extends SearchPanel { constructor($, $element) { super($, $element); + this.getUpcomingCampaing(); + } + + getUpcomingCampaing() { this.$http.get('Campaigns/upcoming').then(res => { const filter = this.$.filter; filter.campaign = res.data.id; - console.log(res.data); }); } diff --git a/modules/client/front/consumption-search-panel/index.spec.js b/modules/client/front/consumption-search-panel/index.spec.js new file mode 100644 index 000000000..dfab07086 --- /dev/null +++ b/modules/client/front/consumption-search-panel/index.spec.js @@ -0,0 +1,31 @@ +import './index.js'; + +describe('Client', () => { + describe('Component vnConsumptionSearchPanel', () => { + let $httpBackend; + let $element; + let controller; + + beforeEach(ngModule('client')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + $element = angular.element(`
`); + controller = $componentController('vnConsumptionSearchPanel', {$element}); + controller.$.filter = {}; + $httpBackend.expect('GET', 'Campaigns/upcoming').respond(200, {id: 1}); + })); + + describe('getUpcomingCampaing()', () => { + it(`should make an HTTP GET query`, () => { + $httpBackend.expect('GET', 'Campaigns/upcoming').respond(200, {id: 2, code: 'allSaints'}); + controller.getUpcomingCampaing(); + $httpBackend.flush(); + + const filter = controller.$.filter; + + expect(filter.campaign).toEqual(2); + }); + }); + }); +}); From 71e29e747a5df9ced4d58af228bd2b236448e8ab Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 5 Oct 2020 14:45:39 +0200 Subject: [PATCH 3/4] Requested changes --- back/methods/campaign/spec/latest.spec.js | 24 ++++++++++--- back/methods/campaign/upcoming.js | 7 ++-- front/core/components/autocomplete/index.js | 3 -- .../front/consumption-search-panel/index.html | 2 +- .../consumption-search-panel/index.spec.js | 2 +- .../worker/specs/createAbsence.spec.js | 11 +++--- .../worker/specs/deleteAbsence.spec.js | 23 ++++++------ .../worker/specs/updateAbsence.spec.js | 35 +++++++++++++------ 8 files changed, 66 insertions(+), 41 deletions(-) diff --git a/back/methods/campaign/spec/latest.spec.js b/back/methods/campaign/spec/latest.spec.js index c76231ab2..b76e4fd0a 100644 --- a/back/methods/campaign/spec/latest.spec.js +++ b/back/methods/campaign/spec/latest.spec.js @@ -2,17 +2,33 @@ const app = require('vn-loopback/server/server'); describe('campaign latest()', () => { it('should return the campaigns from the last year', async() => { - let response = await app.models.Campaign.latest(); + let result = await app.models.Campaign.latest(); const lastYearDate = new Date(); lastYearDate.setFullYear(lastYearDate.getFullYear() - 1); const lastYear = lastYearDate.getFullYear(); - const randomIndex = Math.floor(Math.random() * 3); - const campaignDated = response[randomIndex].dated; + const randomIndex = Math.floor(Math.random() * result.length); + const campaignDated = result[randomIndex].dated; const campaignYear = campaignDated.getFullYear(); - expect(response.length).toEqual(3); + expect(result.length).toEqual(3); expect(campaignYear).toEqual(lastYear); }); + + it('should return the campaigns from the current year', async() => { + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + + const result = await app.models.Campaign.latest({ + where: {dated: {like: `%${currentYear}%`}} + }); + + const randomIndex = Math.floor(Math.random() * result.length); + const campaignDated = result[randomIndex].dated; + const campaignYear = campaignDated.getFullYear(); + + expect(result.length).toEqual(3); + expect(campaignYear).toEqual(currentYear); + }); }); diff --git a/back/methods/campaign/upcoming.js b/back/methods/campaign/upcoming.js index cc9f7f910..cabb66705 100644 --- a/back/methods/campaign/upcoming.js +++ b/back/methods/campaign/upcoming.js @@ -15,16 +15,15 @@ module.exports = Self => { Self.upcoming = async() => { const minDate = new Date(); - minDate.setMonth(0); - minDate.setDate(1); + minDate.setFullYear(minDate.getFullYear() - 1); return Self.findOne({ where: { dated: { - lt: minDate + gte: minDate } }, - order: 'dated DESC' + order: 'dated ASC' }); }; }; diff --git a/front/core/components/autocomplete/index.js b/front/core/components/autocomplete/index.js index 742ad6f2a..18c277f06 100755 --- a/front/core/components/autocomplete/index.js +++ b/front/core/components/autocomplete/index.js @@ -208,9 +208,6 @@ export default class Autocomplete extends Field { } this.input.value = display; - - /* if (this.translateFields.indexOf(this.showField) > -1) - this.input.value = this.$t(display); */ } } diff --git a/modules/client/front/consumption-search-panel/index.html b/modules/client/front/consumption-search-panel/index.html index 0edb0b34a..f8d633c71 100644 --- a/modules/client/front/consumption-search-panel/index.html +++ b/modules/client/front/consumption-search-panel/index.html @@ -59,7 +59,7 @@ ng-model="filter.campaign" order="dated DESC" selection="$ctrl.campaignSelection" - search-function="{or: [{dated: {like: '%'+ $search +'%'}}]}"> + search-function="{dated: {like: '%'+ $search +'%'}}"> {{code}} {{dated | date: 'yyyy'}} diff --git a/modules/client/front/consumption-search-panel/index.spec.js b/modules/client/front/consumption-search-panel/index.spec.js index dfab07086..7209fe3c3 100644 --- a/modules/client/front/consumption-search-panel/index.spec.js +++ b/modules/client/front/consumption-search-panel/index.spec.js @@ -17,7 +17,7 @@ describe('Client', () => { })); describe('getUpcomingCampaing()', () => { - it(`should make an HTTP GET query`, () => { + it(`should make an HTTP query and then set the campaign property`, () => { $httpBackend.expect('GET', 'Campaigns/upcoming').respond(200, {id: 2, code: 'allSaints'}); controller.getUpcomingCampaing(); $httpBackend.flush(); diff --git a/modules/worker/back/methods/worker/specs/createAbsence.spec.js b/modules/worker/back/methods/worker/specs/createAbsence.spec.js index 324dab99d..ace412890 100644 --- a/modules/worker/back/methods/worker/specs/createAbsence.spec.js +++ b/modules/worker/back/methods/worker/specs/createAbsence.spec.js @@ -3,12 +3,6 @@ const LoopBackContext = require('loopback-context'); describe('Worker createAbsence()', () => { const workerId = 18; - let createdAbsence; - - afterAll(async() => { - const absence = await app.models.Calendar.findById(createdAbsence.id); - await absence.destroy(); - }); it('should return an error for a user without enough privileges', async() => { const ctx = {req: {accessToken: {userId: 18}}}; @@ -40,12 +34,15 @@ describe('Worker createAbsence()', () => { const absenceTypeId = 1; const dated = new Date(); - createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated); + const createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated); const expectedBusinessId = 18; const expectedAbsenceTypeId = 1; expect(createdAbsence.businessFk).toEqual(expectedBusinessId); expect(createdAbsence.dayOffTypeFk).toEqual(expectedAbsenceTypeId); + + // Restores + await app.models.Calendar.destroyById(createdAbsence.id); }); }); diff --git a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js index 2f72a8435..15bd854ed 100644 --- a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js +++ b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js @@ -2,29 +2,35 @@ const app = require('vn-loopback/server/server'); const LoopBackContext = require('loopback-context'); describe('Worker deleteAbsence()', () => { + const businessId = 18; const workerId = 18; - let createdAbsence; const activeCtx = { - accessToken: {userId: 19}, + accessToken: {userId: 106}, headers: {origin: 'http://localhost'} }; const ctx = {req: activeCtx}; ctx.req.__ = value => { return value; }; + let createdAbsence; - it('should return an error for a user without enough privileges', async() => { + beforeEach(async() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); - - activeCtx.accessToken.userId = 106; - const businessId = 18; createdAbsence = await app.models.Calendar.create({ businessFk: businessId, dayOffTypeFk: 1, dated: new Date() }); + }); + + afterEach(async() => { + await app.models.Calendar.destroyById(createdAbsence.id); + }); + + it('should return an error for a user without enough privileges', async() => { + activeCtx.accessToken.userId = 106; let error; await app.models.Worker.deleteAbsence(ctx, 18, createdAbsence.id).catch(e => { @@ -37,12 +43,7 @@ describe('Worker deleteAbsence()', () => { }); it('should create a new absence', async() => { - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - activeCtx.accessToken.userId = 19; - const businessId = 18; expect(createdAbsence.businessFk).toEqual(businessId); diff --git a/modules/worker/back/methods/worker/specs/updateAbsence.spec.js b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js index 1b34cf2e0..2ff8e64e1 100644 --- a/modules/worker/back/methods/worker/specs/updateAbsence.spec.js +++ b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js @@ -1,22 +1,37 @@ const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); describe('Worker updateAbsence()', () => { const workerId = 106; + const businessId = 106; + const activeCtx = { + accessToken: {userId: 106}, + headers: {origin: 'http://localhost'} + }; + const ctx = {req: activeCtx}; + ctx.req.__ = value => { + return value; + }; let createdAbsence; - afterAll(async() => { - const absence = await app.models.Calendar.findById(createdAbsence.id); - await absence.destroy(); - }); - - it('should return an error for a user without enough privileges', async() => { - const ctx = {req: {accessToken: {userId: 106}}}; - const expectedAbsenceTypeId = 2; + beforeEach(async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); createdAbsence = await app.models.Calendar.create({ - businessFk: 106, + businessFk: businessId, dayOffTypeFk: 1, dated: new Date() }); + }); + + afterEach(async() => { + await app.models.Calendar.destroyById(createdAbsence.id); + }); + + it('should return an error for a user without enough privileges', async() => { + activeCtx.accessToken.userId = 106; + const expectedAbsenceTypeId = 2; let error; await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId).catch(e => { @@ -29,7 +44,7 @@ describe('Worker updateAbsence()', () => { }); it('should create a new absence', async() => { - const ctx = {req: {accessToken: {userId: 37}}}; + activeCtx.accessToken.userId = 37; const expectedAbsenceTypeId = 2; const updatedAbsence = await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId); From 91fa3e2aec912783964f0edcc96304e6cef6c015 Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 5 Oct 2020 14:47:42 +0200 Subject: [PATCH 4/4] Updated description --- back/methods/campaign/upcoming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/campaign/upcoming.js b/back/methods/campaign/upcoming.js index cabb66705..2f1a5a377 100644 --- a/back/methods/campaign/upcoming.js +++ b/back/methods/campaign/upcoming.js @@ -1,6 +1,6 @@ module.exports = Self => { Self.remoteMethod('upcoming', { - description: 'Returns the lastest campaigns', + description: 'Returns the upcoming campaign', accessType: 'READ', accepts: [], returns: {