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],
|
$days: [day],
|
||||||
$type: '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",
|
"base": "VnModel",
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
"table": "workerCalendar"
|
"table": "workerCalendar2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"businessFk": {
|
"id": {
|
||||||
"id": 1,
|
"id": true,
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
},
|
},
|
||||||
"workerFk": {
|
"businessFk": {
|
||||||
"id": 2,
|
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
},
|
},
|
||||||
"dated": {
|
"dated": {
|
||||||
"id": 3,
|
|
||||||
"type": "Date"
|
"type": "Date"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
"worker": {
|
|
||||||
"type": "belongsTo",
|
|
||||||
"model": "Worker",
|
|
||||||
"foreignKey": "workerFk"
|
|
||||||
},
|
|
||||||
"absenceType": {
|
"absenceType": {
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
"model": "AbsenceType",
|
"model": "AbsenceType",
|
||||||
|
|
|
@ -4,4 +4,7 @@ module.exports = Self => {
|
||||||
require('../methods/worker/isSubordinate')(Self);
|
require('../methods/worker/isSubordinate')(Self);
|
||||||
require('../methods/worker/getWorkedHours')(Self);
|
require('../methods/worker/getWorkedHours')(Self);
|
||||||
require('../methods/worker/uploadFile')(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>
|
</vn-crud-model>
|
||||||
<div class="vn-w-lg">
|
<div class="vn-w-lg">
|
||||||
<vn-card class="vn-pa-sm calendars">
|
<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
|
<vn-calendar
|
||||||
ng-repeat="month in $ctrl.months"
|
ng-repeat="month in $ctrl.months"
|
||||||
data="$ctrl.events"
|
data="$ctrl.events"
|
||||||
|
@ -12,7 +15,8 @@
|
||||||
format-day="$ctrl.formatDay($day, $element)"
|
format-day="$ctrl.formatDay($day, $element)"
|
||||||
display-controls="false"
|
display-controls="false"
|
||||||
hide-contiguous="true"
|
hide-contiguous="true"
|
||||||
hide-year="true">
|
hide-year="true"
|
||||||
|
on-selection="$ctrl.onSelection($event, $days)">
|
||||||
</vn-calendar>
|
</vn-calendar>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,12 +30,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="vn-pt-md" style="overflow: hidden;">
|
<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
|
<vn-avatar
|
||||||
ng-style="{backgroundColor: absenceType.rgb}">
|
ng-style="{backgroundColor: absenceType.rgb}">
|
||||||
|
<vn-icon icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
|
||||||
</vn-avatar>
|
</vn-avatar>
|
||||||
{{absenceType.name}}
|
{{absenceType.name}}
|
||||||
</vn-chip>
|
</vn-chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</vn-side-menu>
|
</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) {
|
set worker(value) {
|
||||||
this._worker = value;
|
this._worker = value;
|
||||||
if (!value) return;
|
|
||||||
|
|
||||||
let params = {
|
if (value) {
|
||||||
workerFk: this.worker.id,
|
this.refresh().then(() => this.repaint());
|
||||||
started: this.started,
|
this.getIsSubordinate();
|
||||||
ended: this.ended
|
}
|
||||||
};
|
}
|
||||||
this.$http.get(`WorkerCalendars/absences`, {params})
|
|
||||||
.then(res => this.onData(res.data));
|
getIsSubordinate() {
|
||||||
|
this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res =>
|
||||||
|
this.isSubordinate = res.data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onData(data) {
|
onData(data) {
|
||||||
|
@ -79,12 +81,12 @@ class Controller extends Section {
|
||||||
let type = absence.absenceType;
|
let type = absence.absenceType;
|
||||||
addEvent(absence.dated, {
|
addEvent(absence.dated, {
|
||||||
name: type.name,
|
name: type.name,
|
||||||
color: type.rgb
|
color: type.rgb,
|
||||||
|
type: type.code,
|
||||||
|
absenceId: absence.id
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.repaint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repaint() {
|
repaint() {
|
||||||
|
@ -102,6 +104,105 @@ class Controller extends Section {
|
||||||
dayNumber.style.backgroundColor = event.color;
|
dayNumber.style.backgroundColor = event.color;
|
||||||
dayNumber.style.color = 'rgba(0, 0, 0, 0.7)';
|
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', {
|
ngModule.component('vnWorkerCalendar', {
|
||||||
|
|
|
@ -3,17 +3,23 @@ import './index';
|
||||||
describe('Worker', () => {
|
describe('Worker', () => {
|
||||||
describe('Component vnWorkerCalendar', () => {
|
describe('Component vnWorkerCalendar', () => {
|
||||||
let $httpBackend;
|
let $httpBackend;
|
||||||
|
let $httpParamSerializer;
|
||||||
let $scope;
|
let $scope;
|
||||||
let controller;
|
let controller;
|
||||||
let year = new Date().getFullYear();
|
let year = new Date().getFullYear();
|
||||||
|
|
||||||
beforeEach(ngModule('worker'));
|
beforeEach(ngModule('worker'));
|
||||||
|
|
||||||
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
|
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
|
||||||
$scope = $rootScope.$new();
|
$scope = $rootScope.$new();
|
||||||
$httpBackend = _$httpBackend_;
|
$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 = $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', () => {
|
describe('started property', () => {
|
||||||
|
@ -44,8 +50,9 @@ describe('Worker', () => {
|
||||||
|
|
||||||
describe('worker() setter', () => {
|
describe('worker() setter', () => {
|
||||||
it(`should perform a get query and set the reponse data on the model`, () => {
|
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());
|
let tomorrow = new Date(today.getTime());
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
|
|
||||||
|
@ -64,7 +71,7 @@ describe('Worker', () => {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.worker = {id: 1};
|
controller.worker = {id: 107};
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
let events = controller.events;
|
let events = controller.events;
|
||||||
|
@ -73,11 +80,14 @@ describe('Worker', () => {
|
||||||
expect(events[tomorrow.getTime()].name).toEqual('Easter');
|
expect(events[tomorrow.getTime()].name).toEqual('Easter');
|
||||||
expect(events[yesterday.getTime()].name).toEqual('Leave');
|
expect(events[yesterday.getTime()].name).toEqual('Leave');
|
||||||
expect(events[yesterday.getTime()].color).toEqual('#bbb');
|
expect(events[yesterday.getTime()].color).toEqual('#bbb');
|
||||||
|
expect(controller.getIsSubordinate).toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('formatDay()', () => {
|
describe('formatDay()', () => {
|
||||||
it(`should set the day element style`, () => {
|
it(`should set the day element style`, () => {
|
||||||
|
jest.spyOn(controller, 'getIsSubordinate').mockReturnThis();
|
||||||
|
|
||||||
let today = new Date();
|
let today = new Date();
|
||||||
|
|
||||||
$httpBackend.whenRoute('GET', 'WorkerCalendars/absences')
|
$httpBackend.whenRoute('GET', 'WorkerCalendars/absences')
|
||||||
|
@ -99,5 +109,219 @@ describe('Worker', () => {
|
||||||
expect(dayNumber.style.backgroundColor).toEqual('rgb(0, 0, 0)');
|
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
|
Used: Utilizados
|
||||||
of: de
|
of: de
|
||||||
days: días
|
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 {
|
vn-worker-calendar {
|
||||||
.calendars {
|
.calendars {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -16,4 +17,23 @@ vn-worker-calendar {
|
||||||
max-width: 288px;
|
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