const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); describe('workerTimeControl clockIn()', () => { const workerId = 9; const salesBossId = 19; const hankPymId = 1107; const jessicaJonesId = 1110; const HHRRId = 37; const teamBossId = 13; const monday = 1; const tuesday = 2; const thursday = 4; const friday = 5; const sunday = 7; const inTime = '2001-01-01T00:00:00.000Z'; const activeCtx = { accessToken: {userId: 50}, }; const ctx = {req: activeCtx}; beforeAll(() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); }); it('should correctly clock in', async() => { const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; await models.WorkerTimeControl.clockIn(workerId, inTime, 'in', 'test', options); const isClockIn = await models.WorkerTimeControl.findOne({ where: { userFk: workerId } }, options); expect(isClockIn).toBeDefined(); expect(isClockIn.direction).toBe('in'); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); it('should throw an error trying to change a middle hour to out not resting 12h', async() => { activeCtx.accessToken.userId = HHRRId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const entryTime = "2000-12-25T11:00:00.000Z"; ctx.args = {timed: entryTime, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); const middleTime ="2000-12-26T11:00:00.000Z"; ctx.args = {timed: middleTime, direction: 'middle'}; const middleEntryTime = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); const direction = 'out'; await models.WorkerTimeControl.updateTimeEntry(ctx, middleEntryTime.id, direction, options); await tx.rollback(); } catch (e) { expect(e.message).toBe('Superado el tiempo máximo entre entrada y salida'); await tx.rollback(); } }); it('should updates the time entry direction and remaining not be manual', async() => { activeCtx.accessToken.userId = HHRRId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const entryTime = "2000-12-25T11:00:00.000Z"; ctx.args = {timed: entryTime, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); const middleTime ="2000-12-25T16:00:00.000Z"; ctx.args = {timed: middleTime, direction: 'middle'}; const middleEntryTime = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); middleEntryTime.updateAttribute('manual', false); const direction = 'out'; const outTimeEntryId = await models.WorkerTimeControl.updateTimeEntry(ctx, middleEntryTime.id, direction, options); const outTimeEntry = await models.WorkerTimeControl.findById(outTimeEntryId, null, options); expect(outTimeEntry.manual).toBeFalsy(); await tx.rollback(); } catch (e) { await tx.rollback(); } }); it('should throw an error trying to add an "in" entry if the last clockIn is not out', async() => { activeCtx.accessToken.userId = HHRRId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; ctx.args = {timed: "2000-12-25T21:00:00.000Z", direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); ctx.args = {timed: "2000-12-25T22:00:00.000Z", direction: 'middle'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); ctx.args = {timed: "2000-12-25T22:30:00.000Z", direction: 'middle'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); ctx.args = {timed: "2000-12-26T01:00:00.000Z", direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { expect(e.message).toBe('Dirección incorrecta'); await tx.rollback(); } }); describe('as Role errors', () => { it('should add if the current user is team boss and the target user is himself', async() => { activeCtx.accessToken.userId = teamBossId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = Date.vnNew(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); expect(createdTimeEntry.id).toBeDefined(); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); it('should delete the created time entry for the team boss as himself', async() => { activeCtx.accessToken.userId = teamBossId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = Date.vnNew(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); expect(createdTimeEntry.id).toBeDefined(); await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); expect(deletedTimeEntry).toBeNull(); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); it('should edit the created time entry for the team boss as HHRR', async() => { activeCtx.accessToken.userId = HHRRId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = Date.vnNew(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const entryTime = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); expect(entryTime.id).toBeDefined(); const todayAtTwo = Date.vnNew(); todayAtTwo.setHours(2, 0, 0, 0); ctx.args = {timed: todayAtTwo, direction: 'middle'}; const middleTime = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); const direction = 'out'; const outTimeEntryId = await models.WorkerTimeControl.updateTimeEntry( ctx, middleTime.id, direction, options ); const {direction: updatedDirection} = await models.WorkerTimeControl.findById(outTimeEntryId,{fields:['direction']},options); expect(updatedDirection).toEqual('out'); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); }); describe('as saleBoss editor', () => { let workerId; beforeEach(() => { activeCtx.accessToken.userId = salesBossId; workerId = hankPymId; }); it('should fail to add a time entry if the target user has an absence that day', async() => { const date = Date.vnNew(); date.setHours(8, 0, 0); date.setDate(date.getDate() - 16); 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() => { const date = Date.vnNew(); date.setFullYear(date.getFullYear() - 2); 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 hay un contrato en vigor`); }); it('should fail to add a time entry for a worker without an existing contract and exceeding time', async() => { let date = Date.vnNew(); date.setDate(date.getDate() - 2); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(0, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date.setHours(20, 0, 1); 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(`Superado el tiempo máximo entre entrada y salida`); }); describe('direction errors', () => { let date = Date.vnNew(); date.setDate(date.getDate() - 1); let error; it('should throw an error when trying "in" direction twice', async() => { 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() => { 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() => { 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() => { 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() => { 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', () => { activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; it('should throw an error when the 12h rest is not fulfilled yet', async() => { let date = Date.vnNew(); date.setDate(date.getDate() - 2); 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`); }); it('should not fail as the 12h rest is fulfilled', async() => { let date = Date.vnNew(); date.setDate(date.getDate() - 2); 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', () => { activeCtx.accessToken.userId = salesBossId; const workerId = jessicaJonesId; it('should throw an error when the 9h enforced rest is not fulfilled', async() => { let date = Date.vnNew(); date.setDate(date.getDate() - 2); 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`); }); it('should not fail when the 9h enforced rest is fulfilled', async() => { let date = Date.vnNew(); date.setDate(date.getDate() - 2); 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 72h weekly rest', () => { it('should throw an error when work 11 consecutive days', async() => { let date = Date.vnNew(); date.setMonth(date.getMonth() - 1); 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, friday); date.setHours(10, 0, 1); 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`); }); it('should throw an error when the 72h weekly rest is not fulfilled', async() => { let date = Date.vnNew(); date.setMonth(date.getMonth() - 1); 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, sunday); date.setHours(17, 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`); }); it('should throw an error when the 72h weekly rest is fulfilled', async() => { let date = Date.vnNew(); date.setMonth(date.getMonth() - 1); 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, sunday); date.setHours(18, 00, 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; }); }); }); }); 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; } 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(10, 0, 0); ctx.args = {timed: dateStart, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); dateStart.setHours(18, 0, 0); ctx.args = {timed: dateStart, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); dateStart.setDate(dateStart.getDate() + 1); } }