From 3fcd78e1f3ca5c62cbb4ca35573872d8f2eff207 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 3 Jun 2021 11:00:40 +0200 Subject: [PATCH 01/10] Changes --- e2e/paths/03-worker/05_calendar.spec.js | 2 +- loopback/locale/es.json | 3 +- .../worker/back/methods/calendar/absences.js | 119 ++++++-------- .../back/methods/worker-labour/holidays.js | 155 ++++++++++++++++++ .../back/methods/worker/activeContract.js | 47 ++++++ .../worker/back/methods/worker/contracts.js | 43 +++++ .../back/methods/worker/createAbsence.js | 30 ++-- modules/worker/back/models/worker.js | 2 + modules/worker/front/calendar/index.html | 28 +++- modules/worker/front/calendar/index.js | 42 +++-- 10 files changed, 375 insertions(+), 96 deletions(-) create mode 100644 modules/worker/back/methods/worker-labour/holidays.js create mode 100644 modules/worker/back/methods/worker/activeContract.js create mode 100644 modules/worker/back/methods/worker/contracts.js 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)); -- 2.40.1 From b77ecc2c060c1005f9f8ec4a42894ebdc6c1c185 Mon Sep 17 00:00:00 2001 From: joan Date: Fri, 4 Jun 2021 11:45:35 +0200 Subject: [PATCH 02/10] changes2 --- .../back/methods/worker-labour/holidays.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/modules/worker/back/methods/worker-labour/holidays.js b/modules/worker/back/methods/worker-labour/holidays.js index 29480b301..ff6d48451 100644 --- a/modules/worker/back/methods/worker-labour/holidays.js +++ b/modules/worker/back/methods/worker-labour/holidays.js @@ -1,8 +1,8 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethodCtx('absences', { - description: 'Returns an array of absences from an specified worker', + Self.remoteMethodCtx('holidays', { + description: 'Returns data - Change me', accepts: [{ arg: 'businessFk', type: 'number', @@ -14,23 +14,16 @@ module.exports = Self => { required: true, }], returns: [{ - arg: 'calendar' - }, - { - arg: 'absences', - type: 'number' - }, - { - arg: 'holidays', - type: 'number' + type: 'object', + root: true }], http: { - path: `/absences`, + path: `/holidays`, verb: 'GET' } }); - Self.absences = async(ctx, businessFk, year) => { + Self.holidays = async(ctx, businessFk, year) => { const models = Self.app.models; const calendar = {totalHolidays: 0, holidaysEnjoyed: 0}; const holidays = []; -- 2.40.1 From c853cfc86f82ccaa33584ddc4a2ca10cf0e50448 Mon Sep 17 00:00:00 2001 From: joan Date: Fri, 4 Jun 2021 15:49:48 +0200 Subject: [PATCH 03/10] Refactor --- .../back/methods/worker-labour/holidays.js | 148 ------------ .../worker/back/methods/worker/holidays.js | 213 ++++++++++++++++++ modules/worker/back/models/worker.js | 1 + modules/worker/front/calendar/index.html | 10 +- modules/worker/front/calendar/index.js | 27 ++- modules/worker/front/calendar/locale/es.yml | 2 +- 6 files changed, 245 insertions(+), 156 deletions(-) delete mode 100644 modules/worker/back/methods/worker-labour/holidays.js create mode 100644 modules/worker/back/methods/worker/holidays.js diff --git a/modules/worker/back/methods/worker-labour/holidays.js b/modules/worker/back/methods/worker-labour/holidays.js deleted file mode 100644 index ff6d48451..000000000 --- a/modules/worker/back/methods/worker-labour/holidays.js +++ /dev/null @@ -1,148 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethodCtx('holidays', { - description: 'Returns data - Change me', - accepts: [{ - arg: 'businessFk', - type: 'number', - required: true, - }, - { - arg: 'year', - type: 'date', - required: true, - }], - returns: [{ - type: 'object', - root: true - }], - http: { - path: `/holidays`, - verb: 'GET' - } - }); - - Self.holidays = 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/holidays.js b/modules/worker/back/methods/worker/holidays.js new file mode 100644 index 000000000..e737599d5 --- /dev/null +++ b/modules/worker/back/methods/worker/holidays.js @@ -0,0 +1,213 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('holidays', { + description: 'Returns data - Change me', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'year', + type: 'date', + required: true, + }, + { + arg: 'businessFk', + type: 'number', + required: false + }], + returns: [{ + type: 'object', + root: true + }], + http: { + path: `/:id/holidays`, + verb: 'GET' + } + }); + + Self.holidays = async(ctx, id, year, businessFk) => { + const models = Self.app.models; + /* const calendar = {totalHolidays: 0, holidaysEnjoyed: 0}; + const holidays = []; */ + + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + // Get active contracts on current year + // const year = yearStarted.getFullYear(); + + const started = new Date(); + started.setFullYear(year); + started.setMonth(0); + started.setDate(1); + started.setHours(0, 0, 0, 0); + + const ended = new Date(); + ended.setFullYear(year); + ended.setMonth(12); + ended.setDate(0); + ended.setHours(23, 59, 59, 59); + + const filter = { + include: [{ + relation: 'holidays', + scope: { + where: {year} + } + }, + { + relation: 'workCenter', + scope: { + include: { + relation: 'holidays', + scope: { + include: [{ + relation: 'detail' + }, + { + relation: 'type' + }], + where: { + dated: {between: [started, ended]} + } + } + } + } + }], + where: { + and: [ + {workerFk: id}, + {or: [{ + ended: {gte: [started]} + }, {ended: null}]} + ], + + } + }; + if (businessFk) + filter.where = {businessFk}; + const contracts = await models.WorkerLabour.find(filter); + + if (!contracts.length) return; + + // Contracts ids + const contractsId = contracts.map(contract => contract.businessFk); + + // Get absences of year + const absences = await models.Calendar.find({ + include: { + relation: 'absenceType' + }, + where: { + businessFk: {inq: contractsId}, + dated: {between: [started, ended]} + } + }); + + let totalHolidays = 0; + let holidaysEnjoyed = 0; + let entitlementRate = 0; + for (let absence of absences) { + const absenceType = absence.absenceType(); + const isHoliday = absenceType.code === 'holiday'; + const isHalfHoliday = absenceType.code === 'halfHoliday'; + + if (isHoliday) holidaysEnjoyed += 1; + if (isHalfHoliday) holidaysEnjoyed += 0.5; + + entitlementRate += absenceType.holidayEntitlementRate; + + absence.dated = new Date(absence.dated); // not needed + absence.dated.setHours(0, 0, 0, 0); + } + + // Get number of worked days + // let totalWorkedDays = 0; + for (let contract of contracts) { + const contractStarted = contract.started; + contractStarted.setHours(0, 0, 0, 0); + const contractEnded = contract.ended; + if (contractEnded) + contractEnded.setHours(23, 59, 59, 59); + + // 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.getTime(); + const dayTimestamp = 1000 * 60 * 60 * 24; + + let workedDays = Math.floor((endedTime - startedTime) / dayTimestamp); // debería dar un computo de 366 dias + workedDays += 1; // 1 day inclusion + // workedDays -= entitlementRate; + + /* 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); + } + */ + // Set max holidays + // const maxHolidays = contract.holidays() && contract.holidays().days; + const maxHolidays = contract.holidays() && contract.holidays().days; + + if (workedDays < daysInYear()) + totalHolidays += Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2; + + // totalWorkedDays += workedDays; + } + + function daysInYear() { + const year = started.getFullYear(); + + return isLeapYear(year) ? 366 : 365; + } + + return {totalHolidays, holidaysEnjoyed}; + }; + + function isLeapYear(year) { + return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 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} + }); */ +}; diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 029bd3256..ec6c4af28 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -11,5 +11,6 @@ module.exports = Self => { require('../methods/worker/activeWithRole')(Self); require('../methods/worker/activeWithInheritedRole')(Self); require('../methods/worker/contracts')(Self); + require('../methods/worker/holidays')(Self); require('../methods/worker/activeContract')(Self); }; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index f1359bb7d..d308c2fcb 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -23,18 +23,18 @@
-
Current contract
+
{{'Contract' | translate}} ID: {{$ctrl.businessId}}
- {{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}} - {{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}} + {{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed}} + {{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
{{'Year' | translate}} {{$ctrl.year}}
- {{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}} - {{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}} + {{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed}} + {{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index 6e995e314..776f687e4 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -20,7 +20,10 @@ class Controller extends Section { this.date = newYear; - this.refresh().then(() => this.repaint()); + this.refresh() + .then(() => this.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()); } get date() { @@ -73,7 +76,9 @@ class Controller extends Section { this._businessId = value; if (value) { this.refresh() - .then(() => this.repaint()); + .then(() => this.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()); } } @@ -101,6 +106,24 @@ class Controller extends Section { .then(res => this.businessId = res.data.businessFk); } + getContractHolidays() { + this.getHolidays({ + businessFk: this.businessId, + year: this.year + }, data => this.contractHolidays = data); + } + + getYearHolidays() { + this.getHolidays({ + year: this.year + }, data => this.yearHolidays = data); + } + + getHolidays(params, cb) { + this.$http.get(`Workers/${this.worker.id}/holidays`, {params}) + .then(res => cb(res.data)); + } + onData(data) { this.events = {}; this.calendar = data.calendar; diff --git a/modules/worker/front/calendar/locale/es.yml b/modules/worker/front/calendar/locale/es.yml index 0626b7550..1ff12358c 100644 --- a/modules/worker/front/calendar/locale/es.yml +++ b/modules/worker/front/calendar/locale/es.yml @@ -1,5 +1,5 @@ Calendar: Calendario -Holidays: Vacaciones +Contract: Contrato Festive: Festivo Used: Utilizados Year: Año -- 2.40.1 From 8fa64122f86cb39d99836aff4b3e6ec1084adcea Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 7 Jun 2021 14:58:24 +0200 Subject: [PATCH 04/10] Ammends --- modules/worker/back/methods/worker/holidays.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/worker/back/methods/worker/holidays.js b/modules/worker/back/methods/worker/holidays.js index e737599d5..48c943b58 100644 --- a/modules/worker/back/methods/worker/holidays.js +++ b/modules/worker/back/methods/worker/holidays.js @@ -163,6 +163,7 @@ module.exports = Self => { if (workedDays < daysInYear()) totalHolidays += Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2; + else totalHolidays = maxHolidays; // totalWorkedDays += workedDays; } -- 2.40.1 From 3c8629b3f306d8f969241e3a9cf8fb0b46579a34 Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 8 Jun 2021 16:36:16 +0200 Subject: [PATCH 05/10] Added entilementRate effects --- .../worker/back/methods/worker/holidays.js | 39 ++++++++++++++++--- modules/worker/back/models/worker-labour.json | 5 +++ modules/worker/front/calendar/index.js | 4 +- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/worker/back/methods/worker/holidays.js b/modules/worker/back/methods/worker/holidays.js index 48c943b58..c6f299a73 100644 --- a/modules/worker/back/methods/worker/holidays.js +++ b/modules/worker/back/methods/worker/holidays.js @@ -61,6 +61,17 @@ module.exports = Self => { where: {year} } }, + { + relation: 'absences', + scope: { + include: { + relation: 'absenceType', + }, + where: { + dated: {between: [started, ended]} + } + } + }, { relation: 'workCenter', scope: { @@ -112,8 +123,7 @@ module.exports = Self => { let totalHolidays = 0; let holidaysEnjoyed = 0; - let entitlementRate = 0; - for (let absence of absences) { + /* for (let absence of absences) { const absenceType = absence.absenceType(); const isHoliday = absenceType.code === 'holiday'; const isHalfHoliday = absenceType.code === 'halfHoliday'; @@ -123,9 +133,7 @@ module.exports = Self => { entitlementRate += absenceType.holidayEntitlementRate; - absence.dated = new Date(absence.dated); // not needed - absence.dated.setHours(0, 0, 0, 0); - } + } */ // Get number of worked days // let totalWorkedDays = 0; @@ -138,7 +146,8 @@ module.exports = Self => { // 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.getTime(); + const endedTime = !contractEnded || contractEnded && contractEnded > ended ? ended.getTime() : contractEnded.getTime(); + // const endedTime = contractEnded && contractEnded.getTime() || ended.getTime(); const dayTimestamp = 1000 * 60 * 60 * 24; let workedDays = Math.floor((endedTime - startedTime) / dayTimestamp); // debería dar un computo de 366 dias @@ -156,7 +165,25 @@ module.exports = Self => { holidays.push(day); } + */ + let entitlementRate = 0; + for (let absence of contract.absences()) { + const absenceType = absence.absenceType(); + const isHoliday = absenceType.code === 'holiday'; + const isHalfHoliday = absenceType.code === 'halfHoliday'; + + if (isHoliday) holidaysEnjoyed += 1; + if (isHalfHoliday) holidaysEnjoyed += 0.5; + + entitlementRate += absenceType.holidayEntitlementRate; + + // absence.dated = new Date(absence.dated); // not needed + // absence.dated.setHours(0, 0, 0, 0); + } + + workedDays -= entitlementRate; + // Set max holidays // const maxHolidays = contract.holidays() && contract.holidays().days; const maxHolidays = contract.holidays() && contract.holidays().days; diff --git a/modules/worker/back/models/worker-labour.json b/modules/worker/back/models/worker-labour.json index eae28cf89..62764961a 100644 --- a/modules/worker/back/models/worker-labour.json +++ b/modules/worker/back/models/worker-labour.json @@ -38,6 +38,11 @@ "type": "belongsTo", "model": "WorkCenterHoliday", "foreignKey": "workCenterFk" + }, + "absences": { + "type": "hasMany", + "model": "Calendar", + "foreignKey": "businessFk" } } } diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index 776f687e4..ab172f597 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -114,9 +114,9 @@ class Controller extends Section { } getYearHolidays() { - this.getHolidays({ + /* this.getHolidays({ year: this.year - }, data => this.yearHolidays = data); + }, data => this.yearHolidays = data); */ } getHolidays(params, cb) { -- 2.40.1 From 82847026031a7cbc07db016435e571cfc2f3872f Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 10 Jun 2021 11:24:32 +0200 Subject: [PATCH 06/10] 2567 - Working calendar refactor --- front/core/components/autocomplete/index.js | 2 +- front/core/components/popup/index.js | 2 +- .../worker/back/methods/calendar/absences.js | 97 ++++-------- .../back/methods/worker/createAbsence.js | 92 +++++++----- .../back/methods/worker/deleteAbsence.js | 92 +++++++----- .../worker/back/methods/worker/holidays.js | 139 +++++------------- .../back/methods/worker/isSubordinate.js | 11 +- .../back/methods/worker/mySubordinates.js | 9 +- modules/worker/back/models/worker-labour.json | 4 +- modules/worker/front/calendar/index.js | 61 ++++---- modules/worker/front/calendar/index.spec.js | 115 +++++++-------- 11 files changed, 278 insertions(+), 346 deletions(-) diff --git a/front/core/components/autocomplete/index.js b/front/core/components/autocomplete/index.js index 56b30667e..52491f7e0 100755 --- a/front/core/components/autocomplete/index.js +++ b/front/core/components/autocomplete/index.js @@ -11,7 +11,7 @@ import './style.scss'; * @property {String} valueField The data field name that should be used as value * @property {Array} data Static data for the autocomplete * @property {Object} intialData An initial data to avoid the server request used to get the selection - * @property {Boolean} multiple Wether to allow multiple selection + * @property {Boolean} multiple Whether to allow multiple selection * @property {Object} selection Current object selected * * @event change Thrown when value is changed diff --git a/front/core/components/popup/index.js b/front/core/components/popup/index.js index 2a2433770..3743b9f41 100644 --- a/front/core/components/popup/index.js +++ b/front/core/components/popup/index.js @@ -17,7 +17,7 @@ export default class Popup extends Component { } /** - * @type {Boolean} Wether to show or hide the popup. + * @type {Boolean} Whether to show or hide the popup. */ get shown() { return this._shown; diff --git a/modules/worker/back/methods/calendar/absences.js b/modules/worker/back/methods/calendar/absences.js index 83761242f..96293c931 100644 --- a/modules/worker/back/methods/calendar/absences.js +++ b/modules/worker/back/methods/calendar/absences.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('absences', { - description: 'Returns an array of absences from an specified worker', + description: 'Returns an array of absences from an specified contract', accepts: [{ arg: 'businessFk', type: 'number', @@ -14,9 +14,6 @@ module.exports = Self => { required: true, }], returns: [{ - arg: 'calendar' - }, - { arg: 'absences', type: 'number' }, @@ -30,13 +27,8 @@ module.exports = Self => { } }); - Self.absences = async(ctx, businessFk, year) => { + Self.absences = async(ctx, businessFk, year, options) => { 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); @@ -48,6 +40,11 @@ module.exports = Self => { ended.setMonth(12); ended.setDate(0); + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + const contract = await models.WorkerLabour.findOne({ include: [{ relation: 'holidays', @@ -55,6 +52,17 @@ module.exports = Self => { where: {year} } }, + { + relation: 'absences', + scope: { + include: { + relation: 'absenceType', + }, + where: { + dated: {between: [started, ended]} + } + } + }, { relation: 'workCenter', scope: { @@ -75,58 +83,25 @@ module.exports = Self => { } }], where: {businessFk} - }); + }, myOptions); if (!contract) return; - const isSubordinate = await models.Worker.isSubordinate(ctx, contract.workerFk); + const isSubordinate = await models.Worker.isSubordinate(ctx, contract.workerFk, myOptions); if (!isSubordinate) throw new UserError(`You don't have enough privileges`); - // Get absences of year - let 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; - + const absences = []; + for (let absence of contract.absences()) { 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(); + absences.push(absence); + } // Workcenter holidays - let holidayList = contract.workCenter().holidays(); + const holidays = []; + const holidayList = contract.workCenter().holidays(); for (let day of holidayList) { day.dated = new Date(day.dated); day.dated.setHours(0, 0, 0, 0); @@ -134,24 +109,6 @@ module.exports = Self => { 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]; + return [absences, holidays]; }; - - function isLeapYear(year) { - return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0); - } }; diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js index e327701b3..da64c413f 100644 --- a/modules/worker/back/methods/worker/createAbsence.js +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -34,50 +34,70 @@ module.exports = Self => { } }); - Self.createAbsence = async(ctx, id, businessFk, absenceTypeId, dated) => { + Self.createAbsence = async(ctx, id, options) => { const models = Self.app.models; const $t = ctx.req.__; // $translate + const args = ctx.args; 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`); + let tx; + let myOptions = {}; - const labour = await models.WorkerLabour.findById(businessFk, { - include: {relation: 'department'} - }); + if (typeof options == 'object') + Object.assign(myOptions, options); - 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, - dated: dated - }); - - const department = labour.department(); - if (department && department.notificationEmail) { - const absenceType = await models.AbsenceType.findById(absenceTypeId); - const account = await models.Account.findById(userId); - const subordinated = await models.Account.findById(id); - const origin = ctx.req.headers.origin; - const body = $t('Created absence', { - author: account.nickname, - employee: subordinated.nickname, - absenceType: absenceType.name, - dated: formatDate(dated), - workerUrl: `${origin}/#!/worker/${id}/calendar` - }); - await models.Mail.create({ - subject: $t('Absence change notification on the labour calendar'), - body: body, - sender: department.notificationEmail - }); + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; } - return absence; + try { + const isSubordinate = await models.Worker.isSubordinate(ctx, id, myOptions); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss', myOptions); + + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) + throw new UserError(`You don't have enough privileges`); + + const labour = await models.WorkerLabour.findById(args.businessFk, { + include: {relation: 'department'} + }, myOptions); + + if (args.dated < labour.started || (labour.ended != null && args.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: args.absenceTypeId, + dated: args.dated + }, myOptions); + + const department = labour.department(); + if (department && department.notificationEmail) { + const absenceType = await models.AbsenceType.findById(args.absenceTypeId, null, myOptions); + const account = await models.Account.findById(userId, null, myOptions); + const subordinated = await models.Account.findById(id, null, myOptions); + const origin = ctx.req.headers.origin; + const body = $t('Created absence', { + author: account.nickname, + employee: subordinated.nickname, + absenceType: absenceType.name, + dated: formatDate(args.dated), + workerUrl: `${origin}/#!/worker/${id}/calendar` + }); + await models.Mail.create({ + subject: $t('Absence change notification on the labour calendar'), + body: body, + sender: department.notificationEmail + }, myOptions); + } + + if (tx) await tx.commit(); + + return absence; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; function formatDate(date) { diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js index c7e24fa58..e322288d2 100644 --- a/modules/worker/back/methods/worker/deleteAbsence.js +++ b/modules/worker/back/methods/worker/deleteAbsence.js @@ -5,13 +5,13 @@ module.exports = Self => { description: 'Deletes a worker absence', accepts: [{ arg: 'id', - type: 'Number', + type: 'number', description: 'The worker id', http: {source: 'path'} }, { arg: 'absenceId', - type: 'Number', + type: 'number', required: true }], returns: 'Object', @@ -21,47 +21,67 @@ module.exports = Self => { } }); - Self.deleteAbsence = async(ctx, id, absenceId) => { + Self.deleteAbsence = async(ctx, id, options) => { const models = Self.app.models; const $t = ctx.req.__; // $translate + const args = ctx.args; 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`); + let tx; + let myOptions = {}; - const absence = await models.Calendar.findById(absenceId, { - include: { - relation: 'labour', - scope: { - include: {relation: 'department'} - } - } - }); - const result = await absence.destroy(); - const labour = absence.labour(); - const department = labour && labour.department(); - if (department && department.notificationEmail) { - const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk); - const account = await models.Account.findById(userId); - const subordinated = await models.Account.findById(labour.workerFk); - const origin = ctx.req.headers.origin; - const body = $t('Deleted absence', { - author: account.nickname, - employee: subordinated.nickname, - absenceType: absenceType.name, - dated: formatDate(absence.dated), - workerUrl: `${origin}/#!/worker/${id}/calendar` - }); - await models.Mail.create({ - subject: $t('Absence change notification on the labour calendar'), - body: body, - sender: department.notificationEmail - }); + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; } - return result; + try { + const isSubordinate = await models.Worker.isSubordinate(ctx, id, myOptions); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss', myOptions); + + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) + throw new UserError(`You don't have enough privileges`); + + const absence = await models.Calendar.findById(args.absenceId, { + include: { + relation: 'labour', + scope: { + include: {relation: 'department'} + } + } + }, myOptions); + const result = await absence.destroy(myOptions); + const labour = absence.labour(); + const department = labour && labour.department(); + if (department && department.notificationEmail) { + const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk, null, myOptions); + const account = await models.Account.findById(userId, null, myOptions); + const subordinated = await models.Account.findById(labour.workerFk, null, myOptions); + const origin = ctx.req.headers.origin; + const body = $t('Deleted absence', { + author: account.nickname, + employee: subordinated.nickname, + absenceType: absenceType.name, + dated: formatDate(absence.dated), + workerUrl: `${origin}/#!/worker/${id}/calendar` + }); + await models.Mail.create({ + subject: $t('Absence change notification on the labour calendar'), + body: body, + sender: department.notificationEmail + }, myOptions); + } + + if (tx) await tx.commit(); + + return result; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; function formatDate(date) { diff --git a/modules/worker/back/methods/worker/holidays.js b/modules/worker/back/methods/worker/holidays.js index c6f299a73..704f4d389 100644 --- a/modules/worker/back/methods/worker/holidays.js +++ b/modules/worker/back/methods/worker/holidays.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('holidays', { - description: 'Returns data - Change me', + description: 'Returns the holidays available whitin a contract or a year', accepts: [{ arg: 'id', type: 'number', @@ -30,26 +30,27 @@ module.exports = Self => { } }); - Self.holidays = async(ctx, id, year, businessFk) => { + Self.holidays = async(ctx, id, options) => { const models = Self.app.models; - /* const calendar = {totalHolidays: 0, holidaysEnjoyed: 0}; - const holidays = []; */ + const args = ctx.args; - const isSubordinate = await models.Worker.isSubordinate(ctx, id); + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const isSubordinate = await models.Worker.isSubordinate(ctx, id, myOptions); if (!isSubordinate) throw new UserError(`You don't have enough privileges`); - // Get active contracts on current year - // const year = yearStarted.getFullYear(); - const started = new Date(); - started.setFullYear(year); + started.setFullYear(args.year); started.setMonth(0); started.setDate(1); started.setHours(0, 0, 0, 0); const ended = new Date(); - ended.setFullYear(year); + ended.setFullYear(args.year); ended.setMonth(12); ended.setDate(0); ended.setHours(23, 59, 59, 59); @@ -58,7 +59,7 @@ module.exports = Self => { include: [{ relation: 'holidays', scope: { - where: {year} + where: {year: args.year} } }, { @@ -94,49 +95,26 @@ module.exports = Self => { where: { and: [ {workerFk: id}, - {or: [{ - ended: {gte: [started]} - }, {ended: null}]} + { + or: [ + {started: {between: [started, ended]}}, + {ended: {between: [started, ended]}}, + {and: [{started: {lt: started}}, {ended: {gt: ended}}]}, + {and: [{started: {lt: started}}, {ended: null}]} + ] + } ], } }; - if (businessFk) - filter.where = {businessFk}; - const contracts = await models.WorkerLabour.find(filter); + if (args.businessFk) + filter.where.and.push({businessFk: args.businessFk}); - if (!contracts.length) return; - - // Contracts ids - const contractsId = contracts.map(contract => contract.businessFk); - - // Get absences of year - const absences = await models.Calendar.find({ - include: { - relation: 'absenceType' - }, - where: { - businessFk: {inq: contractsId}, - dated: {between: [started, ended]} - } - }); + const contracts = await models.WorkerLabour.find(filter, myOptions); let totalHolidays = 0; let holidaysEnjoyed = 0; - /* for (let absence of absences) { - const absenceType = absence.absenceType(); - const isHoliday = absenceType.code === 'holiday'; - const isHalfHoliday = absenceType.code === 'halfHoliday'; - if (isHoliday) holidaysEnjoyed += 1; - if (isHalfHoliday) holidaysEnjoyed += 0.5; - - entitlementRate += absenceType.holidayEntitlementRate; - - } */ - - // Get number of worked days - // let totalWorkedDays = 0; for (let contract of contracts) { const contractStarted = contract.started; contractStarted.setHours(0, 0, 0, 0); @@ -144,29 +122,23 @@ module.exports = Self => { if (contractEnded) contractEnded.setHours(23, 59, 59, 59); - // esta mal, la fecha de inicio puede ser un año anterior... - const startedTime = contractStarted < started ? started.getTime() : contractStarted.getTime(); - const endedTime = !contractEnded || contractEnded && contractEnded > ended ? ended.getTime() : contractEnded.getTime(); - // const endedTime = contractEnded && contractEnded.getTime() || ended.getTime(); + let startedTime; + if (contractStarted < started) + startedTime = started.getTime(); + else contractStarted.getTime(); + + let endedTime; + if (!contractEnded || (contractEnded && contractEnded > ended)) + endedTime = ended.getTime(); + else contractEnded.getTime(); + const dayTimestamp = 1000 * 60 * 60 * 24; - let workedDays = Math.floor((endedTime - startedTime) / dayTimestamp); // debería dar un computo de 366 dias + // Get number of worked days between dates + let workedDays = Math.floor((endedTime - startedTime) / dayTimestamp); workedDays += 1; // 1 day inclusion - // workedDays -= entitlementRate; - /* 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); - } - - */ + // Calculates absences let entitlementRate = 0; for (let absence of contract.absences()) { const absenceType = absence.absenceType(); @@ -177,22 +149,16 @@ module.exports = Self => { if (isHalfHoliday) holidaysEnjoyed += 0.5; entitlementRate += absenceType.holidayEntitlementRate; - - // absence.dated = new Date(absence.dated); // not needed - // absence.dated.setHours(0, 0, 0, 0); } workedDays -= entitlementRate; - // Set max holidays - // const maxHolidays = contract.holidays() && contract.holidays().days; + // Max holidays for the selected year const maxHolidays = contract.holidays() && contract.holidays().days; if (workedDays < daysInYear()) totalHolidays += Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2; else totalHolidays = maxHolidays; - - // totalWorkedDays += workedDays; } function daysInYear() { @@ -207,35 +173,4 @@ module.exports = Self => { function isLeapYear(year) { return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 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} - }); */ }; diff --git a/modules/worker/back/methods/worker/isSubordinate.js b/modules/worker/back/methods/worker/isSubordinate.js index 18c7df17e..13cc365c6 100644 --- a/modules/worker/back/methods/worker/isSubordinate.js +++ b/modules/worker/back/methods/worker/isSubordinate.js @@ -23,16 +23,21 @@ module.exports = Self => { } }); - Self.isSubordinate = async(ctx, id) => { + Self.isSubordinate = async(ctx, id, options) => { const models = Self.app.models; const myUserId = ctx.req.accessToken.userId; - const mySubordinates = await Self.mySubordinates(ctx); + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const mySubordinates = await Self.mySubordinates(ctx, myOptions); const isSubordinate = mySubordinates.find(subordinate => { return subordinate.workerFk == id; }); - const isHr = await models.Account.hasRole(myUserId, 'hr'); + const isHr = await models.Account.hasRole(myUserId, 'hr', myOptions); if (isHr || isSubordinate) return true; diff --git a/modules/worker/back/methods/worker/mySubordinates.js b/modules/worker/back/methods/worker/mySubordinates.js index 956374964..07a22291d 100644 --- a/modules/worker/back/methods/worker/mySubordinates.js +++ b/modules/worker/back/methods/worker/mySubordinates.js @@ -20,17 +20,22 @@ module.exports = Self => { } }); - Self.mySubordinates = async ctx => { + Self.mySubordinates = async(ctx, options) => { const conn = Self.dataSource.connector; const userId = ctx.req.accessToken.userId; const stmts = []; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + stmts.push(new ParameterizedSQL('CALL vn.subordinateGetList(?)', [userId])); const queryIndex = stmts.push('SELECT * FROM tmp.subordinate') - 1; stmts.push('DROP TEMPORARY TABLE tmp.subordinate'); const sql = ParameterizedSQL.join(stmts, ';'); - const result = await conn.executeStmt(sql); + const result = await conn.executeStmt(sql, myOptions); return result[queryIndex]; }; diff --git a/modules/worker/back/models/worker-labour.json b/modules/worker/back/models/worker-labour.json index 62764961a..8ad7bf41e 100644 --- a/modules/worker/back/models/worker-labour.json +++ b/modules/worker/back/models/worker-labour.json @@ -12,10 +12,10 @@ "type": "Number" }, "started": { - "type": "Date" + "type": "date" }, "ended": { - "type": "Date" + "type": "date" } }, "relations": { diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index ab172f597..d60fa0647 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -26,6 +26,20 @@ class Controller extends Section { .then(() => this.getYearHolidays()); } + get businessId() { + return this._businessId; + } + + set businessId(value) { + this._businessId = value; + if (value) { + this.refresh() + .then(() => this.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()); + } + } + get date() { return this._date; } @@ -34,16 +48,6 @@ class Controller extends Section { this._date = value; value.setHours(0, 0, 0, 0); - const started = new Date(value.getTime()); - started.setMonth(0); - started.setDate(1); - this.started = started; - - const ended = new Date(value.getTime()); - ended.setMonth(12); - ended.setDate(0); - this.ended = ended; - this.months = new Array(12); for (let i = 0; i < this.months.length; i++) { @@ -62,26 +66,11 @@ class Controller extends Section { this._worker = value; if (value) { - // 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()) - .then(() => this.getContractHolidays()) - .then(() => this.getYearHolidays()); - } - } - buildYearFilter() { const now = new Date(); now.setFullYear(now.getFullYear() + 1); @@ -114,9 +103,9 @@ class Controller extends Section { } getYearHolidays() { - /* this.getHolidays({ + this.getHolidays({ year: this.year - }, data => this.yearHolidays = data); */ + }, data => this.yearHolidays = data); } getHolidays(params, cb) { @@ -198,9 +187,6 @@ class Controller extends Section { if (!this.absenceType) return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu')); - if (this.year != new Date().getFullYear()) - return this.vnApp.showMessage(this.$t('You can just add absences within the current year')); - const day = $days[0]; const stamp = day.getTime(); const event = this.events[stamp]; @@ -234,7 +220,10 @@ class Controller extends Section { }; this.repaintCanceller(() => - this.refresh().then(calendar.repaint()) + this.refresh() + .then(calendar.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()) ); }); } @@ -252,7 +241,10 @@ class Controller extends Section { event.type = absenceType.code; this.repaintCanceller(() => - this.refresh().then(calendar.repaint()) + this.refresh() + .then(calendar.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()) ); }); } @@ -264,7 +256,10 @@ class Controller extends Section { delete this.events[day.getTime()]; this.repaintCanceller(() => - this.refresh().then(calendar.repaint()) + this.refresh() + .then(calendar.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()) ); }); } diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index 14a5e2e0d..ec2f5dd8a 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -41,35 +41,31 @@ describe('Worker', () => { }); }); - describe('started property', () => { - it(`should return first day and month of current year`, () => { - let started = new Date(year, 0, 1); + describe('businessId() setter', () => { + it(`should set the contract id and then call to the refresh method`, () => { + jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve()); - expect(controller.started).toEqual(started); - }); - }); + controller.businessId = 106; - describe('ended property', () => { - it(`should return last day and month of current year`, () => { - let ended = new Date(year, 11, 31); - - expect(controller.ended).toEqual(ended); + expect(controller.refresh).toHaveBeenCalledWith(); }); }); describe('months property', () => { it(`should return an array of twelve months length`, () => { + const started = new Date(year, 0, 1); const ended = new Date(year, 11, 1); expect(controller.months.length).toEqual(12); - expect(controller.months[0]).toEqual(controller.started); + expect(controller.months[0]).toEqual(started); expect(controller.months[11]).toEqual(ended); }); }); describe('worker() setter', () => { it(`should perform a get query and set the reponse data on the model`, () => { - jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true); + controller.getIsSubordinate = jest.fn(); + controller.getActiveContract = jest.fn(); let today = new Date(); let tomorrow = new Date(today.getTime()); @@ -78,49 +74,60 @@ describe('Worker', () => { let yesterday = new Date(today.getTime()); yesterday.setDate(yesterday.getDate() - 1); - $httpBackend.whenRoute('GET', 'Calendars/absences') - .respond({ - holidays: [ - {dated: today, detail: {name: 'New year'}}, - {dated: tomorrow, detail: {name: 'Easter'}} - ], - absences: [ - {dated: today, absenceType: {name: 'Holiday', rgb: '#aaa'}}, - {dated: yesterday, absenceType: {name: 'Leave', rgb: '#bbb'}} - ] - }); - controller.worker = {id: 107}; + + expect(controller.getIsSubordinate).toHaveBeenCalledWith(); + expect(controller.getActiveContract).toHaveBeenCalledWith(); + }); + }); + + describe('getIsSubordinate()', () => { + it(`should return whether the worker is a subordinate`, () => { + $httpBackend.expect('GET', `Workers/106/isSubordinate`).respond(true); + controller.getIsSubordinate(); $httpBackend.flush(); - let events = controller.events; + expect(controller.isSubordinate).toBe(true); + }); + }); - expect(events[today.getTime()].name).toEqual('New year, Holiday'); - 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('getActiveContract()', () => { + it(`should return the current contract and then set the businessId property`, () => { + jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve()); + + $httpBackend.expect('GET', `Workers/106/activeContract`).respond({businessFk: 106}); + controller.getActiveContract(); + $httpBackend.flush(); + + expect(controller.businessId).toEqual(106); + }); + }); + + describe('getContractHolidays()', () => { + it(`should return the worker holidays amount and then set the contractHolidays property`, () => { + const today = new Date(); + const year = today.getFullYear(); + + const serializedParams = $httpParamSerializer({year}); + $httpBackend.expect('GET', `Workers/106/holidays?${serializedParams}`).respond({totalHolidays: 28}); + controller.getContractHolidays(); + $httpBackend.flush(); + + expect(controller.contractHolidays).toEqual({totalHolidays: 28}); }); }); describe('formatDay()', () => { it(`should set the day element style`, () => { - jest.spyOn(controller, 'getIsSubordinate').mockReturnThis(); + const today = new Date(); - let today = new Date(); + controller.events[today.getTime()] = { + name: 'Holiday', + color: '#000' + }; - $httpBackend.whenRoute('GET', 'Calendars/absences') - .respond({ - absences: [ - {dated: today, absenceType: {name: 'Holiday', rgb: '#000'}} - ] - }); - - controller.worker = {id: 1}; - $httpBackend.flush(); - - let dayElement = angular.element('
')[0]; - let dayNumber = dayElement.firstElementChild; + const dayElement = angular.element('
')[0]; + const dayNumber = dayElement.firstElementChild; controller.formatDay(today, dayElement); @@ -160,7 +167,7 @@ describe('Worker', () => { expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu'); }); - it(`should show an snackbar message if the selected day is not within the current year`, () => { + /* it(`should show an snackbar message if the selected day is not within the current year`, () => { jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis(); const selectedDay = new Date(); @@ -180,7 +187,7 @@ describe('Worker', () => { controller.onSelection($event, $days); expect(controller.vnApp.showMessage).toHaveBeenCalledWith('You can just add absences within the current year'); - }); + }); */ it(`should call to the create() method`, () => { jest.spyOn(controller, 'create').mockReturnThis(); @@ -342,20 +349,8 @@ describe('Worker', () => { 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 expectedParams = {year: year}; const serializedParams = $httpParamSerializer(expectedParams); $httpBackend.expect('GET', `Calendars/absences?${serializedParams}`).respond(200, expecteResponse); controller.refresh(); -- 2.40.1 From 3f3e5c2735d917864c9e5aa2aa2b6e8adfc184b2 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 10 Jun 2021 16:05:10 +0200 Subject: [PATCH 07/10] Unit tests --- .../methods/calendar/specs/absences.spec.js | 169 +++++++----------- .../worker/specs/createAbsence.spec.js | 69 ++++--- .../worker/specs/deleteAbsence.spec.js | 67 ++++--- .../methods/worker/specs/holidays.spec.js | 30 ++++ 4 files changed, 186 insertions(+), 149 deletions(-) create mode 100644 modules/worker/back/methods/worker/specs/holidays.spec.js diff --git a/modules/worker/back/methods/calendar/specs/absences.spec.js b/modules/worker/back/methods/calendar/specs/absences.spec.js index 42b97e2fc..1b95bfdb6 100644 --- a/modules/worker/back/methods/calendar/specs/absences.spec.js +++ b/modules/worker/back/methods/calendar/specs/absences.spec.js @@ -2,78 +2,56 @@ const app = require('vn-loopback/server/server'); describe('Worker absences()', () => { it('should get the absence calendar for a full year contract', async() => { - let ctx = {req: {accessToken: {userId: 106}}}; - let workerFk = 106; + const ctx = {req: {accessToken: {userId: 106}}}; + const businessId = 106; - const started = new Date(); - started.setHours(0, 0, 0, 0); - started.setMonth(0); - started.setDate(1); + const now = new Date(); + const year = now.getFullYear(); - const monthIndex = 11; - const ended = new Date(); - ended.setHours(0, 0, 0, 0); - ended.setMonth(monthIndex + 1); - ended.setDate(0); + const [absences] = await app.models.Calendar.absences(ctx, businessId, year); - let result = await app.models.Calendar.absences(ctx, workerFk, started, ended); - let calendar = result[0]; - let absences = result[1]; - - expect(calendar.totalHolidays).toEqual(27.5); - expect(calendar.holidaysEnjoyed).toEqual(5); - - let firstType = absences[0].absenceType().name; - let sixthType = absences[5].absenceType().name; + const firstType = absences[0].absenceType().name; + const sixthType = absences[5].absenceType().name; expect(firstType).toMatch(/(Holidays|Leave of absence)/); expect(sixthType).toMatch(/(Holidays|Leave of absence)/); }); it('should get the absence calendar for a permanent contract', async() => { - let workerFk = 106; - let worker = await app.models.WorkerLabour.findById(workerFk); - let endedDate = worker.ended; + const businessId = 106; + const ctx = {req: {accessToken: {userId: 9}}}; - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`, - [null, worker.businessFk] - ); + const now = new Date(); + const year = now.getFullYear(); - let ctx = {req: {accessToken: {userId: 9}}}; + const tx = await app.models.Calendar.beginTransaction({}); - const started = new Date(); - started.setHours(0, 0, 0, 0); - started.setMonth(0); - started.setDate(1); + try { + const options = {transaction: tx}; - const monthIndex = 11; - const ended = new Date(); - ended.setHours(0, 0, 0, 0); - ended.setMonth(monthIndex + 1); - ended.setDate(0); + const worker = await app.models.WorkerLabour.findById(businessId, null, options); - let result = await app.models.Calendar.absences(ctx, workerFk, started, ended); - let calendar = result[0]; - let absences = result[1]; + await app.models.WorkerLabour.rawSql( + `UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`, + [null, worker.businessFk], options); - expect(calendar.totalHolidays).toEqual(27.5); - expect(calendar.holidaysEnjoyed).toEqual(5); + const [absences] = await app.models.Calendar.absences(ctx, businessId, year, options); - let firstType = absences[0].absenceType().name; - let sixthType = absences[5].absenceType().name; + let firstType = absences[0].absenceType().name; + let sixthType = absences[5].absenceType().name; - expect(firstType).toMatch(/(Holidays|Leave of absence)/); - expect(sixthType).toMatch(/(Holidays|Leave of absence)/); - - // restores the contract end date - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`, - [endedDate, worker.businessFk] - ); + expect(firstType).toMatch(/(Holidays|Leave of absence)/); + expect(sixthType).toMatch(/(Holidays|Leave of absence)/); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); it('should give the same holidays as worked days since the holidays amount matches the amount of days in a year', async() => { + const businessId = 106; + const userId = 106; const today = new Date(); // getting how many days in a year @@ -94,70 +72,47 @@ describe('Worker absences()', () => { const daysInYear = Math.round((endedTime - startedTime) / dayTimestamp); - // sets the holidays per year to the amount of days in the current year - let holidaysConfig = await app.models.WorkCenterHoliday.findOne({ - where: { - workCenterFk: 1, - year: today.getFullYear() - }}); + const tx = await app.models.Calendar.beginTransaction({}); + try { + const options = {transaction: tx}; - let originalHolidaysValue = holidaysConfig.days; + // sets the holidays per year to the amount of days in the current year + const holidaysConfig = await app.models.WorkCenterHoliday.findOne({ + where: { + workCenterFk: 1, + year: today.getFullYear() + } + }, options); - await holidaysConfig.updateAttribute('days', daysInYear); + await holidaysConfig.updateAttribute('days', daysInYear, options); - // normal test begins - const userId = 106; - const contract = await app.models.WorkerLabour.findById(userId); - const contractStartDate = contract.started; + // normal test begins + const contract = await app.models.WorkerLabour.findById(businessId, null, options); - const startingContract = new Date(); - startingContract.setHours(0, 0, 0, 0); - startingContract.setMonth(today.getMonth()); - startingContract.setDate(1); + const startingContract = new Date(); + startingContract.setHours(0, 0, 0, 0); + startingContract.setMonth(today.getMonth()); + startingContract.setDate(1); - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_start = ?, date_end = ? WHERE business_id = ?`, - [startingContract, yearEnd, contract.businessFk] - ); + await app.models.WorkerLabour.rawSql( + `UPDATE postgresql.business SET date_start = ?, date_end = ? WHERE business_id = ?`, + [startingContract, yearEnd, contract.businessFk], options + ); - let ctx = {req: {accessToken: {userId: userId}}}; + const ctx = {req: {accessToken: {userId: userId}}}; - let result = await app.models.Calendar.absences(ctx, userId, yearStart, yearEnd); - let calendar = result[0]; - let absences = result[1]; + const [absences] = await app.models.Calendar.absences(ctx, businessId, currentYear); - let remainingDays = 0; - for (let i = today.getMonth(); i < 12; i++) { - today.setDate(1); - today.setMonth(i + 1); - today.setDate(0); + const firstType = absences[0].absenceType().name; + const sixthType = absences[5].absenceType().name; - remainingDays += today.getDate(); + expect(firstType).toMatch(/(Holidays|Leave of absence)/); + expect(sixthType).toMatch(/(Holidays|Leave of absence)/); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; } - - expect(calendar.totalHolidays).toEqual(remainingDays); - expect(calendar.holidaysEnjoyed).toEqual(5); - - let firstType = absences[0].absenceType().name; - let sixthType = absences[5].absenceType().name; - - expect(firstType).toMatch(/(Holidays|Leave of absence)/); - expect(sixthType).toMatch(/(Holidays|Leave of absence)/); - - // resets the holidays per year with originalHolidaysValue and the contract starting date - await app.models.WorkCenterHoliday.updateAll( - { - workCenterFk: 1, - year: today.getFullYear() - }, - { - days: originalHolidaysValue - } - ); - - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_start = ? WHERE business_id = ?`, - [contractStartDate, contract.businessFk] - ); }); }); diff --git a/modules/worker/back/methods/worker/specs/createAbsence.spec.js b/modules/worker/back/methods/worker/specs/createAbsence.spec.js index ace412890..f2c00e804 100644 --- a/modules/worker/back/methods/worker/specs/createAbsence.spec.js +++ b/modules/worker/back/methods/worker/specs/createAbsence.spec.js @@ -5,18 +5,34 @@ describe('Worker createAbsence()', () => { const workerId = 18; it('should return an error for a user without enough privileges', async() => { - const ctx = {req: {accessToken: {userId: 18}}}; - const absenceTypeId = 1; - const dated = new Date(); + const ctx = { + req: {accessToken: {userId: 18}}, + args: { + businessFk: 18, + absenceTypeId: 1, + 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`); - }); + const tx = await app.models.Calendar.beginTransaction({}); - expect(error).toBeDefined(); + try { + const options = {transaction: tx}; + + let error; + await app.models.Worker.createAbsence(ctx, workerId, options).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); it('should create a new absence', async() => { @@ -24,7 +40,14 @@ describe('Worker createAbsence()', () => { accessToken: {userId: 19}, headers: {origin: 'http://localhost'} }; - const ctx = {req: activeCtx}; + const ctx = { + req: activeCtx, + args: { + businessFk: 18, + absenceTypeId: 1, + dated: new Date() + } + }; ctx.req.__ = value => { return value; }; @@ -32,17 +55,23 @@ describe('Worker createAbsence()', () => { active: activeCtx }); - const absenceTypeId = 1; - const dated = new Date(); - const createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated); + const tx = await app.models.Calendar.beginTransaction({}); - const expectedBusinessId = 18; - const expectedAbsenceTypeId = 1; + try { + const options = {transaction: tx}; - expect(createdAbsence.businessFk).toEqual(expectedBusinessId); - expect(createdAbsence.dayOffTypeFk).toEqual(expectedAbsenceTypeId); + const createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, options); - // Restores - await app.models.Calendar.destroyById(createdAbsence.id); + const expectedBusinessId = 18; + const expectedAbsenceTypeId = 1; + + expect(createdAbsence.businessFk).toEqual(expectedBusinessId); + expect(createdAbsence.dayOffTypeFk).toEqual(expectedAbsenceTypeId); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js index 15bd854ed..16036eac6 100644 --- a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js +++ b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js @@ -12,45 +12,68 @@ describe('Worker deleteAbsence()', () => { ctx.req.__ = value => { return value; }; - let createdAbsence; beforeEach(async() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); - createdAbsence = await app.models.Calendar.create({ - businessFk: businessId, - dayOffTypeFk: 1, - dated: new Date() - }); - }); - - afterEach(async() => { - await app.models.Calendar.destroyById(createdAbsence.id); }); it('should return an error for a user without enough privileges', async() => { activeCtx.accessToken.userId = 106; + const tx = await app.models.Calendar.beginTransaction({}); - let error; - await app.models.Worker.deleteAbsence(ctx, 18, createdAbsence.id).catch(e => { - error = e; - }).finally(() => { - expect(error.message).toEqual(`You don't have enough privileges`); - }); + try { + const options = {transaction: tx}; + const createdAbsence = await app.models.Calendar.create({ + businessFk: businessId, + dayOffTypeFk: 1, + dated: new Date() + }, options); - expect(error).toBeDefined(); + ctx.args = {absenceId: createdAbsence.id}; + + let error; + await app.models.Worker.deleteAbsence(ctx, workerId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); - it('should create a new absence', async() => { + it('should successfully delete an absence', async() => { activeCtx.accessToken.userId = 19; - expect(createdAbsence.businessFk).toEqual(businessId); + const tx = await app.models.Calendar.beginTransaction({}); - await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id); + try { + const options = {transaction: tx}; + const createdAbsence = await app.models.Calendar.create({ + businessFk: businessId, + dayOffTypeFk: 1, + dated: new Date() + }, options); - const deletedAbsence = await app.models.Calendar.findById(createdAbsence.id); + ctx.args = {absenceId: createdAbsence.id}; - expect(deletedAbsence).toBeNull(); + await app.models.Worker.deleteAbsence(ctx, workerId, options); + + const deletedAbsence = await app.models.Calendar.findById(createdAbsence.id, null, options); + + expect(deletedAbsence).toBeNull(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/worker/back/methods/worker/specs/holidays.spec.js b/modules/worker/back/methods/worker/specs/holidays.spec.js new file mode 100644 index 000000000..6f588147c --- /dev/null +++ b/modules/worker/back/methods/worker/specs/holidays.spec.js @@ -0,0 +1,30 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); + +describe('Worker holidays()', () => { + const businessId = 106; + const workerId = 106; + const activeCtx = { + accessToken: {userId: workerId}, + headers: {origin: 'http://localhost'} + }; + const ctx = {req: activeCtx}; + + beforeEach(async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should get the absence calendar for a full year contract', async() => { + const now = new Date(); + const year = now.getFullYear(); + + ctx.args = {businessFk: businessId, year: year}; + + const result = await app.models.Worker.holidays(ctx, workerId); + + expect(result.totalHolidays).toEqual(27.5); + expect(result.holidaysEnjoyed).toEqual(5); + }); +}); -- 2.40.1 From a72010554cd41201dd8261d98a3dd9ff4d2e8516 Mon Sep 17 00:00:00 2001 From: joan Date: Fri, 11 Jun 2021 10:58:00 +0200 Subject: [PATCH 08/10] Fixes and E2E updated --- e2e/helpers/selectors.js | 12 ++++----- e2e/paths/03-worker/05_calendar.spec.js | 2 +- modules/worker/front/calendar/index.html | 2 +- modules/worker/front/time-control/index.js | 30 ++++++++++++++++++---- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index a5da8dddd..850a8c3fb 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -888,7 +888,7 @@ export default { }, workerCalendar: { year: 'vn-worker-calendar vn-autocomplete[ng-model="$ctrl.year"]', - totalHolidaysUsed: 'vn-worker-calendar div.totalBox > div', + totalHolidaysUsed: 'vn-worker-calendar div.totalBox:first-child > div', penultimateMondayOfJanuary: 'vn-worker-calendar vn-calendar:nth-child(2) section:nth-child(22) > div', lastMondayOfMarch: 'vn-worker-calendar vn-calendar:nth-child(4) section:nth-child(29) > div', fistMondayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(8) > div', @@ -896,11 +896,11 @@ export default { secondTuesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(16) > div', secondWednesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(17) > div', secondThursdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(18) > div', - holidays: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(1)', - absence: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(2)', - halfHoliday: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(3)', - furlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(4)', - halfFurlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(5)', + holidays: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(1)', + absence: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(2)', + halfHoliday: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(3)', + furlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(4)', + halfFurlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(5)', }, invoiceOutIndex: { topbarSearch: 'vn-searchbar', diff --git a/e2e/paths/03-worker/05_calendar.spec.js b/e2e/paths/03-worker/05_calendar.spec.js index 1a7e2dd19..08ef71f13 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'; -fdescribe('Worker calendar path', () => { +describe('Worker calendar path', () => { let reasonableTimeBetweenClicks = 400; let browser; let page; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index d308c2fcb..f012e8e55 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -62,7 +62,7 @@
-
+
this.getAbsences()); + } + } + /** * The current selected date */ @@ -70,6 +83,11 @@ class Controller extends Section { } } + getActiveContract() { + return this.$http.get(`Workers/${this.worker.id}/activeContract`) + .then(res => this.businessId = res.data.businessFk); + } + fetchHours() { const params = {workerFk: this.$params.id}; const filter = { @@ -80,7 +98,6 @@ class Controller extends Section { }; this.$.model.applyFilter(filter, params).then(() => { this.getWorkedHours(this.started, this.ended); - this.getAbsences(); }); } @@ -89,10 +106,10 @@ class Controller extends Section { } getAbsences() { + const fullYear = this.started.getFullYear(); let params = { - workerFk: this.$params.id, - started: this.started, - ended: this.ended + businessFk: this.businessId, + year: fullYear }; return this.$http.get(`Calendars/absences`, {params}) @@ -257,5 +274,8 @@ Controller.$inject = ['$element', '$scope', 'vnWeekDays']; ngModule.vnComponent('vnWorkerTimeControl', { template: require('./index.html'), - controller: Controller + controller: Controller, + bindings: { + worker: '<' + } }); -- 2.40.1 From de30a2d978e0a552ddce31086f2dc568798038a0 Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 14 Jun 2021 08:09:53 +0200 Subject: [PATCH 09/10] Total holidays fix --- modules/worker/back/methods/worker/holidays.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/worker/back/methods/worker/holidays.js b/modules/worker/back/methods/worker/holidays.js index 704f4d389..f3ce0c661 100644 --- a/modules/worker/back/methods/worker/holidays.js +++ b/modules/worker/back/methods/worker/holidays.js @@ -125,12 +125,12 @@ module.exports = Self => { let startedTime; if (contractStarted < started) startedTime = started.getTime(); - else contractStarted.getTime(); + else startedTime = contractStarted.getTime(); let endedTime; if (!contractEnded || (contractEnded && contractEnded > ended)) endedTime = ended.getTime(); - else contractEnded.getTime(); + else endedTime = contractEnded.getTime(); const dayTimestamp = 1000 * 60 * 60 * 24; -- 2.40.1 From 59e90c2c10fa62e4087427877ff4a2a30f09fadd Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 14 Jun 2021 11:58:39 +0200 Subject: [PATCH 10/10] Removed commented block --- modules/worker/front/calendar/index.spec.js | 22 --------------------- 1 file changed, 22 deletions(-) diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index ec2f5dd8a..141bf6653 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -167,28 +167,6 @@ describe('Worker', () => { expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu'); }); - /* it(`should show an snackbar message if the selected day is not within the current year`, () => { - jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis(); - - const selectedDay = new Date(); - const $event = { - target: { - closest: () => { - return {$ctrl: {}}; - } - } - }; - const $days = [selectedDay]; - const pastYear = new Date(); - pastYear.setFullYear(pastYear.getFullYear() - 1); - - controller.date = pastYear; - controller.absenceType = {id: 1}; - controller.onSelection($event, $days); - - expect(controller.vnApp.showMessage).toHaveBeenCalledWith('You can just add absences within the current year'); - }); */ - it(`should call to the create() method`, () => { jest.spyOn(controller, 'create').mockReturnThis(); -- 2.40.1