diff --git a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js deleted file mode 100644 index 3e64a985a..000000000 --- a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js +++ /dev/null @@ -1,182 +0,0 @@ -const Imap = require('imap'); -module.exports = Self => { - Self.remoteMethod('checkInbox', { - description: 'Check an email inbox and process it', - accessType: 'READ', - returns: - { - arg: 'body', - type: 'file', - root: true - }, - http: { - path: `/checkInbox`, - verb: 'POST' - } - }); - - Self.checkInbox = async() => { - let imapConfig = await Self.app.models.WorkerTimeControlParams.findOne(); - let imap = new Imap({ - user: imapConfig.mailUser, - password: imapConfig.mailPass, - host: imapConfig.mailHost, - port: 993, - tls: true - }); - let isEmailOk; - let uid; - let emailBody; - - function openInbox(cb) { - imap.openBox('INBOX', true, cb); - } - - imap.once('ready', function() { - openInbox(function(err, box) { - if (err) throw err; - const totalMessages = box.messages.total; - if (totalMessages == 0) - imap.end(); - - let f = imap.seq.fetch('1:*', { - bodies: ['HEADER.FIELDS (FROM SUBJECT)', '1'], - struct: true - }); - f.on('message', function(msg, seqno) { - isEmailOk = false; - msg.on('body', function(stream, info) { - let buffer = ''; - let bufferCopy = ''; - stream.on('data', function(chunk) { - buffer = chunk.toString('utf8'); - if (info.which === '1' && bufferCopy.length == 0) - bufferCopy = buffer.replace(/\s/g, ' '); - }); - stream.on('end', function() { - if (bufferCopy.length > 0) { - emailBody = bufferCopy.toUpperCase().trim(); - - const bodyPositionOK = emailBody.match(/\bOK\b/i); - const bodyPositionIndex = (bodyPositionOK.index == 0 || bodyPositionOK.index == 122); - if (bodyPositionOK != null && bodyPositionIndex) - isEmailOk = true; - else - isEmailOk = false; - } - }); - msg.once('attributes', function(attrs) { - uid = attrs.uid; - }); - msg.once('end', function() { - if (info.which === 'HEADER.FIELDS (FROM SUBJECT)') { - if (isEmailOk) { - imap.move(uid, 'exito', function(err) { - }); - emailConfirm(buffer); - } else { - imap.move(uid, 'error', function(err) { - }); - emailReply(buffer, emailBody); - } - } - }); - }); - }); - f.once('end', function() { - imap.end(); - }); - }); - }); - - imap.connect(); - return 'Leer emails de gestion horaria'; - }; - - async function emailConfirm(buffer) { - const now = new Date(); - const from = JSON.stringify(Imap.parseHeader(buffer).from); - const subject = JSON.stringify(Imap.parseHeader(buffer).subject); - - const timeControlDate = await getEmailDate(subject); - const week = timeControlDate[0]; - const year = timeControlDate[1]; - const user = await getUser(from); - let workerMail; - - if (user.id != null) { - workerMail = await Self.app.models.WorkerTimeControlMail.findOne({ - where: { - week: week, - year: year, - workerFk: user.id - } - }); - } - if (workerMail != null) { - await workerMail.updateAttributes({ - updated: now, - state: 'CONFIRMED' - }); - } - } - - async function emailReply(buffer, emailBody) { - const now = new Date(); - const from = JSON.stringify(Imap.parseHeader(buffer).from); - const subject = JSON.stringify(Imap.parseHeader(buffer).subject); - - const timeControlDate = await getEmailDate(subject); - const week = timeControlDate[0]; - const year = timeControlDate[1]; - const user = await getUser(from); - let workerMail; - - if (user.id != null) { - workerMail = await Self.app.models.WorkerTimeControlMail.findOne({ - where: { - week: week, - year: year, - workerFk: user.id - } - }); - - if (workerMail != null) { - await workerMail.updateAttributes({ - updated: now, - state: 'REVISE', - emailResponse: emailBody - }); - } else - await sendMail(user, subject, emailBody); - } - } - - async function getUser(workerEmail) { - const userEmail = workerEmail.match(/(?<=<)(.*?)(?=>)/); - - let [user] = await Self.rawSql(`SELECT u.id,u.name FROM account.user u - LEFT JOIN account.mailForward m on m.account = u.id - WHERE forwardTo =? OR - CONCAT(u.name,'@verdnatura.es') = ?`, - [userEmail[0], userEmail[0]]); - - return user; - } - - async function getEmailDate(subject) { - const date = subject.match(/\d+/g); - return date; - } - - async function sendMail(user, subject, emailBody) { - const sendTo = 'rrhh@verdnatura.es'; - const emailSubject = subject + ' ' + user.name; - - await Self.app.models.Mail.create({ - receiver: sendTo, - subject: emailSubject, - body: emailBody - }); - } -}; diff --git a/modules/worker/back/methods/worker-time-control/sendMail.js b/modules/worker/back/methods/worker-time-control/sendMail.js index c87df5cea..57f58ef22 100644 --- a/modules/worker/back/methods/worker-time-control/sendMail.js +++ b/modules/worker/back/methods/worker-time-control/sendMail.js @@ -225,11 +225,11 @@ module.exports = Self => { for (let journey of journeys) { const start = new Date(); const [startHours, startMinutes, startSeconds] = getTime(journey.start); - start.setHours(startHours, startMinutes, startSeconds); + start.setHours(startHours, startMinutes, startSeconds, 0); const end = new Date(); const [endHours, endMinutes, endSeconds] = getTime(journey.end); - end.setHours(endHours, endMinutes, endSeconds); + end.setHours(endHours, endMinutes, endSeconds, 0); const result = (end - start) / 1000; timeTableDecimalInSeconds += result; @@ -237,6 +237,7 @@ module.exports = Self => { for (let journey of journeys) { const timeTableDecimal = timeTableDecimalInSeconds / 3600; + console.log(timeTableDecimal); if (day.timeWorkDecimal == timeTableDecimal) { const timed = new Date(day.dated); const [startHours, startMinutes, startSeconds] = getTime(journey.start); @@ -349,11 +350,11 @@ module.exports = Self => { body: `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}` }, myOptions); - await models.WorkerTimeControlMail.create({ - workerFk: previousWorkerFk, - week: args.week, - year: args.year - }, myOptions); + // await models.WorkerTimeControlMail.create({ + // workerFk: previousWorkerFk, + // week: args.week, + // year: args.year + // }, myOptions); previousWorkerFk = day.workerFk; previousReceiver = day.receiver; diff --git a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js new file mode 100644 index 000000000..978891bd0 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js @@ -0,0 +1,529 @@ +/* eslint max-len: ["error", { "code": 150 }]*/ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('workerTimeControl sendMail()', () => { + const HHRRId = 37; + const teamBossId = 13; + const employeeId = 1; + const salesPersonId = 1106; + const salesBossId = 19; + const hankPymId = 1107; + const jessicaJonesId = 1110; + const monday = 1; + const tuesday = 2; + const thursday = 4; + const friday = 5; + const saturday = 6; + const sunday = 7; + const activeCtx = { + accessToken: {userId: 50}, + }; + const ctx = {req: activeCtx}; + + beforeAll(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + describe('WorkerTimeControl_clockIn calls', () => { + it('should fail to add a time entry if the target user has an absence that day', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + const date = new Date(); + date.setDate(date.getDate() - 16); + date.setHours(8, 0, 0); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + try { + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`No está permitido trabajar`); + }); + + it('should fail to add a time entry for a worker without an existing contract', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + const date = new Date(); + date.setFullYear(date.getFullYear() - 2); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`No hay un contrato en vigor`); + }); + + describe('direction errors', () => { + it('should throw an error when trying "in" direction twice', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('Should throw an error when trying "out" before closing a "middle" couple', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "middle" after "out"', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "out" direction twice', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + }); + + describe('12h rest', () => { + it('should throw an error when the 12h rest is not fulfilled yet', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario 12h.`); + }); + + it('should not fail as the 12h rest is fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 3500kg drivers with enforced 9h rest', () => { + it('should throw an error when the 9h enforced rest is not fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = jessicaJonesId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario 9h.`); + }); + + it('should not fail when the 9h enforced rest is fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = jessicaJonesId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 36h weekly rest', () => { + it('should throw an error when the 36h weekly rest is not fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setMonth(date.getMonth() - 2); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + date = weekDay(date, friday); + date.setHours(7, 59, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(8, 1, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); + }); + + it('should throw an error when the 36h weekly rest is not fulfilled again', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setMonth(date.getMonth() - 2); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + + try { + date = weekDay(date, saturday); + date.setHours(3, 59, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); + }); + }); + + describe('for 72h weekly rest', () => { + it('should throw when the 72h weekly rest is not fulfilled yet', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setMonth(date.getMonth() - 2); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, friday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, saturday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, saturday, ctx, workerId, options); + date = lastWeek(date); + + try { + date = weekDay(date, sunday); + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); + }); + }); + }); +}); + +function weekDay(date, dayToSet) { + const currentDay = date.getDay(); + const distance = dayToSet - currentDay; + + date.setDate(date.getDate() + distance); + return date; +} + +function nextWeek(date) { + const sunday = 7; + const currentDay = date.getDay(); + let newDate = date; + if (currentDay != 0) + newDate = weekDay(date, sunday); + + newDate.setDate(newDate.getDate() + 1); + return newDate; +} + +function lastWeek(date) { + const monday = 1; + newDate = weekDay(date, monday); + + newDate.setDate(newDate.getDate() - 1); + return newDate; +} + +async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { + const dateStart = new Date(weekDay(date, dayStart)); + const dateEnd = new Date(dateStart); + dateEnd.setDate(dateStart.getDate() + dayEnd); + + for (let i = dayStart; i <= dayEnd; i++) { + dateStart.setHours(8, 0, 0); + ctx.args = {timed: dateStart, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setHours(16, 0, 0); + ctx.args = {timed: dateStart, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setDate(dateStart.getDate() + 1); + } +} diff --git a/modules/worker/back/models/worker-time-control-mail.js b/modules/worker/back/models/worker-time-control-mail.js deleted file mode 100644 index 36f3851b6..000000000 --- a/modules/worker/back/models/worker-time-control-mail.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = Self => { - require('../methods/worker-time-control-mail/checkInbox')(Self); -};