This commit is contained in:
Joan Sanchez 2021-06-03 11:00:40 +02:00
parent e2ec78461a
commit 3fcd78e1f3
10 changed files with 375 additions and 96 deletions

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Worker calendar path', () => { fdescribe('Worker calendar path', () => {
let reasonableTimeBetweenClicks = 400; let reasonableTimeBetweenClicks = 400;
let browser; let browser;
let page; let page;

View File

@ -180,5 +180,6 @@
"This genus already exist": "Este genus ya existe", "This genus already exist": "Este genus ya existe",
"This specie already exist": "Esta especie ya existe", "This specie already exist": "Esta especie ya existe",
"Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})", "Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "Ninguno" "None": "Ninguno",
"The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada"
} }

View File

@ -4,18 +4,13 @@ module.exports = Self => {
Self.remoteMethodCtx('absences', { Self.remoteMethodCtx('absences', {
description: 'Returns an array of absences from an specified worker', description: 'Returns an array of absences from an specified worker',
accepts: [{ accepts: [{
arg: 'workerFk', arg: 'businessFk',
type: 'Number', type: 'number',
required: true, required: true,
}, },
{ {
arg: 'started', arg: 'year',
type: 'Date', type: 'date',
required: true,
},
{
arg: 'ended',
type: 'Date',
required: true, required: true,
}], }],
returns: [{ returns: [{
@ -23,11 +18,11 @@ module.exports = Self => {
}, },
{ {
arg: 'absences', arg: 'absences',
type: 'Number' type: 'number'
}, },
{ {
arg: 'holidays', arg: 'holidays',
type: 'Number' type: 'number'
}], }],
http: { http: {
path: `/absences`, path: `/absences`,
@ -35,19 +30,25 @@ module.exports = Self => {
} }
}); });
Self.absences = async(ctx, workerFk, yearStarted, yearEnded) => { Self.absences = async(ctx, businessFk, year) => {
const models = Self.app.models; const models = Self.app.models;
const isSubordinate = await models.Worker.isSubordinate(ctx, workerFk);
if (!isSubordinate)
throw new UserError(`You don't have enough privileges`);
const calendar = {totalHolidays: 0, holidaysEnjoyed: 0}; const calendar = {totalHolidays: 0, holidaysEnjoyed: 0};
const holidays = []; const holidays = [];
// Get active contracts on current year // Get active contracts on current year
const year = yearStarted.getFullYear(); // const year = yearStarted.getFullYear();
const contracts = await models.WorkerLabour.find({
const started = new Date();
started.setFullYear(year);
started.setMonth(0);
started.setDate(1);
const ended = new Date();
ended.setFullYear(year);
ended.setMonth(12);
ended.setDate(0);
const contract = await models.WorkerLabour.findOne({
include: [{ include: [{
relation: 'holidays', relation: 'holidays',
scope: { scope: {
@ -67,27 +68,20 @@ module.exports = Self => {
relation: 'type' relation: 'type'
}], }],
where: { where: {
dated: {between: [yearStarted, yearEnded]} dated: {between: [started, ended]}
} }
} }
} }
} }
}], }],
where: { where: {businessFk}
and: [
{workerFk: workerFk},
{or: [{
ended: {gte: [yearStarted]}
}, {ended: null}]}
],
}
}); });
// Contracts ids if (!contract) return;
const contractsId = contracts.map(contract => {
return contract.businessFk; const isSubordinate = await models.Worker.isSubordinate(ctx, contract.workerFk);
}); if (!isSubordinate)
throw new UserError(`You don't have enough privileges`);
// Get absences of year // Get absences of year
let absences = await Self.find({ let absences = await Self.find({
@ -95,8 +89,8 @@ module.exports = Self => {
relation: 'absenceType' relation: 'absenceType'
}, },
where: { where: {
businessFk: {inq: contractsId}, businessFk: contract.businessFk,
dated: {between: [yearStarted, yearEnded]} dated: {between: [started, ended]}
} }
}); });
@ -119,11 +113,11 @@ module.exports = Self => {
// Get number of worked days // Get number of worked days
let workedDays = 0; let workedDays = 0;
contracts.forEach(contract => { const contractStarted = contract.started;
const started = contract.started; const contractEnded = contract.ended;
const ended = contract.ended; // esta mal, la fecha de inicio puede ser un año anterior...
const startedTime = started.getTime(); const startedTime = contractStarted < started ? started.getTime() : contractStarted.getTime();
const endedTime = ended && ended.getTime() || yearEnded; const endedTime = contractEnded && contractEnded.getTime() || ended;
const dayTimestamp = 1000 * 60 * 60 * 24; const dayTimestamp = 1000 * 60 * 60 * 24;
workedDays += Math.floor((endedTime - startedTime) / dayTimestamp); workedDays += Math.floor((endedTime - startedTime) / dayTimestamp);
@ -139,24 +133,17 @@ module.exports = Self => {
holidays.push(day); holidays.push(day);
} }
});
const currentContract = contracts.find(contract => {
return contract.started <= new Date()
&& (contract.ended >= new Date() || contract.ended == null);
});
if (currentContract) { const maxHolidays = contract.holidays() && contract.holidays().days;
const maxHolidays = currentContract.holidays() && currentContract.holidays().days;
calendar.totalHolidays = maxHolidays; calendar.totalHolidays = maxHolidays;
workedDays -= entitlementRate; workedDays -= entitlementRate;
if (workedDays < daysInYear()) if (workedDays < daysInYear())
calendar.totalHolidays = Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2; calendar.totalHolidays = Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2;
}
function daysInYear() { function daysInYear() {
const year = yearStarted.getFullYear(); const year = started.getFullYear();
return isLeapYear(year) ? 366 : 365; return isLeapYear(year) ? 366 : 365;
} }

View File

@ -0,0 +1,155 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('absences', {
description: 'Returns an array of absences from an specified worker',
accepts: [{
arg: 'businessFk',
type: 'number',
required: true,
},
{
arg: 'year',
type: 'date',
required: true,
}],
returns: [{
arg: 'calendar'
},
{
arg: 'absences',
type: 'number'
},
{
arg: 'holidays',
type: 'number'
}],
http: {
path: `/absences`,
verb: 'GET'
}
});
Self.absences = async(ctx, businessFk, year) => {
const models = Self.app.models;
const calendar = {totalHolidays: 0, holidaysEnjoyed: 0};
const holidays = [];
// Get active contracts on current year
// const year = yearStarted.getFullYear();
const started = new Date();
started.setFullYear(year);
started.setMonth(0);
started.setDate(1);
const ended = new Date();
ended.setFullYear(year);
ended.setMonth(12);
ended.setDate(0);
const contract = await models.WorkerLabour.findOne({
include: [{
relation: 'holidays',
scope: {
where: {year}
}
},
{
relation: 'workCenter',
scope: {
include: {
relation: 'holidays',
scope: {
include: [{
relation: 'detail'
},
{
relation: 'type'
}],
where: {
dated: {between: [started, ended]}
}
}
}
}
}],
where: {businessFk}
});
if (!contract) return;
const isSubordinate = await models.Worker.isSubordinate(ctx, contract.workerFk);
if (!isSubordinate)
throw new UserError(`You don't have enough privileges`);
// Get absences of year
const absences = await Self.find({
include: {
relation: 'absenceType'
},
where: {
businessFk: contract.businessFk,
dated: {between: [started, ended]}
}
});
let entitlementRate = 0;
absences.forEach(absence => {
const absenceType = absence.absenceType();
const isHoliday = absenceType.code === 'holiday';
const isHalfHoliday = absenceType.code === 'halfHoliday';
if (isHoliday) calendar.holidaysEnjoyed += 1;
if (isHalfHoliday) calendar.holidaysEnjoyed += 0.5;
entitlementRate += absenceType.holidayEntitlementRate;
absence.dated = new Date(absence.dated);
absence.dated.setHours(0, 0, 0, 0);
});
// Get number of worked days
let workedDays = 0;
const contractStarted = contract.started;
const contractEnded = contract.ended;
// esta mal, la fecha de inicio puede ser un año anterior...
const startedTime = contractStarted < started ? started.getTime() : contractStarted.getTime();
const endedTime = contractEnded && contractEnded.getTime() || ended;
const dayTimestamp = 1000 * 60 * 60 * 24;
workedDays += Math.floor((endedTime - startedTime) / dayTimestamp);
if (workedDays > daysInYear())
workedDays = daysInYear();
// Workcenter holidays
let holidayList = contract.workCenter().holidays();
for (let day of holidayList) {
day.dated = new Date(day.dated);
day.dated.setHours(0, 0, 0, 0);
holidays.push(day);
}
const maxHolidays = contract.holidays() && contract.holidays().days;
calendar.totalHolidays = maxHolidays;
workedDays -= entitlementRate;
if (workedDays < daysInYear())
calendar.totalHolidays = Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2;
function daysInYear() {
const year = started.getFullYear();
return isLeapYear(year) ? 366 : 365;
}
return [calendar, absences, holidays];
};
function isLeapYear(year) {
return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
}
};

View File

@ -0,0 +1,47 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('activeContract', {
description: 'Returns an array of contracts from an specified worker',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The worker id',
http: {source: 'path'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/activeContract`,
verb: 'GET'
}
});
Self.activeContract = async(ctx, id) => {
const models = Self.app.models;
const isSubordinate = await models.Worker.isSubordinate(ctx, id);
if (!isSubordinate)
throw new UserError(`You don't have enough privileges`);
const now = new Date();
return models.WorkerLabour.findOne({
where: {
and: [
{workerFk: id},
{started: {lte: now}},
{
or: [
{ended: {gte: now}},
{ended: null}
]
}
]
}
});
};
};

View File

@ -0,0 +1,43 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('contracts', {
description: 'Returns an array of contracts from an specified worker',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'Object',
description: 'Filter defining where and paginated data',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/contracts`,
verb: 'GET'
}
});
Self.contracts = async(ctx, id, filter) => {
const models = Self.app.models;
const isSubordinate = await models.Worker.isSubordinate(ctx, id);
if (!isSubordinate)
throw new UserError(`You don't have enough privileges`);
if (!filter.where) filter.where = {};
const where = filter.where;
where['workerFk'] = id;
return models.WorkerLabour.find(filter);
};
};

View File

@ -5,19 +5,24 @@ module.exports = Self => {
description: 'Creates a new worker absence', description: 'Creates a new worker absence',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The worker id', description: 'The worker id',
http: {source: 'path'} http: {source: 'path'}
}, },
{
arg: 'businessFk',
type: 'number',
required: true
},
{ {
arg: 'absenceTypeId', arg: 'absenceTypeId',
type: 'Number', type: 'number',
required: true required: true
}, },
{ {
arg: 'dated', arg: 'dated',
type: 'Date', type: 'date',
required: false required: true
}], }],
returns: { returns: {
type: 'Object', type: 'Object',
@ -29,7 +34,7 @@ module.exports = Self => {
} }
}); });
Self.createAbsence = async(ctx, id, absenceTypeId, dated) => { Self.createAbsence = async(ctx, id, businessFk, absenceTypeId, dated) => {
const models = Self.app.models; const models = Self.app.models;
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
@ -39,18 +44,13 @@ module.exports = Self => {
if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss))
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const labour = await models.WorkerLabour.findOne({ const labour = await models.WorkerLabour.findById(businessFk, {
include: {relation: 'department'}, include: {relation: 'department'}
where: {
and: [
{workerFk: id},
{or: [{
ended: {gte: [dated]}
}, {ended: null}]}
]
}
}); });
if (dated < labour.started || (labour.ended != null && dated > labour.ended))
throw new UserError(`The contract was not active during the selected date`);
const absence = await models.Calendar.create({ const absence = await models.Calendar.create({
businessFk: labour.businessFk, businessFk: labour.businessFk,
dayOffTypeFk: absenceTypeId, dayOffTypeFk: absenceTypeId,

View File

@ -10,4 +10,6 @@ module.exports = Self => {
require('../methods/worker/active')(Self); require('../methods/worker/active')(Self);
require('../methods/worker/activeWithRole')(Self); require('../methods/worker/activeWithRole')(Self);
require('../methods/worker/activeWithInheritedRole')(Self); require('../methods/worker/activeWithInheritedRole')(Self);
require('../methods/worker/contracts')(Self);
require('../methods/worker/activeContract')(Self);
}; };

View File

@ -22,13 +22,22 @@
</div> </div>
<vn-side-menu side="right"> <vn-side-menu side="right">
<div class="vn-pa-md"> <div class="vn-pa-md">
<div class="totalBox" style="text-align: center;"> <div class="totalBox vn-mb-sm" style="text-align: center;">
<h6 translate>Holidays</h6> <h6 translate>Current contract</h6>
<div> <div>
{{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}} {{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}}
{{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}} {{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}}
</div> </div>
</div> </div>
<div class="totalBox" style="text-align: center;">
<h6>{{'Year' | translate}} {{$ctrl.year}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}}
{{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}}
</div>
</div>
<div class="vn-pt-md"> <div class="vn-pt-md">
<vn-autocomplete label="Year" <vn-autocomplete label="Year"
data="$ctrl.yearFilter" data="$ctrl.yearFilter"
@ -37,6 +46,21 @@
value-field="year" value-field="year"
order="DESC"> order="DESC">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete label="Contract"
url="Workers/{{$ctrl.$params.id}}/contracts"
fields="['started', 'ended']"
ng-model="$ctrl.businessId"
search-function="{businessFk: $search}"
value-field="businessFk"
order="businessFk DESC"
limit="5">
<tpl-item>
<div>ID: {{businessFk}}</div>
<div class="text-caption text-secondary">
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
</div>
</tpl-item>
</vn-autocomplete>
</div> </div>
<div class="input vn-py-md" style="overflow: hidden;"> <div class="input vn-py-md" style="overflow: hidden;">
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" <vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}"

View File

@ -59,26 +59,46 @@ class Controller extends Section {
this._worker = value; this._worker = value;
if (value) { if (value) {
this.refresh().then(() => this.repaint()); // this.refresh().then(() => this.repaint());
this.getIsSubordinate(); this.getIsSubordinate();
this.getActiveContract();
}
}
get businessId() {
return this._businessId;
}
set businessId(value) {
this._businessId = value;
if (value) {
this.refresh()
.then(() => this.repaint());
} }
} }
buildYearFilter() { buildYearFilter() {
const currentYear = new Date().getFullYear(); const now = new Date();
const minRange = currentYear - 5; now.setFullYear(now.getFullYear() + 1);
const maxYear = now.getFullYear();
const minRange = maxYear - 5;
const years = []; const years = [];
for (let i = currentYear; i > minRange; i--) for (let i = maxYear; i > minRange; i--)
years.push({year: i}); years.push({year: i});
this.yearFilter = years; this.yearFilter = years;
} }
getIsSubordinate() { getIsSubordinate() {
this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res => this.$http.get(`Workers/${this.worker.id}/isSubordinate`)
this.isSubordinate = res.data .then(res => this.isSubordinate = res.data);
); }
getActiveContract() {
this.$http.get(`Workers/${this.worker.id}/activeContract`)
.then(res => this.businessId = res.data.businessFk);
} }
onData(data) { onData(data) {
@ -176,7 +196,8 @@ class Controller extends Section {
const absenceType = this.absenceType; const absenceType = this.absenceType;
const params = { const params = {
dated: dated, dated: dated,
absenceTypeId: absenceType.id absenceTypeId: absenceType.id,
businessFk: this.businessId
}; };
const path = `Workers/${this.$params.id}/createAbsence`; const path = `Workers/${this.$params.id}/createAbsence`;
@ -237,9 +258,8 @@ class Controller extends Section {
refresh() { refresh() {
const params = { const params = {
workerFk: this.worker.id, businessFk: this.businessId,
started: this.started, year: this.year
ended: this.ended
}; };
return this.$http.get(`Calendars/absences`, {params}) return this.$http.get(`Calendars/absences`, {params})
.then(res => this.onData(res.data)); .then(res => this.onData(res.data));