Skip to content

Commit

Permalink
Auto appoval flag feature.
Browse files Browse the repository at this point in the history
* Add auto approve logic into leave workflow.

* Make migration prone to re-executions.

* Add migration to add new auto_approve flag to users table

* Add test for auto approvals.
  • Loading branch information
vpp committed Jun 6, 2017
1 parent 8b46f06 commit a784666
Show file tree
Hide file tree
Showing 21 changed files with 661 additions and 150 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

*.swp
*.swo
*.swn
*.swm
node_modules/
*.sqlite
23 changes: 19 additions & 4 deletions lib/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,16 @@ Email.prototype.promise_leave_request_revoke_emails = function(args){
leave = args.leave,
send_mail = self.get_send_email();

var template_name_to_supervisor = 'leave_request_revoke_to_supervisor';
var template_name_to_requestor = 'leave_request_revoke_to_requestor';

if ( leave.get('user').is_auto_approve() ) {
template_name_to_supervisor = 'leave_request_revoke_to_supervisor_autoapprove';
template_name_to_requestor = 'leave_request_revoke_to_requestor_autoapprove';
}

var promise_email_to_supervisor = self.promise_rendered_email_template({
template_name : 'leave_request_revoke_to_supervisor',
template_name : template_name_to_supervisor,
context : {
leave : leave,
approver : leave.get('approver'),
Expand All @@ -185,7 +193,7 @@ Email.prototype.promise_leave_request_revoke_emails = function(args){
});

var promise_email_to_requestor = self.promise_rendered_email_template({
template_name : 'leave_request_revoke_to_requestor',
template_name : template_name_to_requestor,
context : {
leave : leave,
approver : leave.get('approver'),
Expand Down Expand Up @@ -220,9 +228,16 @@ Email.prototype.promise_leave_request_emails = function(args){
leave = args.leave,
send_mail = self.get_send_email();

var template_name_to_supervisor = 'leave_request_to_supervisor';
var template_name_to_requestor = 'leave_request_to_requestor';

if ( leave.get('user').is_auto_approve() ) {
template_name_to_supervisor = 'leave_request_to_supervisor_autoapprove';
template_name_to_requestor = 'leave_request_to_requestor_autoapprove';
}

var promise_email_to_supervisor = self.promise_rendered_email_template({
template_name : 'leave_request_to_supervisor',
template_name : template_name_to_supervisor,
context : {
leave : leave,
approver : leave.get('approver'),
Expand All @@ -245,7 +260,7 @@ Email.prototype.promise_leave_request_emails = function(args){
});

var promise_email_to_requestor = self.promise_rendered_email_template({
template_name : 'leave_request_to_requestor',
template_name : template_name_to_requestor,
context : {
leave : leave,
approver : leave.get('approver'),
Expand Down
87 changes: 48 additions & 39 deletions lib/model/db/leave.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ module.exports = function(sequelize, DataTypes) {

// Make sure all required data is provided
_.each(
['for_employee','of_type','with_parameters'],
function(property){
if (! _.has(args, property)) {
throw new Error('No mandatory '+property+' was provided');
}
['for_employee','of_type','with_parameters'],
function(property){
if (! _.has(args, property)) {
throw new Error('No mandatory '+property+' was provided');
}
}
);

var employee = args.for_employee,
Expand All @@ -124,45 +124,49 @@ module.exports = function(sequelize, DataTypes) {
// Make sure that booking to be created is not going to ovelap with
// any existing bookings
return Promise.try(function(){
return employee.validate_overlapping(valide_attributes);
return employee.validate_overlapping(valide_attributes);
})
.then(function(){
return employee.promise_superviser();
return employee.promise_superviser();
})

.then(function(superviser){

var start_date = moment(valide_attributes.from_date),
end_date = moment(valide_attributes.to_date);

// Check that start date is not bigger then end one
if ( start_date.toDate() > end_date.toDate() ) {
throw new Error('Start date is later than end date');
}

// Following statement creates in memory only leave object
// it is not in database until .save() method is called
var leave_to_create = sequelize.models.Leave.build({
userId : employee.id,
leaveTypeId : leave_type.id,
status : Leave.status_new(),
approverId : superviser.id,
employee_comment : valide_attributes.reason,

date_start : start_date.format('YYYY-MM-DD'),
date_end : end_date.format('YYYY-MM-DD'),
day_part_start : valide_attributes.from_date_part,
day_part_end : valide_attributes.to_date_part,
});

return employee.validate_leave_fits_into_remaining_allowance({
year : start_date,
leave_type : leave_type,
leave : leave_to_create,
})
.then(function(){
return leave_to_create.save();
});
var start_date = moment(valide_attributes.from_date),
end_date = moment(valide_attributes.to_date);

// Check that start date is not bigger then end one
if ( start_date.toDate() > end_date.toDate() ) {
throw new Error('Start date is later than end date');
}

var new_leave_status = employee.is_auto_approve()
? Leave.status_approved()
: Leave.status_new();

// Following statement creates in memory only leave object
// it is not in database until .save() method is called
var leave_to_create = sequelize.models.Leave.build({
userId : employee.id,
leaveTypeId : leave_type.id,
status : new_leave_status,
approverId : superviser.id,
employee_comment : valide_attributes.reason,

date_start : start_date.format('YYYY-MM-DD'),
date_end : end_date.format('YYYY-MM-DD'),
day_part_start : valide_attributes.from_date_part,
day_part_end : valide_attributes.to_date_part,
});

return employee.validate_leave_fits_into_remaining_allowance({
year : start_date,
leave_type : leave_type,
leave : leave_to_create,
})
.then(function(){
return leave_to_create.save();
});


});
Expand Down Expand Up @@ -378,8 +382,13 @@ promise_to_revoke : function(){
],
})
.then(function(user){

var new_leave_status = user.is_auto_approve()
? Leave.status_rejected()
: Leave.status_pended_revoke();

self.approverId = user.department.bossId;
self.status = Leave.status_pended_revoke();
self.status = new_leave_status;

return self.save();
})
Expand Down
101 changes: 57 additions & 44 deletions lib/model/db/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ module.exports = function(sequelize, DataTypes) {
defaultValue : false,
comment : 'Indicate if account can edit company wide settings',
},
auto_approve : {
type : DataTypes.BOOLEAN,
allowNull : false,
defaultValue : false,
comment : 'Indicate if leave request from current employee are auto approveed',
},
start_date : {
type : DataTypes.DATE,
allowNull : false,
Expand Down Expand Up @@ -114,59 +120,66 @@ function get_instance_methods(sequelize) {
},

is_admin : function() {
return this.admin === true;
return this.admin === true;
},

/*
* Indicates is leave requests from current user are automatically approved
* */
is_auto_approve : function(){
return this.auto_approve === true;
},

full_name : function() {
return this.name + ' ' + this.lastname;
return this.name + ' ' + this.lastname;
},

promise_users_I_can_manage : function(){
var this_user = this;
var this_user = this;

// Check if current user is admin, then fetch all users form company
if ( this_user.is_admin() ) {
// Check if current user is admin, then fetch all users form company
if ( this_user.is_admin() ) {

return this_user.get_company_with_all_users()
.then(function(company){
return Promise.resolve( company.users );
});
}
return this_user.get_company_with_all_users()
.then(function(company){
return Promise.resolve( company.users );
});
}

// If current user has any departments under supervision then get
// all users from those departments plus user himself,
// if no supervised users an array with only current user is returned
return this_user.getSupervised_departments({
include : [{
model : sequelize.models.User,
as : 'users',
}],
})
.then(function(departments){
var users = _.flatten(
_.map(
departments,
function(department){ return department.users; }
)
);

// Make sure current user is considered as well
users.push(this_user);

// Remove duplicates
users = _.uniq(
users,
function(user){ return user.id; }
);

// Order by last name
users = _.sortBy(
users,
function(user){ return user.lastname; }
);

return users;
});
// If current user has any departments under supervision then get
// all users from those departments plus user himself,
// if no supervised users an array with only current user is returned
return this_user.getSupervised_departments({
include : [{
model : sequelize.models.User,
as : 'users',
}],
})
.then(function(departments){
var users = _.flatten(
_.map(
departments,
function(department){ return department.users; }
)
);

// Make sure current user is considered as well
users.push(this_user);

// Remove duplicates
users = _.uniq(
users,
function(user){ return user.id; }
);

// Order by last name
users = _.sortBy(
users,
function(user){ return user.lastname; }
);

return users;
});

}, // promise_users_I_can_manage

Expand Down
2 changes: 1 addition & 1 deletion lib/model/mixin/user/absence_aware.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ module.exports = function(sequelize){
};

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

return this.promise_my_leaves({
ignore_year : true,
Expand Down
22 changes: 11 additions & 11 deletions lib/model/mixin/user/company_aware.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,18 @@ module.exports = function(sequelize){
as : 'boss',
}
}
],
order : [
[
{model : sequelize.models.Department, as : 'departments'},
sequelize.models.Department.default_order_field(),
]
],
})
],
order : [
[
{model : sequelize.models.Department, as : 'departments'},
sequelize.models.Department.default_order_field(),
]
],
})

// Make sure that company got only one user associated with for
// provided user_id
.then(function(company){
// Make sure that company got only one user associated with for
// provided user_id
.then(function(company){

if (!company || company.users.length !== 1) {
throw new Error(
Expand Down
15 changes: 11 additions & 4 deletions lib/route/requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ router.post(
Promise.try(function(){
return Promise.join(
// Get user's leaves
req.user.promise_my_active_leaves({}),
req.user.promise_my_active_leaves_ever(),
// Get all leaves from users supervised by current user
req.user
.promise_users_I_can_manage()
Expand All @@ -205,7 +205,7 @@ router.post(
_.map(
users,
function(user){
return user.promise_my_active_leaves({});
return user.promise_my_active_leaves_ever();
}
))
.then(function(array_of_leave_arrays){
Expand All @@ -225,7 +225,7 @@ router.post(

if (! leave_to_process) {
throw new Error('Provided ID '+request_id
+'does not correspond to any leave requests to be revoked by user '
+' does not correspond to any leave requests to be revoked by user '
+ req.user.id
);
}
Expand Down Expand Up @@ -253,7 +253,14 @@ router.post(
});
})
.then(function(processed_leave){
req.session.flash_message('You have requested leave to be revoked, your supervisor needs to approve it');
req.session.flash_message(
'You have requested leave to be revoked. '
+ (
processed_leave.user.is_auto_approve()
? ''
: 'Your supervisor needs to approve it'
)
);

return res.redirect_with_session('../');
})
Expand Down
Loading

0 comments on commit a784666

Please sign in to comment.