Skip to content

Commit

Permalink
Merge pull request #464 from timeoff-management/leaves-report
Browse files Browse the repository at this point in the history
Leaves report & expose leave details as popup
  • Loading branch information
vpp authored Feb 10, 2021
2 parents 26b2f90 + 51f8b5e commit 54e123a
Show file tree
Hide file tree
Showing 18 changed files with 7,351 additions and 16 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
75 changes: 74 additions & 1 deletion lib/model/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,84 @@ const leaveIntoObject = (leave) => {
type: leave.leave_type.name,
deductedDays: leave.get_deducted_days_number(),
approver: (leave.approver ? leave.approver.full_name() : 'N/A'),
approverId: leave.approverId,
status: statusMap[leave.status]||'Unknown',

id: leave.id,
employeeId: leave.userId,
employeeFullName: (leave.user ? leave.user.full_name() : 'N/A'),
departmentId: (leave.user ? leave.user.departmentId : null),
departmentName: (leave.user && leave.user.department ? leave.user.department.name : 'N/A'),
typeId: leave.leaveTypeId,
createdAt: moment.utc(leave.createdAt).format(dateFormat),
};
};

const fetchLeavesForLeavesReport = async ({startDate, endDate, departmentId, leaveTypeId, actingUser, dbModel}) => {
let users = await dbModel.User.findAll({where: {companyId: actingUser.companyId}});

// If department was provided in filter out everyone who is not part of it
if (departmentId) {
users = users.filter(u => `${u.DepartmentId}` === `${departmentId}`);
}

// The way how we fetch the leaves is not most efficient way (mildly speaking)
// but we go in its favour because we reuse the existing code that does similar
// logic. Hopefully that would make the app more stable when we come to refactor
// underlying code.

let leavesObjects = [];

for (let user of users) {
await user.promise_schedule_I_obey();
let usersLeaves = await user.getMyActiveLeavesForDateRange({dateStart: startDate, dateEnd: endDate});

// Filter out redundant leave types if we are interesting in only particular one
if (leaveTypeId) {
usersLeaves = usersLeaves.filter(l => `${l.leaveTypeId}` === `${leaveTypeId}`);
}

if (startDate) {
const epoch = startDate.valueOf();
usersLeaves = usersLeaves.filter(l => moment.utc(l.date_start).toDate().valueOf() >= epoch);
}

if (endDate) {
const epoch = endDate.valueOf();
usersLeaves = usersLeaves.filter(l => moment.utc(l.date_end).toDate().valueOf() <= epoch);
}

leavesObjects.push(...usersLeaves);
}

// Get comments that were added to leaves, so we can enrich leave data later.
// The idea is to get all LEAVE comments into memory as a map and then use it
// to inject comment into each leave while cycling through them
const allComments = await dbModel.Comment.findAll({where: {
companyId: actingUser.companyId,
entityType: dbModel.Comment.getEntityTypeLeave(),
}});

const commentsMap = allComments.reduce((m, c) => {
if (m[c.entityId] === undefined) {
m[c.entityId] = [c]
} else {
m[c.entityId].push(c);
}

return m;
}, {});

let leaves = leavesObjects.map(l => leaveIntoObject(l));

leaves = leaves.map(l => ({...l, comment: (commentsMap[l.id] ? commentsMap[l.id].map(({comment}) => comment).join('. ') : '')}));

return {leaves};
};


module.exports = {
getUsersWithLeaves
getUsersWithLeaves,
fetchLeavesForLeavesReport,
leaveIntoObject,
};
48 changes: 47 additions & 1 deletion lib/model/leave/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,52 @@ const commentLeaveIfNeeded = ({leave,comment, companyId}) => {
return comment ? commentLeave({leave,comment,companyId}) : Promise.resolve();
};

const getLeaveForUserView = async ({actingUser, leaveId, dbModel}) => {

const [leave] = await dbModel.Leave.findAll({
where: {
id: leaveId,
},
include: [{
model: dbModel.User,
as: 'user',
where: {
companyId: actingUser.companyId,
}
}],
});

if (!leave) {
throw new Error(`User [${actingUser.id}] tried to access leave [${leaveId}] which does not belong to the same company.`);
}

return leave;
};

const doesUserHasExtendedViewOfLeave = async ({user, leave}) => {
if (user.companyId !== (await leave.getUser()).companyId) {
throw new Error(`User [${user.id}] and leave [${leave.id}] do not share company.`);
}

let extendedView = false;

if (user.is_admin()) {
extendedView = true;
}

if (! extendedView) {
const reports = await user.promise_supervised_users();

if (reports.filter(u => `${u.id}` === `${leave.userId}`).length > 0) {
extendedView = true;
}
}

return extendedView;
};

module.exports = {
createNewLeave : createNewLeave,
createNewLeave,
doesUserHasExtendedViewOfLeave,
getLeaveForUserView,
}
46 changes: 45 additions & 1 deletion lib/model/mixin/user/absence_aware.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ module.exports = function(sequelize){
this.promise_my_leaves = function(args){

var self = this,
where_clause = {},
where_clause = args.filter || {},
// Time zone does not really matter here, although there could be issues
// around New Year (but we can tolerate that)
year = args.year || moment.utc();
Expand All @@ -197,6 +197,25 @@ module.exports = function(sequelize){
]
}
};

// Case when there are start and end date defined
} else if (args && args.dateStart && args.dateEnd) {
const {dateStart, dateEnd} = args;

where_clause['$or'] = {
date_start : {
$between : [
moment.utc(dateStart).startOf('day').format('YYYY-MM-DD'),
moment.utc(dateEnd).endOf('day').format('YYYY-MM-DD HH:mm'),
]
},
date_end : {
$between : [
moment.utc(dateStart).startOf('day').format('YYYY-MM-DD'),
moment.utc(dateEnd).endOf('day').format('YYYY-MM-DD HH:mm'),
]
}
};
}

var promise_my_leaves = this.getMy_leaves({
Expand Down Expand Up @@ -240,6 +259,12 @@ module.exports = function(sequelize){
);
})

.then(async (leaves) => {
const department = await self.getDepartment();
leaves.forEach(l => l.user.department = department);
return leaves;
})

.then(leaves => LeaveCollectionUtil.promise_to_sort_leaves(leaves));

return promise_my_leaves;
Expand All @@ -260,6 +285,25 @@ module.exports = function(sequelize){
.then(leaves => LeaveCollectionUtil.promise_to_sort_leaves(leaves));
};

this.getMyActiveLeavesForDateRange = async function({dateStart, dateEnd}) {
const self = this;

const rawLeaves = await self.promise_my_leaves({
ignore_year: true,
dateStart,
dateEnd,
filter_status : [
sequelize.models.Leave.status_approved(),
sequelize.models.Leave.status_new(),
sequelize.models.Leave.status_pended_revoke(),
],
});

const leaves = await LeaveCollectionUtil.promise_to_sort_leaves(rawLeaves);

return leaves;
};

// Promises leaves ever booked for current user
this.promise_my_active_leaves_ever = function() {

Expand Down
39 changes: 37 additions & 2 deletions lib/route/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ var express = require('express'),
validator = require('validator'),
get_and_validate_leave_params = require('./validator/leave_request'),
TeamView = require('../model/team_view'),
EmailTransport = require('../email'),
createNewLeave = require('../model/leave').createNewLeave;
EmailTransport = require('../email');

const {createNewLeave, getLeaveForUserView, doesUserHasExtendedViewOfLeave} = require('../model/leave');
const { leaveIntoObject } = require('../model/Report');
const { getCommentsForLeave } = require('../model/comment');

router.post('/bookleave/', function(req, res){

Expand Down Expand Up @@ -245,4 +248,36 @@ function promise_feed_of_type(args) {
return feed_promise;
}

router.get('/leave-summary/:leaveId/', async (req, res) => {
const actingUser = req.user;
const leaveId = validator.trim(req.params['leaveId']);
const dbModel = req.app.get('db_model');

try {
const leave = await getLeaveForUserView({actingUser, leaveId, dbModel});
const extendedView = await doesUserHasExtendedViewOfLeave({user: actingUser, leave});
if (extendedView) {
const user = await leave.getUser();
await user.promise_schedule_I_obey();
const [extendedLeave] = await user.promise_my_leaves({ignore_year: true, filter: {id: leave.id}});
const leaveDetails = leaveIntoObject(extendedLeave);
const comments = await getCommentsForLeave({leave});

leaveDetails.commentsString = comments.map(({comment}) => comment).join('<br>');

return res.render('leave/popup_leave_details', {
leave: leaveDetails,
layout: false,
});
} else {
return res.send('Short');
}
} catch (error) {
console.log(`Failed to obtain Leave [${leaveId}] summary: ${error} at ${error.stack}`);
return res.send('Failed to get leave details...');
}

return res.send('Failed to get leave details (should never happen)...');
});

module.exports = router;
19 changes: 12 additions & 7 deletions lib/route/feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const
numberOfFutureMonthsInTeamViewFeed = config.get('number_of_future_months_in_team_view_feed') || 6,
numberOfPastMonthsInTeamViewFeed = config.get('number_of_past_months_in_team_view_feed') || 2;

const { getCommentsForLeave } = require('../model/comment');

router.get('/:token/ical.ics', function(req, res){

var cal = ical({
Expand Down Expand Up @@ -102,13 +104,11 @@ router.get('/:token/ical.ics', function(req, res){
}
})

.then(function(days){

days.forEach(function(day){

.then(async (days) => {
for (const day of days) {
// We care only about days when employee is on leave
if (!(day.is_leave_morning || day.is_leave_afternoon)) {
return;
continue;
}

let start = moment.utc(day.moment),
Expand All @@ -127,14 +127,19 @@ router.get('/:token/ical.ics', function(req, res){
end.hour(13).minute(0);
}

const comments = await getCommentsForLeave({leave: day.leave_obj});

cal.createEvent({
start : start.toDate(),
end : end.toDate(),
allDay : allDay,
summary : day.user.full_name() + ' is OOO (out of office)',
// description : 'It works ;)',
description: (comments.length > 0
? `With comments: ${comments.map(({comment}) => comment).join('. ')}`
: ''
),
});
});
}

res.send( cal.toString() );
})
Expand Down
Loading

0 comments on commit 54e123a

Please sign in to comment.