diff --git a/e2e/paths/03-worker/05_calendar.spec.js b/e2e/paths/03-worker/05_calendar.spec.js index 08ef71f13..1a7e2dd19 100644 --- a/e2e/paths/03-worker/05_calendar.spec.js +++ b/e2e/paths/03-worker/05_calendar.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -describe('Worker calendar path', () => { +fdescribe('Worker calendar path', () => { let reasonableTimeBetweenClicks = 400; let browser; let page; diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 89e301fca..e2194b8ad 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -180,5 +180,6 @@ "This genus already exist": "Este genus 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}}})", - "None": "Ninguno" + "None": "Ninguno", + "The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada" } \ No newline at end of file diff --git a/modules/worker/back/methods/calendar/absences.js b/modules/worker/back/methods/calendar/absences.js index b84b55843..83761242f 100644 --- a/modules/worker/back/methods/calendar/absences.js +++ b/modules/worker/back/methods/calendar/absences.js @@ -4,18 +4,13 @@ module.exports = Self => { Self.remoteMethodCtx('absences', { description: 'Returns an array of absences from an specified worker', accepts: [{ - arg: 'workerFk', - type: 'Number', + arg: 'businessFk', + type: 'number', required: true, }, { - arg: 'started', - type: 'Date', - required: true, - }, - { - arg: 'ended', - type: 'Date', + arg: 'year', + type: 'date', required: true, }], returns: [{ @@ -23,11 +18,11 @@ module.exports = Self => { }, { arg: 'absences', - type: 'Number' + type: 'number' }, { arg: 'holidays', - type: 'Number' + type: 'number' }], http: { 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 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 holidays = []; // Get active contracts on current year - const year = yearStarted.getFullYear(); - const contracts = await models.WorkerLabour.find({ + // 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: { @@ -67,27 +68,20 @@ module.exports = Self => { relation: 'type' }], where: { - dated: {between: [yearStarted, yearEnded]} + dated: {between: [started, ended]} } } } } }], - where: { - and: [ - {workerFk: workerFk}, - {or: [{ - ended: {gte: [yearStarted]} - }, {ended: null}]} - ], - - } + where: {businessFk} }); - // Contracts ids - const contractsId = contracts.map(contract => { - return contract.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 let absences = await Self.find({ @@ -95,8 +89,8 @@ module.exports = Self => { relation: 'absenceType' }, where: { - businessFk: {inq: contractsId}, - dated: {between: [yearStarted, yearEnded]} + businessFk: contract.businessFk, + dated: {between: [started, ended]} } }); @@ -119,44 +113,37 @@ module.exports = Self => { // Get number of worked days let workedDays = 0; - contracts.forEach(contract => { - const started = contract.started; - const ended = contract.ended; - const startedTime = started.getTime(); - const endedTime = ended && ended.getTime() || yearEnded; - const dayTimestamp = 1000 * 60 * 60 * 24; + 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); + workedDays += Math.floor((endedTime - startedTime) / dayTimestamp); - if (workedDays > daysInYear()) - workedDays = daysInYear(); + 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); + // 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 currentContract = contracts.find(contract => { - return contract.started <= new Date() - && (contract.ended >= new Date() || contract.ended == null); - }); - - if (currentContract) { - const maxHolidays = currentContract.holidays() && currentContract.holidays().days; - calendar.totalHolidays = maxHolidays; - - workedDays -= entitlementRate; - - if (workedDays < daysInYear()) - calendar.totalHolidays = Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2; + 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 = yearStarted.getFullYear(); + const year = started.getFullYear(); return isLeapYear(year) ? 366 : 365; } diff --git a/modules/worker/back/methods/worker-labour/holidays.js b/modules/worker/back/methods/worker-labour/holidays.js new file mode 100644 index 000000000..29480b301 --- /dev/null +++ b/modules/worker/back/methods/worker-labour/holidays.js @@ -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); + } +}; diff --git a/modules/worker/back/methods/worker/activeContract.js b/modules/worker/back/methods/worker/activeContract.js new file mode 100644 index 000000000..05dcee6b5 --- /dev/null +++ b/modules/worker/back/methods/worker/activeContract.js @@ -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} + ] + } + ] + } + }); + }; +}; diff --git a/modules/worker/back/methods/worker/contracts.js b/modules/worker/back/methods/worker/contracts.js new file mode 100644 index 000000000..4a38cac8f --- /dev/null +++ b/modules/worker/back/methods/worker/contracts.js @@ -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); + }; +}; diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js index cc0ee13a6..e327701b3 100644 --- a/modules/worker/back/methods/worker/createAbsence.js +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -5,19 +5,24 @@ module.exports = Self => { description: 'Creates a new worker absence', accepts: [{ arg: 'id', - type: 'Number', + type: 'number', description: 'The worker id', http: {source: 'path'} }, + { + arg: 'businessFk', + type: 'number', + required: true + }, { arg: 'absenceTypeId', - type: 'Number', + type: 'number', required: true }, { arg: 'dated', - type: 'Date', - required: false + type: 'date', + required: true }], returns: { 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 $t = ctx.req.__; // $translate const userId = ctx.req.accessToken.userId; @@ -39,18 +44,13 @@ module.exports = Self => { if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); - const labour = await models.WorkerLabour.findOne({ - include: {relation: 'department'}, - where: { - and: [ - {workerFk: id}, - {or: [{ - ended: {gte: [dated]} - }, {ended: null}]} - ] - } + const labour = await models.WorkerLabour.findById(businessFk, { + include: {relation: 'department'} }); + 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({ businessFk: labour.businessFk, dayOffTypeFk: absenceTypeId, diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 9bfef5235..029bd3256 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -10,4 +10,6 @@ module.exports = Self => { require('../methods/worker/active')(Self); require('../methods/worker/activeWithRole')(Self); require('../methods/worker/activeWithInheritedRole')(Self); + require('../methods/worker/contracts')(Self); + require('../methods/worker/activeContract')(Self); }; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index f10201763..f1359bb7d 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -22,13 +22,22 @@
-
-
Holidays
+
+
Current contract
{{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}} {{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}}
+ +
+
{{'Year' | translate}} {{$ctrl.year}}
+
+ {{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}} + {{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}} +
+
+
+ + +
ID: {{businessFk}}
+
+ {{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}} +
+
+
this.repaint()); + // this.refresh().then(() => this.repaint()); this.getIsSubordinate(); + this.getActiveContract(); + } + } + + get businessId() { + return this._businessId; + } + + set businessId(value) { + this._businessId = value; + if (value) { + this.refresh() + .then(() => this.repaint()); } } buildYearFilter() { - const currentYear = new Date().getFullYear(); - const minRange = currentYear - 5; + const now = new Date(); + now.setFullYear(now.getFullYear() + 1); + + const maxYear = now.getFullYear(); + const minRange = maxYear - 5; const years = []; - for (let i = currentYear; i > minRange; i--) + for (let i = maxYear; i > minRange; i--) years.push({year: i}); this.yearFilter = years; } getIsSubordinate() { - this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res => - this.isSubordinate = res.data - ); + this.$http.get(`Workers/${this.worker.id}/isSubordinate`) + .then(res => this.isSubordinate = res.data); + } + + getActiveContract() { + this.$http.get(`Workers/${this.worker.id}/activeContract`) + .then(res => this.businessId = res.data.businessFk); } onData(data) { @@ -176,7 +196,8 @@ class Controller extends Section { const absenceType = this.absenceType; const params = { dated: dated, - absenceTypeId: absenceType.id + absenceTypeId: absenceType.id, + businessFk: this.businessId }; const path = `Workers/${this.$params.id}/createAbsence`; @@ -237,9 +258,8 @@ class Controller extends Section { refresh() { const params = { - workerFk: this.worker.id, - started: this.started, - ended: this.ended + businessFk: this.businessId, + year: this.year }; return this.$http.get(`Calendars/absences`, {params}) .then(res => this.onData(res.data));