diff --git a/db/changes/10200-normality/01-calendar_employee.sql b/db/changes/10200-normality/01-calendar_employee.sql
new file mode 100644
index 000000000..c5db9da2c
--- /dev/null
+++ b/db/changes/10200-normality/01-calendar_employee.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `postgresql`.`calendar_employee`
+ADD COLUMN `id` INT NULL AUTO_INCREMENT FIRST,
+ADD UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE,
+ADD INDEX `id_index` (`id` ASC) VISIBLE;
+;
diff --git a/db/changes/10200-normality/02-workerCalendar.sql b/db/changes/10200-normality/02-workerCalendar.sql
new file mode 100644
index 000000000..479470ac0
--- /dev/null
+++ b/db/changes/10200-normality/02-workerCalendar.sql
@@ -0,0 +1,12 @@
+USE `vn`;
+CREATE
+ OR REPLACE ALGORITHM = UNDEFINED
+ DEFINER = `root`@`%`
+ SQL SECURITY DEFINER
+VIEW `workerCalendar2` AS
+ SELECT
+ `ce`.`id` AS `id`,
+ `ce`.`business_id` AS `businessFk`,
+ `ce`.`calendar_state_id` AS `absenceTypeFk`,
+ `ce`.`date` AS `dated`
+ FROM `postgresql`.`calendar_employee` `ce`;
diff --git a/front/core/components/calendar/index.js b/front/core/components/calendar/index.js
index 96bc435db..02d2a4798 100644
--- a/front/core/components/calendar/index.js
+++ b/front/core/components/calendar/index.js
@@ -135,7 +135,7 @@ export default class Calendar extends FormInput {
$days: [day],
$type: 'day'
});
- this.repaint();
+ // this.repaint();
}
/*
diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js
new file mode 100644
index 000000000..28a2b9d9d
--- /dev/null
+++ b/modules/worker/back/methods/worker/createAbsence.js
@@ -0,0 +1,58 @@
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('createAbsence', {
+ description: 'Creates a new worker absence',
+ accepts: [{
+ arg: 'id',
+ type: 'Number',
+ description: 'The worker id',
+ http: {source: 'path'}
+ },
+ {
+ arg: 'absenceTypeId',
+ type: 'Number',
+ required: true
+ },
+ {
+ arg: 'dated',
+ type: 'Date',
+ required: false
+ }],
+ returns: {
+ type: 'Object',
+ root: true
+ },
+ http: {
+ path: `/:id/createAbsence`,
+ verb: 'POST'
+ }
+ });
+
+ Self.createAbsence = async(ctx, id, absenceTypeId, dated) => {
+ const models = Self.app.models;
+ const userId = ctx.req.accessToken.userId;
+ const isSubordinate = await models.Worker.isSubordinate(ctx, id);
+ const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss');
+
+ if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss))
+ throw new UserError(`You don't have enough privileges`);
+
+ const labour = await models.WorkerLabour.findOne({
+ where: {
+ and: [
+ {workerFk: id},
+ {or: [{
+ ended: {gte: [dated]}
+ }, {ended: null}]}
+ ]
+ }
+ });
+
+ return models.WorkerCalendar.create({
+ businessFk: labour.businessFk,
+ absenceTypeFk: absenceTypeId,
+ dated: dated
+ });
+ };
+};
diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js
new file mode 100644
index 000000000..ea156d7eb
--- /dev/null
+++ b/modules/worker/back/methods/worker/deleteAbsence.js
@@ -0,0 +1,37 @@
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('deleteAbsence', {
+ description: 'Deletes a worker absence',
+ accepts: [{
+ arg: 'id',
+ type: 'Number',
+ description: 'The worker id',
+ http: {source: 'path'}
+ },
+ {
+ arg: 'absenceId',
+ type: 'Number',
+ required: true
+ }],
+ returns: 'Object',
+ http: {
+ path: `/:id/deleteAbsence`,
+ verb: 'DELETE'
+ }
+ });
+
+ Self.deleteAbsence = async(ctx, id, absenceId) => {
+ const models = Self.app.models;
+ const userId = ctx.req.accessToken.userId;
+ const isSubordinate = await models.Worker.isSubordinate(ctx, id);
+ const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss');
+
+ if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss))
+ throw new UserError(`You don't have enough privileges`);
+
+ const absence = await models.WorkerCalendar.findById(absenceId);
+
+ return absence.destroy();
+ };
+};
diff --git a/modules/worker/back/methods/worker/specs/createAbsence.spec.js b/modules/worker/back/methods/worker/specs/createAbsence.spec.js
new file mode 100644
index 000000000..33bc2a80e
--- /dev/null
+++ b/modules/worker/back/methods/worker/specs/createAbsence.spec.js
@@ -0,0 +1,39 @@
+const app = require('vn-loopback/server/server');
+
+describe('Worker createAbsence()', () => {
+ const workerId = 106;
+ let createdAbsence;
+
+ afterAll(async() => {
+ const absence = await app.models.WorkerCalendar.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 absenceTypeId = 1;
+ const dated = new Date();
+
+ let error;
+ await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated).catch(e => {
+ error = e;
+ }).finally(() => {
+ expect(error.message).toEqual(`You don't have enough privileges`);
+ });
+
+ expect(error).toBeDefined();
+ });
+
+ it('should create a new absence', async() => {
+ const ctx = {req: {accessToken: {userId: 37}}};
+ const absenceTypeId = 1;
+ const dated = new Date();
+ createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated);
+
+ const expectedBusinessId = 106;
+ const expectedAbsenceTypeId = 1;
+
+ expect(createdAbsence.businessFk).toEqual(expectedBusinessId);
+ expect(createdAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId);
+ });
+});
diff --git a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js
new file mode 100644
index 000000000..c506ae86d
--- /dev/null
+++ b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js
@@ -0,0 +1,38 @@
+const app = require('vn-loopback/server/server');
+
+describe('Worker deleteAbsence()', () => {
+ const workerId = 106;
+ let createdAbsence;
+
+ it('should return an error for a user without enough privileges', async() => {
+ const ctx = {req: {accessToken: {userId: 106}}};
+ const businessId = 106;
+ createdAbsence = await app.models.WorkerCalendar.create({
+ businessFk: businessId,
+ absenceTypeFk: 1,
+ dated: new Date()
+ });
+
+ let error;
+ await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id).catch(e => {
+ error = e;
+ }).finally(() => {
+ expect(error.message).toEqual(`You don't have enough privileges`);
+ });
+
+ expect(error).toBeDefined();
+ });
+
+ it('should create a new absence', async() => {
+ const ctx = {req: {accessToken: {userId: 37}}};
+ const businessId = 106;
+
+ expect(createdAbsence.businessFk).toEqual(businessId);
+
+ await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id);
+
+ const deletedAbsence = await app.models.WorkerCalendar.findById(createdAbsence.id);
+
+ expect(deletedAbsence).toBeNull();
+ });
+});
diff --git a/modules/worker/back/methods/worker/specs/updateAbsence.spec.js b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js
new file mode 100644
index 000000000..689d36136
--- /dev/null
+++ b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js
@@ -0,0 +1,38 @@
+const app = require('vn-loopback/server/server');
+
+describe('Worker updateAbsence()', () => {
+ const workerId = 106;
+ let createdAbsence;
+
+ afterAll(async() => {
+ const absence = await app.models.WorkerCalendar.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;
+ createdAbsence = await app.models.WorkerCalendar.create({
+ businessFk: 106,
+ absenceTypeFk: 1,
+ dated: new Date()
+ });
+
+ let error;
+ await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId).catch(e => {
+ error = e;
+ }).finally(() => {
+ expect(error.message).toEqual(`You don't have enough privileges`);
+ });
+
+ expect(error).toBeDefined();
+ });
+
+ it('should create a new absence', async() => {
+ const ctx = {req: {accessToken: {userId: 37}}};
+ const expectedAbsenceTypeId = 2;
+ const updatedAbsence = await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId);
+
+ expect(updatedAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId);
+ });
+});
diff --git a/modules/worker/back/methods/worker/updateAbsence.js b/modules/worker/back/methods/worker/updateAbsence.js
new file mode 100644
index 000000000..719bca7e4
--- /dev/null
+++ b/modules/worker/back/methods/worker/updateAbsence.js
@@ -0,0 +1,42 @@
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('updateAbsence', {
+ description: 'Updates a worker absence',
+ accepts: [{
+ arg: 'id',
+ type: 'Number',
+ description: 'The worker id',
+ http: {source: 'path'}
+ },
+ {
+ arg: 'absenceId',
+ type: 'Number',
+ required: true
+ },
+ {
+ arg: 'absenceTypeId',
+ type: 'Number',
+ required: true
+ }],
+ returns: 'Object',
+ http: {
+ path: `/:id/updateAbsence`,
+ verb: 'PATCH'
+ }
+ });
+
+ Self.updateAbsence = async(ctx, id, absenceId, absenceTypeId) => {
+ const models = Self.app.models;
+ const userId = ctx.req.accessToken.userId;
+ const isSubordinate = await models.Worker.isSubordinate(ctx, id);
+ const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss');
+
+ if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss))
+ throw new UserError(`You don't have enough privileges`);
+
+ const absence = await models.WorkerCalendar.findById(absenceId);
+
+ return absence.updateAttribute('absenceTypeFk', absenceTypeId);
+ };
+};
diff --git a/modules/worker/back/models/worker-calendar.json b/modules/worker/back/models/worker-calendar.json
index 569d4d1ba..ca802caa7 100644
--- a/modules/worker/back/models/worker-calendar.json
+++ b/modules/worker/back/models/worker-calendar.json
@@ -3,29 +3,22 @@
"base": "VnModel",
"options": {
"mysql": {
- "table": "workerCalendar"
+ "table": "workerCalendar2"
}
},
"properties": {
- "businessFk": {
- "id": 1,
+ "id": {
+ "id": true,
"type": "Number"
},
- "workerFk": {
- "id": 2,
+ "businessFk": {
"type": "Number"
},
"dated": {
- "id": 3,
"type": "Date"
}
},
"relations": {
- "worker": {
- "type": "belongsTo",
- "model": "Worker",
- "foreignKey": "workerFk"
- },
"absenceType": {
"type": "belongsTo",
"model": "AbsenceType",
diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js
index 692c8c735..0d94c788e 100644
--- a/modules/worker/back/models/worker.js
+++ b/modules/worker/back/models/worker.js
@@ -4,4 +4,7 @@ module.exports = Self => {
require('../methods/worker/isSubordinate')(Self);
require('../methods/worker/getWorkedHours')(Self);
require('../methods/worker/uploadFile')(Self);
+ require('../methods/worker/createAbsence')(Self);
+ require('../methods/worker/deleteAbsence')(Self);
+ require('../methods/worker/updateAbsence')(Self);
};
diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html
index 08b3f469c..197fb0797 100644
--- a/modules/worker/front/calendar/index.html
+++ b/modules/worker/front/calendar/index.html
@@ -5,15 +5,19 @@
-
-
+
+
+
+
@@ -26,12 +30,19 @@
-
+
+
{{absenceType.name}}
-
\ No newline at end of file
+
+
+
diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js
index 6b849d19f..32adc272d 100644
--- a/modules/worker/front/calendar/index.js
+++ b/modules/worker/front/calendar/index.js
@@ -43,15 +43,17 @@ class Controller extends Section {
set worker(value) {
this._worker = value;
- if (!value) return;
- let params = {
- workerFk: this.worker.id,
- started: this.started,
- ended: this.ended
- };
- this.$http.get(`WorkerCalendars/absences`, {params})
- .then(res => this.onData(res.data));
+ if (value) {
+ this.refresh().then(() => this.repaint());
+ this.getIsSubordinate();
+ }
+ }
+
+ getIsSubordinate() {
+ this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res =>
+ this.isSubordinate = res.data
+ );
}
onData(data) {
@@ -79,12 +81,12 @@ class Controller extends Section {
let type = absence.absenceType;
addEvent(absence.dated, {
name: type.name,
- color: type.rgb
+ color: type.rgb,
+ type: type.code,
+ absenceId: absence.id
});
});
}
-
- this.repaint();
}
repaint() {
@@ -102,6 +104,105 @@ class Controller extends Section {
dayNumber.style.backgroundColor = event.color;
dayNumber.style.color = 'rgba(0, 0, 0, 0.7)';
}
+
+ pick(absenceType) {
+ if (!this.isSubordinate) return;
+ if (absenceType == this.absenceType)
+ absenceType = null;
+
+ this.absenceType = absenceType;
+ }
+
+ onSelection($event, $days) {
+ if (!this.absenceType)
+ return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu'));
+
+ const day = $days[0];
+ const stamp = day.getTime();
+ const event = this.events[stamp];
+ const calendar = $event.target.closest('vn-calendar').$ctrl;
+
+ if (event) {
+ if (event.type == this.absenceType.code)
+ this.delete(calendar, day, event);
+ else
+ this.edit(calendar, event);
+ } else
+ this.create(calendar, day);
+ }
+
+ create(calendar, dated) {
+ const absenceType = this.absenceType;
+ const params = {
+ dated: dated,
+ absenceTypeId: absenceType.id
+ };
+
+ const path = `Workers/${this.$params.id}/createAbsence`;
+ this.$http.post(path, params).then(res => {
+ const newEvent = res.data;
+ this.events[dated.getTime()] = {
+ name: absenceType.name,
+ color: absenceType.rgb,
+ type: absenceType.code,
+ absenceId: newEvent.id
+ };
+
+ this.repaintCanceller(() =>
+ this.refresh().then(calendar.repaint())
+ );
+ });
+ }
+
+ edit(calendar, event) {
+ const absenceType = this.absenceType;
+ const params = {
+ absenceId: event.absenceId,
+ absenceTypeId: absenceType.id
+ };
+ const path = `Workers/${this.$params.id}/updateAbsence`;
+ this.$http.patch(path, params).then(() => {
+ event.color = absenceType.rgb;
+ event.name = absenceType.name;
+ event.type = absenceType.code;
+
+ this.repaintCanceller(() =>
+ this.refresh().then(calendar.repaint())
+ );
+ });
+ }
+
+ delete(calendar, day, event) {
+ const params = {absenceId: event.absenceId};
+ const path = `Workers/${this.$params.id}/deleteAbsence`;
+ this.$http.delete(path, {params}).then(() => {
+ delete this.events[day.getTime()];
+
+ this.repaintCanceller(() =>
+ this.refresh().then(calendar.repaint())
+ );
+ });
+ }
+
+ repaintCanceller(cb) {
+ if (this.canceller) {
+ clearTimeout(this.canceller);
+ this.canceller = null;
+ }
+
+ this.canceller = setTimeout(
+ () => cb(), 500);
+ }
+
+ refresh() {
+ const params = {
+ workerFk: this.worker.id,
+ started: this.started,
+ ended: this.ended
+ };
+ return this.$http.get(`WorkerCalendars/absences`, {params})
+ .then(res => this.onData(res.data));
+ }
}
ngModule.component('vnWorkerCalendar', {
diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js
index da49b8f0f..9d14cca20 100644
--- a/modules/worker/front/calendar/index.spec.js
+++ b/modules/worker/front/calendar/index.spec.js
@@ -3,17 +3,23 @@ import './index';
describe('Worker', () => {
describe('Component vnWorkerCalendar', () => {
let $httpBackend;
+ let $httpParamSerializer;
let $scope;
let controller;
let year = new Date().getFullYear();
beforeEach(ngModule('worker'));
- beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
+ beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
- const $element = angular.element('');
+ $httpParamSerializer = _$httpParamSerializer_;
+ const $element = angular.element('');
controller = $componentController('vnWorkerCalendar', {$element, $scope});
+ controller.isSubordinate = true;
+ controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'};
+ controller.$params.id = 106;
+ controller._worker = {id: 106};
}));
describe('started property', () => {
@@ -44,8 +50,9 @@ describe('Worker', () => {
describe('worker() setter', () => {
it(`should perform a get query and set the reponse data on the model`, () => {
- let today = new Date();
+ jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true);
+ let today = new Date();
let tomorrow = new Date(today.getTime());
tomorrow.setDate(tomorrow.getDate() + 1);
@@ -64,7 +71,7 @@ describe('Worker', () => {
]
});
- controller.worker = {id: 1};
+ controller.worker = {id: 107};
$httpBackend.flush();
let events = controller.events;
@@ -73,11 +80,14 @@ describe('Worker', () => {
expect(events[tomorrow.getTime()].name).toEqual('Easter');
expect(events[yesterday.getTime()].name).toEqual('Leave');
expect(events[yesterday.getTime()].color).toEqual('#bbb');
+ expect(controller.getIsSubordinate).toHaveBeenCalledWith();
});
});
describe('formatDay()', () => {
it(`should set the day element style`, () => {
+ jest.spyOn(controller, 'getIsSubordinate').mockReturnThis();
+
let today = new Date();
$httpBackend.whenRoute('GET', 'WorkerCalendars/absences')
@@ -99,5 +109,219 @@ describe('Worker', () => {
expect(dayNumber.style.backgroundColor).toEqual('rgb(0, 0, 0)');
});
});
+
+ describe('pick()', () => {
+ it(`should set the absenceType property to null if they match with the current one`, () => {
+ const absenceType = {id: 1, name: 'Holiday'};
+ controller.absenceType = absenceType;
+ controller.pick(absenceType);
+
+ expect(controller.absenceType).toBeNull();
+ });
+
+ it(`should set the absenceType property`, () => {
+ const absenceType = {id: 1, name: 'Holiday'};
+ const expectedAbsence = {id: 2, name: 'Leave of absence'};
+ controller.absenceType = absenceType;
+ controller.pick(expectedAbsence);
+
+ expect(controller.absenceType).toEqual(expectedAbsence);
+ });
+ });
+
+ describe('onSelection()', () => {
+ it(`should show an snackbar message if no absence type is selected`, () => {
+ jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis();
+
+ const $event = {};
+ const $days = [];
+ controller.absenceType = null;
+ controller.onSelection($event, $days);
+
+ expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu');
+ });
+
+ it(`should call to the create() method`, () => {
+ jest.spyOn(controller, 'create').mockReturnThis();
+
+ const selectedDay = new Date();
+ const $event = {
+ target: {
+ closest: () => {
+ return {$ctrl: {}};
+ }
+ }
+ };
+ const $days = [selectedDay];
+ controller.absenceType = {id: 1};
+ controller.onSelection($event, $days);
+
+ expect(controller.create).toHaveBeenCalledWith(jasmine.any(Object), selectedDay);
+ });
+
+ it(`should call to the delete() method`, () => {
+ jest.spyOn(controller, 'delete').mockReturnThis();
+
+ const selectedDay = new Date();
+ const expectedEvent = {
+ dated: selectedDay,
+ type: 'holiday'
+ };
+ const $event = {
+ target: {
+ closest: () => {
+ return {$ctrl: {}};
+ }
+ }
+ };
+ const $days = [selectedDay];
+ controller.events[selectedDay.getTime()] = expectedEvent;
+ controller.absenceType = {id: 1, code: 'holiday'};
+ controller.onSelection($event, $days);
+
+ expect(controller.delete).toHaveBeenCalledWith(jasmine.any(Object), selectedDay, expectedEvent);
+ });
+
+ it(`should call to the edit() method`, () => {
+ jest.spyOn(controller, 'edit').mockReturnThis();
+
+ const selectedDay = new Date();
+ const expectedEvent = {
+ dated: selectedDay,
+ type: 'leaveOfAbsence'
+ };
+ const $event = {
+ target: {
+ closest: () => {
+ return {$ctrl: {}};
+ }
+ }
+ };
+ const $days = [selectedDay];
+ controller.events[selectedDay.getTime()] = expectedEvent;
+ controller.absenceType = {id: 1, code: 'holiday'};
+ controller.onSelection($event, $days);
+
+ expect(controller.edit).toHaveBeenCalledWith(jasmine.any(Object), expectedEvent);
+ });
+ });
+
+ describe('create()', () => {
+ it(`should make a HTTP POST query and then call to the repaintCanceller() method`, () => {
+ jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
+
+ const dated = new Date();
+ const calendarElement = {};
+ const expectedResponse = {id: 10};
+
+ $httpBackend.expect('POST', `Workers/106/createAbsence`).respond(200, expectedResponse);
+ controller.create(calendarElement, dated);
+ $httpBackend.flush();
+
+ const createdEvent = controller.events[dated.getTime()];
+ const absenceType = controller.absenceType;
+
+ expect(createdEvent.absenceId).toEqual(expectedResponse.id);
+ expect(createdEvent.color).toEqual(absenceType.rgb);
+ expect(controller.repaintCanceller).toHaveBeenCalled();
+ });
+ });
+
+ describe('edit()', () => {
+ it(`should make a HTTP PATCH query and then call to the repaintCanceller() method`, () => {
+ jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
+
+ const event = {absenceId: 10};
+ const calendarElement = {};
+ const newAbsenceType = {
+ id: 2,
+ name: 'Leave of absence',
+ code: 'leaveOfAbsence',
+ rgb: 'purple'
+ };
+ controller.absenceType = newAbsenceType;
+
+ const expectedParams = {absenceId: 10, absenceTypeId: 2};
+ $httpBackend.expect('PATCH', `Workers/106/updateAbsence`, expectedParams).respond(200);
+ controller.edit(calendarElement, event);
+ $httpBackend.flush();
+
+ expect(event.name).toEqual(newAbsenceType.name);
+ expect(event.color).toEqual(newAbsenceType.rgb);
+ expect(event.type).toEqual(newAbsenceType.code);
+ expect(controller.repaintCanceller).toHaveBeenCalled();
+ });
+ });
+
+ describe('delete()', () => {
+ it(`should make a HTTP DELETE query and then call to the repaintCanceller() method`, () => {
+ jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
+
+ const expectedParams = {absenceId: 10};
+ const calendarElement = {};
+ const selectedDay = new Date();
+ const expectedEvent = {
+ dated: selectedDay,
+ type: 'leaveOfAbsence',
+ absenceId: 10
+ };
+
+ controller.events[selectedDay.getTime()] = expectedEvent;
+
+ const serializedParams = $httpParamSerializer(expectedParams);
+ $httpBackend.expect('DELETE', `Workers/106/deleteAbsence?${serializedParams}`).respond(200);
+ controller.delete(calendarElement, selectedDay, expectedEvent);
+ $httpBackend.flush();
+
+ const event = controller.events[selectedDay.getTime()];
+
+ expect(event).toBeUndefined();
+ expect(controller.repaintCanceller).toHaveBeenCalled();
+ });
+ });
+
+ describe('repaintCanceller()', () => {
+ it(`should cancell the callback execution timer`, () => {
+ jest.spyOn(window, 'clearTimeout');
+ jest.spyOn(window, 'setTimeout');
+
+ const timeoutId = 90;
+ controller.canceller = timeoutId;
+
+ controller.repaintCanceller(() => {
+ return 'My callback';
+ });
+
+ expect(window.clearTimeout).toHaveBeenCalledWith(timeoutId);
+ expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 500);
+ });
+ });
+
+ describe('refresh()', () => {
+ it(`should make a HTTP GET query and then call to the onData() method`, () => {
+ jest.spyOn(controller, 'onData').mockReturnThis();
+
+ const dated = controller.date;
+ const started = new Date(dated.getTime());
+ started.setMonth(0);
+ started.setDate(1);
+
+ const ended = new Date(dated.getTime());
+ ended.setMonth(12);
+ ended.setDate(0);
+
+ controller.started = started;
+ controller.ended = ended;
+
+ const expecteResponse = [{id: 1}];
+ const expectedParams = {workerFk: 106, started: started, ended: ended};
+ const serializedParams = $httpParamSerializer(expectedParams);
+ $httpBackend.expect('GET', `WorkerCalendars/absences?${serializedParams}`).respond(200, expecteResponse);
+ controller.refresh();
+ $httpBackend.flush();
+
+ expect(controller.onData).toHaveBeenCalledWith(expecteResponse);
+ });
+ });
});
});
diff --git a/modules/worker/front/calendar/locale/es.yml b/modules/worker/front/calendar/locale/es.yml
index 82939ce91..6681f730f 100644
--- a/modules/worker/front/calendar/locale/es.yml
+++ b/modules/worker/front/calendar/locale/es.yml
@@ -2,4 +2,6 @@ Calendar: Calendario
Holidays: Vacaciones
Used: Utilizados
of: de
-days: días
\ No newline at end of file
+days: días
+Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha
+To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia
\ No newline at end of file
diff --git a/modules/worker/front/calendar/style.scss b/modules/worker/front/calendar/style.scss
index 9b3fc749b..1934a6ca0 100644
--- a/modules/worker/front/calendar/style.scss
+++ b/modules/worker/front/calendar/style.scss
@@ -2,6 +2,7 @@
vn-worker-calendar {
.calendars {
+ position: relative;
display: flex;
flex-wrap: wrap;
justify-content: center;
@@ -16,4 +17,23 @@ vn-worker-calendar {
max-width: 288px;
}
}
+
+ vn-chip.selectable {
+ cursor: pointer
+ }
+
+ vn-chip.selectable:hover {
+ opacity: 0.8
+ }
+
+ vn-chip vn-avatar {
+ text-align: center;
+ color: white
+ }
+
+ vn-icon[icon="info"] {
+ position: absolute;
+ top: 16px;
+ right: 16px
+ }
}