From bb80621890c0631e67348dec8f862873c8e8b722 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 17 Jun 2021 15:09:04 +0200 Subject: [PATCH] 2874 - Edit time entry direction --- front/core/components/chip/index.html | 8 +- front/core/components/chip/index.js | 9 +- front/core/components/chip/style.scss | 53 +++++- .../worker-time-control/addTimeEntry.js | 54 ++++-- .../worker-time-control/deleteTimeEntry.js | 21 ++- .../specs/timeEntry.spec.js | 170 ++++++++++-------- .../worker-time-control/updateTimeEntry.js | 53 ++++++ .../worker/back/models/worker-time-control.js | 1 + .../back/models/worker-time-control.json | 8 +- modules/worker/front/calendar/index.html | 4 +- modules/worker/front/time-control/index.html | 45 ++++- modules/worker/front/time-control/index.js | 37 +++- .../worker/front/time-control/locale/es.yml | 1 + modules/worker/front/time-control/style.scss | 3 + 14 files changed, 339 insertions(+), 128 deletions(-) create mode 100644 modules/worker/back/methods/worker-time-control/updateTimeEntry.js diff --git a/front/core/components/chip/index.html b/front/core/components/chip/index.html index 38a56923d5..7cd56031c7 100644 --- a/front/core/components/chip/index.html +++ b/front/core/components/chip/index.html @@ -1,6 +1,12 @@ +
+
diff --git a/front/core/components/chip/index.js b/front/core/components/chip/index.js index 6cffecc2b3..ff0f19ba22 100644 --- a/front/core/components/chip/index.js +++ b/front/core/components/chip/index.js @@ -3,16 +3,19 @@ import Component from '../../lib/component'; import './style.scss'; export default class Chip extends Component { - onRemove() { - if (!this.disabled) this.emit('remove'); + onRemove($event) { + if (!this.disabled) this.emit('remove', {$event}); } } Chip.$inject = ['$element', '$scope', '$transclude']; ngModule.vnComponent('vnChip', { template: require('./index.html'), + transclude: { + prepend: '?prepend', + append: '?append' + }, controller: Chip, - transclude: true, bindings: { disabled: ' div { @@ -75,6 +98,20 @@ vn-chip { opacity: 1; } } + + & > .prepend { + padding: 0 5px; + padding-right: 0; + + &:empty {display:none;} + } + & > .append { + padding: 0 5px; + padding-left: 0; + + &:empty {display:none;} + } + } vn-avatar { diff --git a/modules/worker/back/methods/worker-time-control/addTimeEntry.js b/modules/worker/back/methods/worker-time-control/addTimeEntry.js index 86030d7133..2079a62a39 100644 --- a/modules/worker/back/methods/worker-time-control/addTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/addTimeEntry.js @@ -5,38 +5,54 @@ module.exports = Self => { description: 'Adds a new hour registry', accessType: 'WRITE', accepts: [{ - arg: 'data', - type: 'object', - required: true, - description: 'workerFk, timed', - http: {source: 'body'} + arg: 'id', + type: 'number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'timed', + type: 'date', + required: true + }, + { + arg: 'direction', + type: 'string', + required: true }], returns: [{ type: 'Object', root: true }], http: { - path: `/addTimeEntry`, + path: `/:id/addTimeEntry`, verb: 'POST' } }); - Self.addTimeEntry = async(ctx, data) => { - const Worker = Self.app.models.Worker; - const myUserId = ctx.req.accessToken.userId; - const myWorker = await Worker.findOne({where: {userFk: myUserId}}); - const isSubordinate = await Worker.isSubordinate(ctx, data.workerFk); - const isTeamBoss = await Self.app.models.Account.hasRole(myUserId, 'teamBoss'); + Self.addTimeEntry = async(ctx, workerId, options) => { + const models = Self.app.models; + const args = ctx.args; + const currentUserId = ctx.req.accessToken.userId; - if (isSubordinate === false || (isSubordinate && myWorker.id == data.workerFk && !isTeamBoss)) + let myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const isSubordinate = await models.Worker.isSubordinate(ctx, workerId, myOptions); + const isTeamBoss = await models.Account.hasRole(currentUserId, 'teamBoss', myOptions); + const isHimself = currentUserId == workerId; + + if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); - const subordinate = await Worker.findById(data.workerFk); - const timed = new Date(data.timed); + const timed = new Date(args.timed); - let [result] = await Self.rawSql('SELECT vn.workerTimeControl_add(?, ?, ?, ?) AS id', [ - subordinate.userFk, null, timed, true]); - - return result; + return models.WorkerTimeControl.create({ + userFk: workerId, + direction: args.direction, + timed: timed, + manual: true + }, myOptions); }; }; diff --git a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js index 97637d197b..23e4c5fffd 100644 --- a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js @@ -21,21 +21,24 @@ module.exports = Self => { } }); - Self.deleteTimeEntry = async(ctx, id) => { + Self.deleteTimeEntry = async(ctx, id, options) => { const currentUserId = ctx.req.accessToken.userId; - const workerModel = Self.app.models.Worker; + const models = Self.app.models; - const targetTimeEntry = await Self.findById(id); - const isSubordinate = await workerModel.isSubordinate(ctx, targetTimeEntry.userFk); - const isTeamBoss = await Self.app.models.Account.hasRole(currentUserId, 'teamBoss'); + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const targetTimeEntry = await Self.findById(id, null, myOptions); + const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions); + const isTeamBoss = await models.Account.hasRole(currentUserId, 'teamBoss', myOptions); const isHimself = currentUserId == targetTimeEntry.userFk; - const notAllowed = isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss); - - if (notAllowed) + if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); return Self.rawSql('CALL vn.workerTimeControl_remove(?, ?)', [ - targetTimeEntry.userFk, targetTimeEntry.timed]); + targetTimeEntry.userFk, targetTimeEntry.timed], myOptions); }; }; 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 0f055bdc52..94be12e894 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 @@ -1,5 +1,6 @@ const app = require('vn-loopback/server/server'); const LoopBackContext = require('loopback-context'); +const models = app.models; describe('workerTimeControl add/delete timeEntry()', () => { const HHRRId = 37; @@ -12,19 +13,6 @@ describe('workerTimeControl add/delete timeEntry()', () => { }; let ctx = {req: activeCtx}; - let timeEntry; - let createdTimeEntry; - - afterEach(async() => { - if (createdTimeEntry) { - try { - await app.models.WorkerTimeControl.destroyById(createdTimeEntry.id); - } catch (error) { - console.error(error); - } - } - }); - beforeAll(() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx @@ -33,14 +21,13 @@ describe('workerTimeControl add/delete timeEntry()', () => { it('should fail to add a time entry if the target user is not a subordinate', async() => { activeCtx.accessToken.userId = employeeId; + const workerId = 2; + let error; - let data = { - workerFk: 2, - timed: new Date() - }; try { - await app.models.WorkerTimeControl.addTimeEntry(ctx, data); + ctx.args = {timed: new Date(), direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId); } catch (e) { error = e; } @@ -52,14 +39,12 @@ describe('workerTimeControl add/delete timeEntry()', () => { it('should fail to add if the current and the target user are the same and is not team boss', async() => { activeCtx.accessToken.userId = employeeId; + const workerId = employeeId; let error; - let data = { - workerFk: 1, - timed: new Date() - }; try { - await app.models.WorkerTimeControl.addTimeEntry(ctx, data); + ctx.args = {timed: new Date(), direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId); } catch (e) { error = e; } @@ -71,41 +56,49 @@ describe('workerTimeControl add/delete timeEntry()', () => { it('should add if the current user is team boss and the target user is a himself', async() => { activeCtx.accessToken.userId = teamBossId; - let todayAtSix = new Date(); - todayAtSix.setHours(18, 30, 0, 0); + const workerId = teamBossId; - let data = { - workerFk: teamBossId, - timed: todayAtSix - }; + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; - timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data); + const todayAtSix = new Date(); + todayAtSix.setHours(18, 30, 0, 0); - createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); + ctx.args = {timed: todayAtSix, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - expect(createdTimeEntry).toBeDefined(); + expect(createdTimeEntry.id).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); it('should try but fail to delete his own time entry', async() => { activeCtx.accessToken.userId = salesBossId; + const workerId = salesBossId; + let error; - let todayAtSeven = new Date(); - todayAtSeven.setHours(19, 30, 0, 0); - - let data = { - workerFk: salesPersonId, - timed: todayAtSeven - }; - - timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data); - - createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); - + const tx = await models.WorkerTimeControl.beginTransaction({}); try { + const options = {transaction: tx}; + + const todayAtSeven = new Date(); + todayAtSeven.setHours(19, 30, 0, 0); + + ctx.args = {timed: todayAtSeven, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + activeCtx.accessToken.userId = salesPersonId; - await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id); + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + + await tx.rollback(); } catch (e) { error = e; + await tx.rollback(); } expect(error).toBeDefined(); @@ -115,49 +108,86 @@ describe('workerTimeControl add/delete timeEntry()', () => { it('should delete the created time entry for the team boss as himself', async() => { activeCtx.accessToken.userId = teamBossId; + const workerId = teamBossId; - let todayAtFive = new Date(); - todayAtFive.setHours(17, 30, 0, 0); + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; - let data = { - workerFk: teamBossId, - timed: todayAtFive - }; + const todayAtFive = new Date(); + todayAtFive.setHours(17, 30, 0, 0); - timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data); + ctx.args = {timed: todayAtFive, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); + expect(createdTimeEntry.id).toBeDefined(); - expect(createdTimeEntry).toBeDefined(); + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id); + const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); - createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); - - expect(createdTimeEntry).toBeNull(); + expect(deletedTimeEntry).toBeNull(); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); it('should delete the created time entry for the team boss as HHRR', async() => { activeCtx.accessToken.userId = HHRRId; + const workerId = teamBossId; - let todayAtFive = new Date(); - todayAtFive.setHours(17, 30, 0, 0); + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; - let data = { - workerFk: teamBossId, - timed: todayAtFive - }; + const todayAtFive = new Date(); + todayAtFive.setHours(17, 30, 0, 0); - timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data); + ctx.args = {timed: todayAtFive, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); + expect(createdTimeEntry.id).toBeDefined(); - expect(createdTimeEntry).toBeDefined(); + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id); + const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); - createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); + expect(deletedTimeEntry).toBeNull(); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); - expect(createdTimeEntry).toBeNull(); + 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 todayAtFive = new Date(); + todayAtFive.setHours(17, 30, 0, 0); + + ctx.args = {timed: todayAtFive, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + ctx.args = {direction: 'out'}; + const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options); + + // const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); + + expect(updatedTimeEntry.direction).toEqual('out'); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/worker/back/methods/worker-time-control/updateTimeEntry.js b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js new file mode 100644 index 0000000000..abeda7f8e4 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js @@ -0,0 +1,53 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('updateTimeEntry', { + description: 'Updates a time entry for a worker if the user role is above the worker', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The time entry id', + http: {source: 'path'} + }, + { + arg: 'direction', + type: 'string', + required: true + }], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/:id/updateTimeEntry`, + verb: 'POST' + } + }); + + Self.updateTimeEntry = async(ctx, id, options) => { + const currentUserId = ctx.req.accessToken.userId; + const models = Self.app.models; + const args = ctx.args; + + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const targetTimeEntry = await Self.findById(id, null, myOptions); + const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions); + const isTeamBoss = await models.Account.hasRole(currentUserId, 'teamBoss', myOptions); + const isHimself = currentUserId == targetTimeEntry.userFk; + + const notAllowed = isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss); + + if (notAllowed) + throw new UserError(`You don't have enough privileges`); + + return targetTimeEntry.updateAttributes({ + direction: args.direction + }, myOptions); + }; +}; diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index f0191c36f2..45f4e2194e 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -4,6 +4,7 @@ module.exports = Self => { require('../methods/worker-time-control/filter')(Self); require('../methods/worker-time-control/addTimeEntry')(Self); require('../methods/worker-time-control/deleteTimeEntry')(Self); + require('../methods/worker-time-control/updateTimeEntry')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/worker/back/models/worker-time-control.json b/modules/worker/back/models/worker-time-control.json index 5212222bdd..ab07802ca1 100644 --- a/modules/worker/back/models/worker-time-control.json +++ b/modules/worker/back/models/worker-time-control.json @@ -9,16 +9,16 @@ "properties": { "id": { "id": true, - "type": "Number" + "type": "number" }, "timed": { - "type": "Date" + "type": "date" }, "manual": { - "type": "Boolean" + "type": "boolean" }, "order": { - "type": "Number" + "type": "number" }, "direction": { "type": "string" diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index f012e8e55b..42d3639529 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -23,7 +23,7 @@
-
{{'Contract' | translate}} ID: {{$ctrl.businessId}}
+
{{'Contract' | translate}} #{{$ctrl.businessId}}
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed}} {{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}} @@ -55,7 +55,7 @@ order="businessFk DESC" limit="5"> -
ID: {{businessFk}}
+
#{{businessFk}}
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
diff --git a/modules/worker/front/time-control/index.html b/modules/worker/front/time-control/index.html index a333b85851..a973b510e2 100644 --- a/modules/worker/front/time-control/index.html +++ b/modules/worker/front/time-control/index.html @@ -43,9 +43,16 @@ ng-class="::{'invisible': hour.direction == 'middle'}"> + on-remove="$ctrl.showDeleteDialog($event, hour)" + ng-click="$ctrl.edit($event, hour)" + > + + + + {{::hour.timed | date: 'HH:mm'}} @@ -97,10 +104,20 @@ + required="true"> + + @@ -112,4 +129,22 @@ on-accept="$ctrl.deleteTimeEntry()" message="This time entry will be deleted" question="Are you sure you want to delete this entry?"> - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index bed9e7af3a..6069dff33c 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -7,6 +7,11 @@ class Controller extends Section { super($element, $); this.weekDays = []; this.weekdayNames = vnWeekDays.locales; + this.entryDirections = [ + {code: 'in', description: this.$t('In')}, + {code: 'middle', description: this.$t('Intermediate')}, + {code: 'out', description: this.$t('Out')} + ]; } $postLink() { @@ -241,21 +246,23 @@ class Controller extends Section { const timed = new Date(weekday.dated.getTime()); timed.setHours(0, 0, 0, 0); - this.newTime = timed; + this.newTimeEntry = { + workerFk: this.$params.id, + timed: timed + }; this.selectedWeekday = weekday; this.$.addTimeDialog.show(); } addTime() { - let data = { - workerFk: this.$params.id, - timed: this.newTime - }; - this.$http.post(`WorkerTimeControls/addTimeEntry`, data) + const query = `WorkerTimeControls/${this.worker.id}/addTimeEntry`; + this.$http.post(query, this.newTimeEntry) .then(() => this.fetchHours()); } - showDeleteDialog(hour) { + showDeleteDialog($event, hour) { + $event.preventDefault(); + this.timeEntryToDelete = hour; this.$.deleteEntryDialog.show(); } @@ -268,6 +275,22 @@ class Controller extends Section { this.vnApp.showSuccess(this.$t('Entry removed')); }); } + + edit($event, hour) { + if ($event.defaultPrevented) return; + + this.selectedRow = hour; + this.$.editEntry.show($event); + } + + save() { + const entry = this.selectedRow; + const query = `WorkerTimeControls/${entry.id}/updateTimeEntry`; + this.$http.post(query, {direction: entry.direction}) + .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))) + .then(() => this.$.editEntry.hide()) + .then(() => this.fetchHours()); + } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; diff --git a/modules/worker/front/time-control/locale/es.yml b/modules/worker/front/time-control/locale/es.yml index 1d28b0e2b3..98c317793e 100644 --- a/modules/worker/front/time-control/locale/es.yml +++ b/modules/worker/front/time-control/locale/es.yml @@ -1,5 +1,6 @@ In: Entrada Out: Salida +Intermediate: Intermedio Hour: Hora Hours: Horas Add time: AƱadir hora diff --git a/modules/worker/front/time-control/style.scss b/modules/worker/front/time-control/style.scss index 99a21883f2..2a91a865e8 100644 --- a/modules/worker/front/time-control/style.scss +++ b/modules/worker/front/time-control/style.scss @@ -24,4 +24,7 @@ vn-worker-time-control { .totalBox { max-width: none } + .edit-entry { + width: 150px + } } \ No newline at end of file