diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 1c740b8ee..1aba201a8 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1833,12 +1833,12 @@ INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `from`) (8, DATE_ADD(CURDATE(), INTERVAL +5 DAY)), (8, DATE_ADD(CURDATE(), INTERVAL +6 DAY)); -INSERT INTO `vn`.`workerTimeControl`(`userFk`,`timed`,`manual`) +INSERT INTO `vn`.`workerTimeControl`(`userFk`,`timed`,`manual`, `direction`) VALUES - (106, CONCAT(CURDATE(), ' 07:00'), TRUE), - (106, CONCAT(CURDATE(), ' 10:00'), TRUE), - (106, CONCAT(CURDATE(), ' 10:10'), TRUE), - (106, CONCAT(CURDATE(), ' 15:00'), TRUE); + (106, CONCAT(CURDATE(), ' 07:00'), TRUE, 'in'), + (106, CONCAT(CURDATE(), ' 10:00'), TRUE, 'middle'), + (106, CONCAT(CURDATE(), ' 10:10'), TRUE, 'middle'), + (106, CONCAT(CURDATE(), ' 15:00'), TRUE, 'out'); INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `code`) VALUES @@ -1950,3 +1950,8 @@ INSERT INTO `vn`.`userPhone`(`id`, `userFk`, `typeFk`, `phone`) (65, 107, 'businessPhone', 700987987), (67, 106, 'businessPhone', 1111111112), (68, 106, 'personalPhone', 1111111113); + +INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`) + VALUES + (1, 43200, 129600, 734400, 43200, 50400); + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 838878b60..8d38f5466 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -679,34 +679,35 @@ export default { saturdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(6) > vn-icon-button', sundayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(7) > vn-icon-button', confirmButton: '.vn-dialog.shown tpl-buttons > button', - firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > span', - firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > span', - firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > span', - firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > span', - firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > span', - firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > span', - firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > span', - secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > span', - secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > span', - secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > span', - secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > span', - secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > span', - secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > span', - secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > span', - thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > span', - thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > span', - thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > span', - thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > span', - thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > span', - thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > span', - thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > span', - fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > span', - fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > span', - fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > span', - fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > span', - fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > span', - fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > span', - fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > span', + firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > vn-chip > div', + firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > vn-chip > div', + firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > vn-chip > div', + firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > vn-chip > div', + firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > vn-chip > div', + firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > vn-chip > div', + firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > vn-chip > div', + secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > vn-chip > div', + secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > vn-chip > div', + secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > vn-chip > div', + secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > vn-chip > div', + secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > vn-chip > div', + secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > vn-chip > div', + secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > vn-chip > div', + thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > div', + thirdEntryOfMondayDelete: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > vn-icon[icon="cancel"]', + thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > vn-chip > div', + thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > vn-chip > div', + thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > vn-chip > div', + thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > vn-chip > div', + thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > vn-chip > div', + thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > vn-chip > div', + fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > vn-chip > div', + fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > vn-chip > div', + fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > vn-chip > div', + fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > vn-chip > div', + fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > vn-chip > div', + fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > vn-chip > div', + fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > vn-chip > div', mondayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(1)', tuesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(2)', wednesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(3)', @@ -717,7 +718,8 @@ export default { weekWorkedHours: 'vn-worker-time-control vn-side-menu vn-label-value > section > span', nextMonthButton: 'vn-worker-time-control vn-side-menu vn-calendar vn-button[icon=keyboard_arrow_right]', secondWeekDay: 'vn-worker-time-control vn-side-menu vn-calendar .day:nth-child(8) > .day-number', - navigateBackToIndex: 'vn-worker-descriptor vn-icon[icon="chevron_left"]' + navigateBackToIndex: 'vn-worker-descriptor vn-icon[icon="chevron_left"]', + acceptDeleteDialog: '.vn-confirm.shown button[response="accept"]' }, invoiceOutIndex: { searchInvoiceOutInput: `vn-invoice-out-index vn-textfield input`, diff --git a/e2e/paths/03-worker-module/02_time_control.spec.js b/e2e/paths/03-worker-module/02_time_control.spec.js index ce7316773..706fc2a74 100644 --- a/e2e/paths/03-worker-module/02_time_control.spec.js +++ b/e2e/paths/03-worker-module/02_time_control.spec.js @@ -35,8 +35,30 @@ describe('Worker time control path', () => { expect(result).toEqual(scanTime); }); - it(`should scan out Hank Pym and forget to scan in from the break`, async() => { - const scanTime = '15:00'; + it(`should scan in Hank Pym for a wrong hour and forget to scan in from the break`, async() => { + const scanTime = '18:00'; + const result = await nightmare + .waitToClick(selectors.workerTimeControl.mondayAddTimeButton) + .pickTime(selectors.workerTimeControl.timeDialogInput, scanTime) + .waitToClick(selectors.workerTimeControl.confirmButton) + .waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText'); + + expect(result).toEqual(scanTime); + }); + + it(`should delete the wrong entry for Hank Pym`, async() => { + const wrongScanTime = '18:00'; + const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.thirdEntryOfMonday, wrongScanTime) + .waitToClick(selectors.workerTimeControl.thirdEntryOfMondayDelete) + .waitToClick(selectors.workerTimeControl.acceptDeleteDialog) + .waitForLastSnackbar(); + + expect(result).toEqual('Entry removed'); + }); + + it(`should scan out Hank Pym to leave early`, async() => { + const scanTime = '14:00'; const result = await nightmare .waitToClick(selectors.workerTimeControl.mondayAddTimeButton) .pickTime(selectors.workerTimeControl.timeDialogInput, scanTime) @@ -54,7 +76,7 @@ describe('Worker time control path', () => { .waitToClick(selectors.workerTimeControl.confirmButton) .waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText'); - expect(result).toEqual('15:00'); + expect(result).toEqual('14:00'); }); it(`should the third entry be the scan in from break`, async() => { @@ -67,9 +89,10 @@ describe('Worker time control path', () => { it(`should check Hank Pym worked 8 hours`, async() => { const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '07:00 h.') .waitToGetProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText'); - expect(result).toEqual('08:00 h.'); + expect(result).toEqual('07:00 h.'); }); }); @@ -120,6 +143,7 @@ describe('Worker time control path', () => { it(`should check Hank Pym worked 8 happy hours`, async() => { const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.tuesdayWorkedHours, '08:00 h.') .waitToGetProperty(selectors.workerTimeControl.tuesdayWorkedHours, 'innerText'); expect(result).toEqual('08:00 h.'); @@ -173,6 +197,7 @@ describe('Worker time control path', () => { it(`should check Hank Pym worked 8 cheerfull hours`, async() => { const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.wednesdayWorkedHours, '08:00 h.') .waitToGetProperty(selectors.workerTimeControl.wednesdayWorkedHours, 'innerText'); expect(result).toEqual('08:00 h.'); @@ -226,6 +251,7 @@ describe('Worker time control path', () => { it(`should check Hank Pym worked 8 joyfull hours`, async() => { const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.thursdayWorkedHours, '08:00 h.') .waitToGetProperty(selectors.workerTimeControl.thursdayWorkedHours, 'innerText'); expect(result).toEqual('08:00 h.'); @@ -279,6 +305,7 @@ describe('Worker time control path', () => { it(`should check Hank Pym worked 8 hours with a smile on his face`, async() => { const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.fridayWorkedHours, '08:00 h.') .waitToGetProperty(selectors.workerTimeControl.fridayWorkedHours, 'innerText'); expect(result).toEqual('08:00 h.'); @@ -319,6 +346,7 @@ describe('Worker time control path', () => { it(`should check Hank Pym worked 8 hours with all his will`, async() => { const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.saturdayWorkedHours, '08:00 h.') .waitToGetProperty(selectors.workerTimeControl.saturdayWorkedHours, 'innerText'); expect(result).toEqual('08:00 h.'); @@ -350,6 +378,7 @@ describe('Worker time control path', () => { it(`should check Hank Pym worked 8 glad hours`, async() => { const result = await nightmare + .waitForTextInElement(selectors.workerTimeControl.sundayWorkedHours, '08:00 h.') .waitToGetProperty(selectors.workerTimeControl.sundayWorkedHours, 'innerText'); expect(result).toEqual('08:00 h.'); @@ -387,10 +416,10 @@ describe('Worker time control path', () => { it('should Hank Pym check his hours are alright', async() => { const wholeWeekHours = await nightmare - .waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '56:00 h.') + .waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '55:00 h.') .waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText'); - expect(wholeWeekHours).toEqual('56:00 h.'); + expect(wholeWeekHours).toEqual('55:00 h.'); }); }); }); diff --git a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js index 717d4ce8c..f04292681 100644 --- a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js @@ -1,3 +1,5 @@ +const UserError = require('vn-loopback/util/user-error'); + module.exports = Self => { Self.remoteMethodCtx('deleteTimeEntry', { description: 'Deletes a manual time entry for a worker if the user role is above the worker', @@ -20,13 +22,17 @@ module.exports = Self => { }); Self.deleteTimeEntry = async(ctx, id) => { + const currentUserId = ctx.req.accessToken.userId; const workerModel = Self.app.models.Worker; const targetTimeEntry = await Self.findById(id); - const hasRightsToDelete = await workerModel.isSubordinate(ctx, targetTimeEntry.userFk); + const isSubordinate = await workerModel.isSubordinate(ctx, targetTimeEntry.userFk); + const isTeamBoss = await Self.app.models.Account.hasRole(currentUserId, 'teamBoss'); - if (!hasRightsToDelete) + const notAllowed = isSubordinate === false || (isSubordinate && currentUserId == targetTimeEntry.userFk && !isTeamBoss); + + if (notAllowed) throw new UserError(`You don't have enough privileges`); return Self.rawSql('CALL vn.workerTimeControl_remove(?, ?)', [ diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index 30c8d1604..c355138c3 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -88,6 +88,8 @@ class Controller extends Component { } getWorkedHours(from, to) { + this.weekTotalHours = null; + let weekTotalHours = 0; let params = { id: this.$params.id, from: from, @@ -102,6 +104,7 @@ class Controller extends Component { for (const workDay of workDays) { workDay.dated = new Date(workDay.dated); map.set(workDay.dated, workDay); + weekTotalHours += workDay.workedHours; } for (const weekDay of this.weekDays) { @@ -114,9 +117,13 @@ class Controller extends Component { return weekDay.dated >= from && weekDay.dated <= to; }); - weekDay.expectedHours = workDay.expectedHours; - weekDay.workedHours = workDay.workedHours; + + if (workDay) { + weekDay.expectedHours = workDay.expectedHours; + weekDay.workedHours = workDay.workedHours; + } } + this.weekTotalHours = weekTotalHours; }); } @@ -128,32 +135,33 @@ class Controller extends Component { let todayInWeek = this.weekDays.find(day => day.dated.getTime() === today.getTime()); - if (todayInWeek && todayInWeek.hours) { - const remainingTime = (todayInWeek.expectedHours - todayInWeek.workedHours) * 1000; - const lastKnownTime = new Date(todayInWeek.hours[todayInWeek.hours.length - 1].timed).getTime(); - const finishTimeStamp = lastKnownTime + remainingTime; + if (todayInWeek && todayInWeek.hours && todayInWeek.hours.length) { + const remainingTime = todayInWeek.workedHours ? ((todayInWeek.expectedHours - todayInWeek.workedHours) * 1000) : null; + const lastKnownEntry = todayInWeek.hours[todayInWeek.hours.length - 1]; + const lastKnownTime = new Date(lastKnownEntry.timed).getTime(); + const finishTimeStamp = lastKnownTime && remainingTime ? lastKnownTime + remainingTime : null; - let finishDate = new Date(finishTimeStamp); - let hour = finishDate.getHours(); - let minute = finishDate.getMinutes(); + if (finishTimeStamp) { + let finishDate = new Date(finishTimeStamp); + let hour = finishDate.getHours(); + let minute = finishDate.getMinutes(); - if (hour < 10) hour = `0${hour}`; - if (minute < 10) minute = `0${minute}`; + if (hour < 10) hour = `0${hour}`; + if (minute < 10) minute = `0${minute}`; - return `${hour}:${minute} h.`; + return `${hour}:${minute} h.`; + } } } + set weekTotalHours(totalHours) { + if (!totalHours) return this._weekTotalHours = this.formatHours(0); + + this._weekTotalHours = this.formatHours(totalHours); + } + get weekTotalHours() { - if (!this.weekDays) return 0; - let total = 0; - - this.weekDays.forEach(weekday => { - if (weekday.workedHours) - total += weekday.workedHours; - }); - - return this.formatHours(total); + return this._weekTotalHours; } formatHours(timestamp) {