Worker calendar - Add absence #336
|
@ -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;
|
||||
;
|
|
@ -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`;
|
|
@ -135,7 +135,7 @@ export default class Calendar extends FormInput {
|
|||
$days: [day],
|
||||
$type: 'day'
|
||||
});
|
||||
this.repaint();
|
||||
// this.repaint();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
});
|
||||
};
|
||||
};
|
|
@ -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();
|
||||
};
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
};
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
</vn-crud-model>
|
||||
<div class="vn-w-lg">
|
||||
<vn-card class="vn-pa-sm calendars">
|
||||
<vn-icon ng-if="::$ctrl.isSubordinate" icon="info" color-marginal
|
||||
vn-tooltip="To start adding absences, click an absence type from the right menu and then on the day you want to add an absence">
|
||||
</vn-icon>
|
||||
<vn-calendar
|
||||
ng-repeat="month in $ctrl.months"
|
||||
data="$ctrl.events"
|
||||
|
@ -12,7 +15,8 @@
|
|||
format-day="$ctrl.formatDay($day, $element)"
|
||||
display-controls="false"
|
||||
hide-contiguous="true"
|
||||
hide-year="true">
|
||||
hide-year="true"
|
||||
on-selection="$ctrl.onSelection($event, $days)">
|
||||
</vn-calendar>
|
||||
</vn-card>
|
||||
</div>
|
||||
|
@ -26,12 +30,19 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="vn-pt-md" style="overflow: hidden;">
|
||||
<vn-chip ng-repeat="absenceType in absenceTypes">
|
||||
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}"
|
||||
ng-click="$ctrl.pick(absenceType)">
|
||||
<vn-avatar
|
||||
ng-style="{backgroundColor: absenceType.rgb}">
|
||||
<vn-icon icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
|
||||
</vn-avatar>
|
||||
{{absenceType.name}}
|
||||
</vn-chip>
|
||||
</div>
|
||||
</div>
|
||||
</vn-side-menu>
|
||||
<vn-confirm
|
||||
vn-id="confirm"
|
||||
message="This item will be deleted"
|
||||
question="Are you sure you want to continue?">
|
||||
</vn-confirm>
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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('<vn-worker-calencar></vn-worker-calencar>');
|
||||
$httpParamSerializer = _$httpParamSerializer_;
|
||||
const $element = angular.element('<vn-worker-calendar></vn-worker-calendar>');
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,3 +3,5 @@ Holidays: Vacaciones
|
|||
Used: Utilizados
|
||||
of: de
|
||||
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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue