From 2ca8de1d2eb063ee63a0e2c2c3771b28350db875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Sun, 2 Mar 2025 09:41:53 +0100 Subject: [PATCH] feat: refs #8644 workerTimeControl add mandatory break times and update procedures --- .../procedures/workerTimeControl_clockIn.sql | 81 +++++++++-------- .../workerTimeControl_direction.sql | 62 +++++++++---- .../11453-pinkTulip/00-firstScript.sql | 8 ++ .../worker-time-control/specs/clockIn.spec.js | 86 +++++++++++++++++-- .../specs/timeEntry.spec.js | 28 +++--- 5 files changed, 188 insertions(+), 77 deletions(-) create mode 100644 db/versions/11453-pinkTulip/00-firstScript.sql diff --git a/db/routines/vn/procedures/workerTimeControl_clockIn.sql b/db/routines/vn/procedures/workerTimeControl_clockIn.sql index 522546918d..714d9637ae 100644 --- a/db/routines/vn/procedures/workerTimeControl_clockIn.sql +++ b/db/routines/vn/procedures/workerTimeControl_clockIn.sql @@ -18,14 +18,13 @@ BEGIN * Solo retorna el primer problema, en caso de no ocurrir ningún error se añadirá * fichada a la tabla vn.workerTimeControl */ - DECLARE vLastIn DATETIME; DECLARE vLastOut DATETIME; DECLARE vNextIn DATETIME; DECLARE vNextOut DATETIME; DECLARE vNextDirection ENUM('in', 'out'); DECLARE vLastDirection ENUM('in', 'out'); - DECLARE vDayMaxTime INTEGER; + DECLARE vDayMaxTime INTEGER; DECLARE vDayBreak INT; DECLARE vShortWeekBreak INT; DECLARE vLongWeekBreak INT; @@ -40,6 +39,7 @@ BEGIN DECLARE vIsManual BOOLEAN DEFAULT TRUE; DECLARE vMaxWorkShortCycle INT; DECLARE vMaxWorkLongCycle INT; + DECLARE vReentryWaitTime TIME DEFAULT NULL; DECLARE EXIT HANDLER FOR SQLSTATE '45000' BEGIN @@ -52,19 +52,19 @@ BEGIN WHERE w.id = vWorkerFk; SELECT `description` INTO vErrorMessage - FROM workerTimeControlError + FROM workerTimeControlError WHERE `code` = vErrorCode; IF vErrorMessage IS NULL THEN SET vErrorMessage = 'Error sin definir'; END IF; - SELECT vErrorMessage `error`; + SELECT CONCAT(vErrorMessage, IFNULL(DATE_FORMAT(vReentryWaitTime, '%i:%s'), '')) `error`; SELECT CONCAT(vUserName, ' no ha podido fichar por el siguiente problema: ', vErrorMessage) INTO vErrorMessage; - + CALL mail_insert( vMailTo, vMailTo, 'Error al fichar', vErrorMessage); END; @@ -97,19 +97,19 @@ BEGIN JOIN workerTimeControlConfig wc WHERE b.workerFk = vWorkerFk AND vDated BETWEEN b.started AND IFNULL(b.ended, vDated); - + -- CONTRATO EN VIGOR IF vDayBreak IS NULL THEN SET vErrorCode = 'INACTIVE_BUSINESS'; CALL util.throw(vErrorCode); END IF; - + -- FICHADAS A FUTURO IF vTimed > util.VN_NOW() + INTERVAL 1 MINUTE THEN SET vErrorCode = 'IS_NOT_ALLOWED_FUTURE'; CALL util.throw(vErrorCode); END IF; - + -- VERIFICAR SI ESTÁ PERMITIDO TRABAJAR CALL timeBusiness_calculateByWorker(vWorkerFk, vDated, vDated); SELECT isAllowedToWork INTO vIsAllowedToWork @@ -124,6 +124,16 @@ BEGIN -- DIRECCION CORRECTA CALL workerTimeControl_direction(vWorkerFk, vTimed); + + SELECT reentryWaitTime INTO vReentryWaitTime + FROM tmp.workerTimeControlDirection + LIMIT 1; + + IF vReentryWaitTime IS NOT NULL THEN + SET vErrorCode = 'WAIT_TIME'; + CALL util.throw(vErrorCode); + END IF; + IF (SELECT IF((IF(option1 IN ('inMiddle', 'outMiddle'), 'middle', @@ -138,13 +148,13 @@ BEGIN ) THEN SET vIsError = TRUE; END IF; - - + + IF vIsError THEN SET vErrorCode = 'WRONG_DIRECTION'; IF(SELECT option1 IS NULL AND option2 IS NULL FROM tmp.workerTimeControlDirection) THEN - + SET vErrorCode = 'DAY_MAX_TIME'; END IF; CALL util.throw(vErrorCode); @@ -158,7 +168,7 @@ BEGIN AND timed < vTimed ORDER BY timed DESC LIMIT 1; - + IF (SELECT IF(vDirection = 'in', MOD(COUNT(*), 2) , IF (vDirection = 'out', NOT MOD(COUNT(*), 2), FALSE)) @@ -169,7 +179,7 @@ BEGIN SET vErrorCode = 'ODD_WORKERTIMECONTROL'; CALL util.throw(vErrorCode); END IF; - + -- DESCANSO DIARIO SELECT timed INTO vLastOut FROM workerTimeControl @@ -178,7 +188,7 @@ BEGIN AND timed < vTimed ORDER BY timed DESC LIMIT 1; - + SELECT timed INTO vNextIn FROM workerTimeControl WHERE userFk = vWorkerFk @@ -186,7 +196,7 @@ BEGIN AND timed > vTimed ORDER BY timed ASC LIMIT 1; - + CASE vDirection WHEN 'in' THEN IF UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(vLastOut) <= vDayBreak THEN @@ -204,11 +214,9 @@ BEGIN CALL util.throw(vErrorCode); END IF; - - IF (vDirection IN('in', 'out')) THEN -- VERIFICA MAXIMO TIEMPO DESDE ENTRADA HASTA LA SALIDA - + SELECT timed INTO vNextOut FROM workerTimeControl WHERE userFk = vWorkerFk @@ -216,7 +224,7 @@ BEGIN AND timed > vTimed ORDER BY timed ASC LIMIT 1; - + SELECT direction INTO vNextDirection FROM workerTimeControl WHERE userFk = vWorkerFk @@ -224,7 +232,7 @@ BEGIN AND timed > vTimed ORDER BY timed ASC LIMIT 1; - + SELECT direction INTO vLastDirection FROM workerTimeControl WHERE userFk = vWorkerFk @@ -232,34 +240,34 @@ BEGIN AND timed < vTimed ORDER BY timed ASC LIMIT 1; - - IF (vDirection ='in' - AND vNextDirection = 'out' + + IF (vDirection ='in' + AND vNextDirection = 'out' AND UNIX_TIMESTAMP(vNextOut) - UNIX_TIMESTAMP(vTimed) > vDayMaxTime) OR - (vDirection ='out' + (vDirection ='out' AND vLastDirection = 'in' - AND UNIX_TIMESTAMP(vTimed) -UNIX_TIMESTAMP(vLastIn) > vDayMaxTime) THEN + AND UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(vLastIn) > vDayMaxTime) THEN SET vErrorCode = 'DAY_MAX_TIME'; CALL util.throw(vErrorCode); END IF; - + -- VERIFICA DESCANSO SEMANAL - + WITH wtc AS( - (SELECT timed - FROM vn.workerTimeControl + (SELECT timed + FROM vn.workerTimeControl WHERE userFk = vWorkerFk AND direction IN ('in', 'out') - AND timed BETWEEN vTimed - INTERVAL (vWeekScope * 2) SECOND + AND timed BETWEEN vTimed - INTERVAL (vWeekScope * 2) SECOND AND vTimed + INTERVAL (vWeekScope * 2) SECOND ) - UNION + UNION (SELECT vTimed) ), wtcGap AS( SELECT timed, - TIMESTAMPDIFF(SECOND, LAG(timed) OVER (ORDER BY timed), timed) gap + TIMESTAMPDIFF(SECOND, LAG(timed) OVER (ORDER BY timed), timed) gap FROM wtc ORDER BY timed - ), wtcBreak AS( + ), wtcBreak AS( SELECT timed, IF(IFNULL(gap, 0) > vShortWeekBreak, TRUE, FALSE) hasShortBreak, IF(IFNULL(gap, 0) > vLongWeekBreak, TRUE, FALSE) hasLongBreak @@ -270,8 +278,8 @@ BEGIN SUM(hasShortBreak) OVER (ORDER BY timed) breakCounter , LEAD(hasLongBreak) OVER (ORDER BY timed) nextHasLongBreak FROM wtcBreak - )SELECT TIMESTAMPDIFF(SECOND, MIN(timed), MAX(timed)) > vMaxWorkLongCycle OR - (TIMESTAMPDIFF(SECOND, MIN(timed), MAX(timed))> vMaxWorkShortCycle + )SELECT TIMESTAMPDIFF(SECOND, MIN(timed), MAX(timed)) > vMaxWorkLongCycle OR + (TIMESTAMPDIFF(SECOND, MIN(timed), MAX(timed))> vMaxWorkShortCycle AND NOT SUM(IFNULL(nextHasLongBreak, 1))) hasError INTO vIsError FROM wtcBreakCounter @@ -291,5 +299,6 @@ BEGIN SELECT LAST_INSERT_ID() id; -END$$ +END +$$ DELIMITER ; diff --git a/db/routines/vn/procedures/workerTimeControl_direction.sql b/db/routines/vn/procedures/workerTimeControl_direction.sql index 84db396cc4..70a7b40c0b 100644 --- a/db/routines/vn/procedures/workerTimeControl_direction.sql +++ b/db/routines/vn/procedures/workerTimeControl_direction.sql @@ -1,39 +1,45 @@ DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`workerTimeControl_direction`(vWorkerFk VARCHAR(10), vTimed DATETIME) +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`workerTimeControl_direction`( + vWorkerFk VARCHAR(10), + vTimed DATETIME +) BEGIN /** - * Devuelve que direcciones de fichadas son lógicas a partir de la anterior fichada + * Devuelve que direcciones de fichadas son lógicas a partir de la anterior fichada, * @param vWorkerFk Identificador del trabajador - * @return (option1, option2) - * Los valores posibles de retorno son ('in', 'inMiddle', 'outMiddle', 'out') + * @return tmp.workerTimeControlDirection(option1, option2, reentryWaitTime) + * Valores posibles options('in', 'inMiddle', 'outMiddle', 'out') */ - DECLARE vLastIn DATETIME ; - DECLARE vIsMiddleOdd BOOLEAN ; + DECLARE vLastIn DATETIME; + DECLARE vLastMiddleIn DATETIME; + DECLARE vIsMiddleOdd BOOLEAN; DECLARE vMailTo VARCHAR(50) DEFAULT NULL; DECLARE vUserName VARCHAR(50) DEFAULT NULL; + DECLARE vReentryWaitTime TIME; - IF (vTimed IS NULL) THEN + IF (vTimed IS NULL) THEN SET vTimed = util.VN_NOW(); END IF; - + SELECT timed INTO vLastIn - FROM workerTimeControl + FROM workerTimeControl WHERE userFk = vWorkerFk - AND direction = 'in' + AND direction = 'in' AND timed < vTimed ORDER BY timed DESC LIMIT 1; - + SELECT (COUNT(*)mod 2 = 1) INTO vIsMiddleOdd - FROM workerTimeControl + FROM workerTimeControl WHERE userFk = vWorkerFk - AND direction = 'middle' + AND direction = 'middle' AND timed BETWEEN vLastIn AND vTimed; DROP TEMPORARY TABLE IF EXISTS tmp.workerTimeControlDirection; CREATE TEMPORARY TABLE tmp.workerTimeControlDirection SELECT IF(isCorrect, option1, NULL) option1, - IF(isCorrect, option2, NULL) option2 + IF(isCorrect, option2, NULL) option2, + CAST(NULL AS TIME) reentryWaitTime FROM( SELECT IF(w.direction <> 'out' AND (UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(w.timed) > wc.dayBreak), FALSE, TRUE) isCorrect, CASE WHEN w.direction ='in' THEN 'inMiddle' WHEN w.direction = 'out' THEN 'in' @@ -59,6 +65,26 @@ BEGIN VALUES('in', NULL); END IF; + IF (SELECT option1 ='outMiddle' AND option2 IS NULL FROM tmp.workerTimeControlDirection) THEN + SELECT timed INTO vLastMiddleIn + FROM workerTimeControl + WHERE userFk = vWorkerFk + AND timed < vTimed + ORDER BY timed DESC + LIMIT 1; + + SELECT TIMEDIFF(vLastMiddleIn + INTERVAL wtc.maxTimeToBreak SECOND, vTimed) INTO vReentryWaitTime + FROM tmp.workerTimeControlDirection wt + JOIN workerTimeControlConfig wtc + WHERE TIME(vLastMiddleIn) BETWEEN wtc.mandatoryBreakFrom AND wtc.mandatoryBreakTo + AND TIMESTAMPDIFF(SECOND, vLastMiddleIn, vTimed) < wtc.maxTimeToBreak; + + IF vReentryWaitTime THEN + UPDATE tmp.workerTimeControlDirection + SET reentryWaitTime = vReentryWaitTime; + END IF; + END IF; + IF (SELECT option1 IS NULL AND option2 IS NULL FROM tmp.workerTimeControlDirection) THEN SELECT CONCAT(u.name, '@verdnatura.es'), CONCAT(w.firstName, ' ', w.lastName) INTO vMailTo, vUserName @@ -66,10 +92,10 @@ BEGIN JOIN worker w ON w.bossFk = u.id WHERE w.id = vWorkerFk; - CALL mail_insert( - vMailTo, - vMailTo, - 'Error al fichar', + CALL mail_insert( + vMailTo, + vMailTo, + 'Error al fichar', CONCAT(vUserName, ' tiene problemas para fichar')); END IF; END$$ diff --git a/db/versions/11453-pinkTulip/00-firstScript.sql b/db/versions/11453-pinkTulip/00-firstScript.sql new file mode 100644 index 0000000000..351fd8396d --- /dev/null +++ b/db/versions/11453-pinkTulip/00-firstScript.sql @@ -0,0 +1,8 @@ +ALTER TABLE vn.workerTimeControlConfig + ADD COLUMN `mandatoryBreakFrom` time DEFAULT '12:00:00' + COMMENT 'Tiempo desde el que se obligará a realizar un descanso para jornada partida'; +ALTER TABLE vn.workerTimeControlConfig + ADD COLUMN `mandatoryBreakTo` time DEFAULT '15:00:00'; + +INSERT INTO vn.workerTimeControlError (`code`, `description`) + VALUES ('WAIT_TIME', 'El descanso terminará en '); diff --git a/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js b/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js index daf7284ac0..78d63ebf32 100644 --- a/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js @@ -45,7 +45,7 @@ describe('workerTimeControl clockIn()', () => { 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; @@ -61,7 +61,7 @@ describe('workerTimeControl clockIn()', () => { 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(); @@ -87,7 +87,7 @@ describe('workerTimeControl clockIn()', () => { 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); @@ -111,10 +111,10 @@ describe('workerTimeControl clockIn()', () => { 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); @@ -196,7 +196,7 @@ describe('workerTimeControl clockIn()', () => { 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 @@ -204,7 +204,7 @@ describe('workerTimeControl clockIn()', () => { const {direction: updatedDirection} = await models.WorkerTimeControl.findById(outTimeEntryId,{fields:['direction']},options); expect(updatedDirection).toEqual('out'); - + await tx.rollback(); } catch (e) { await tx.rollback(); @@ -544,7 +544,7 @@ describe('workerTimeControl clockIn()', () => { }); 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); @@ -598,7 +598,7 @@ describe('workerTimeControl clockIn()', () => { expect(error.message).toBe(`Descanso semanal`); }); - + it('should throw an error when the 72h weekly rest is fulfilled', async() => { let date = Date.vnNew(); @@ -626,6 +626,74 @@ describe('workerTimeControl clockIn()', () => { expect(error).not.toBeDefined; }); + + it('should enforce a 1-hour break if the break starts between 12:00 and 15:00', 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}; + + try { + date.setHours(11, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(13, 30, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(14, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + let error; + try { + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(400); + expect(error.message).toMatch(/El descanso terminará en \d{2}:\d{2}/); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should allow resuming work after a full 1-hour break', 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}; + try { + date.setHours(11, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(14, 00, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(15, 00, 0); + ctx.args = {timed: date, direction: 'middle'}; + let error; + try { + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); }); }); diff --git a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js index 97f1005017..2874f2ebcd 100644 --- a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js @@ -95,9 +95,9 @@ describe('workerTimeControl add/delete timeEntry()', () => { try { await populateWeek(dated, monday, monday, ctx, hankPymId, options); - dated.setHours(14, 59, 0); + dated.setHours(04, 59, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); - dated.setHours(15, 14, 0); + dated.setHours(05, 14, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); const start = new Date(dated - 1); @@ -124,9 +124,9 @@ describe('workerTimeControl add/delete timeEntry()', () => { try { await populateWeek(dated, monday, monday, ctx, hankPymId, options); - dated.setHours(15, 0, 0); + dated.setHours(05, 0, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); - dated.setHours(15, 15, 0); + dated.setHours(05, 15, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); const start = new Date(dated - 1); @@ -153,9 +153,9 @@ describe('workerTimeControl add/delete timeEntry()', () => { try { await populateWeek(dated, monday, monday, ctx, hankPymId, options); - dated.setHours(14, 59, 0); + dated.setHours(04, 59, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); - dated.setHours(15, 24, 0); + dated.setHours(05, 24, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); const start = new Date(dated - 1); @@ -182,9 +182,9 @@ describe('workerTimeControl add/delete timeEntry()', () => { try { await populateWeek(dated, monday, monday, ctx, hankPymId, options); - dated.setHours(15, 0, 0); + dated.setHours(05, 0, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); - dated.setHours(15, 25, 0); + dated.setHours(05, 25, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); const start = new Date(dated - 1); @@ -211,9 +211,9 @@ describe('workerTimeControl add/delete timeEntry()', () => { try { await populateWeek(dated, monday, monday, ctx, hankPymId, options); - dated.setHours(14, 59, 0); + dated.setHours(04, 59, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); - dated.setHours(15, 59, 0); + dated.setHours(05, 59, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); const start = new Date(dated - 1); @@ -240,9 +240,9 @@ describe('workerTimeControl add/delete timeEntry()', () => { try { await populateWeek(dated, monday, monday, ctx, hankPymId, options); - dated.setHours(15, 0, 0); + dated.setHours(05, 0, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); - dated.setHours(16, 0, 0); + dated.setHours(06, 0, 0); await addTimeEntry(ctx, dated, 'middle', hankPymId, options); const start = new Date(dated - 1); @@ -286,10 +286,10 @@ async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { dateEnd.setDate(dateStart.getDate() + dayEnd); for (let i = dayStart; i <= dayEnd; i++) { - dateStart.setHours(10, 0, 0); + dateStart.setHours(00, 0, 0); ctx.args = {timed: dateStart, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - dateStart.setHours(18, 0, 0); + dateStart.setHours(08, 0, 0); ctx.args = {timed: dateStart, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); dateStart.setDate(dateStart.getDate() + 1);