diff --git a/back/methods/campaign/latest.js b/back/methods/campaign/latest.js new file mode 100644 index 0000000000..f1449dfecf --- /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/spec/latest.spec.js b/back/methods/campaign/spec/latest.spec.js new file mode 100644 index 0000000000..b76e4fd0a1 --- /dev/null +++ b/back/methods/campaign/spec/latest.spec.js @@ -0,0 +1,34 @@ +const app = require('vn-loopback/server/server'); + +describe('campaign latest()', () => { + it('should return the campaigns from the last year', async() => { + 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() * result.length); + const campaignDated = result[randomIndex].dated; + const campaignYear = campaignDated.getFullYear(); + + 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/spec/upcoming.spec.js b/back/methods/campaign/spec/upcoming.spec.js new file mode 100644 index 0000000000..953683e7a3 --- /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/back/methods/campaign/upcoming.js b/back/methods/campaign/upcoming.js new file mode 100644 index 0000000000..2f1a5a3776 --- /dev/null +++ b/back/methods/campaign/upcoming.js @@ -0,0 +1,29 @@ +module.exports = Self => { + Self.remoteMethod('upcoming', { + description: 'Returns the upcoming campaign', + accessType: 'READ', + accepts: [], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/upcoming`, + verb: 'GET' + } + }); + + Self.upcoming = async() => { + const minDate = new Date(); + minDate.setFullYear(minDate.getFullYear() - 1); + + return Self.findOne({ + where: { + dated: { + gte: minDate + } + }, + order: 'dated ASC' + }); + }; +}; diff --git a/back/model-config.json b/back/model-config.json index b8a8f04ac9..7a59aaf9aa 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 0000000000..db5f2e8286 --- /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 0000000000..e99e8d8193 --- /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 0000000000..0e5b91170e --- /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 b335d266f0..18c277f062 100755 --- a/front/core/components/autocomplete/index.js +++ b/front/core/components/autocomplete/index.js @@ -190,9 +190,24 @@ 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; } } diff --git a/modules/client/front/consumption-search-panel/index.html b/modules/client/front/consumption-search-panel/index.html index e957c891b3..f8d633c718 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; + }); + } + + 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/index.spec.js b/modules/client/front/consumption-search-panel/index.spec.js new file mode 100644 index 0000000000..7209fe3c3a --- /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 query and then set the campaign property`, () => { + $httpBackend.expect('GET', 'Campaigns/upcoming').respond(200, {id: 2, code: 'allSaints'}); + controller.getUpcomingCampaing(); + $httpBackend.flush(); + + const filter = controller.$.filter; + + expect(filter.campaign).toEqual(2); + }); + }); + }); +}); 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 0000000000..03364d7cff --- /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 68de42b23a..f136283f87 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 diff --git a/modules/worker/back/methods/worker/specs/createAbsence.spec.js b/modules/worker/back/methods/worker/specs/createAbsence.spec.js index 324dab99dd..ace412890f 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 2f72a8435a..15bd854ed1 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 1b34cf2e01..2ff8e64e1e 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);