Skip to content

Commit

Permalink
Merge pull request #38 from dhanasingh/dev
Browse files Browse the repository at this point in the history
v2.1
  • Loading branch information
suganya-thulasiraman committed Feb 2, 2016
2 parents de59948 + 04aaa78 commit 2b71327
Show file tree
Hide file tree
Showing 54 changed files with 2,472 additions and 152 deletions.
34 changes: 19 additions & 15 deletions README.rdoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
= wktime
This Plugin provides the capability to submit Time & Expense within redmine. The user must be a
This Plugin provides the capability to submit Time, Expense and Attendance within redmine. The user must be a
member of a project with permission to log time. If the user has 'Edit time logs' permission,
then he can manage other team member's Time & Expense as well.

Expand Down Expand Up @@ -29,9 +29,11 @@ The following configurations can be made on the plugin
- Set submission deadline
- Set projects for expense
- Allow User filtering to My Issues
- Public holidays can be configured
- 'Approve time log' permission
- T&E Admin groups can be configured
- Enable Clock In / Clock Out
- Break time and Leaves can be configured
- Join date, Date of birth, Designation, Employee Id can be configured

The Time & Expense sheets can be approved/rejected by supervisors after it is submitted by Project Members.
Here is the workflow of Approval system..
Expand All @@ -43,12 +45,15 @@ iv) Once approved, the time & expense sheet can never be edited.
v) If the time & expense sheet is rejected, then it goes back to the project member for re-submission
An email about the rejection is sent to the Project Member.
vi) Once approved, a time & expense sheet can be unapproved, it sends the time & expense sheet back to submitted state.
vii) If the timesheet is not submitted before deadline, then a email notification will be sent to the project memeber.
vii) If the timesheet is not submitted before deadline, then a email notification will be sent to the project member.

Time & Expense plugin supports REST API. It supports both xml and json. Using the api we can list
Time & Attendance plugin supports REST API. It supports both xml and json. Using the api we can list
time/expense sheets for a user, create, update and delete time/expense sheets. User with approve time log
permission can approve, unapprove, reject time/expense sheets. This plugin also supports code hooks.

Attendance module allow the user to clock in, clock out and view their leave status.
T&E admin can manage other users attendance. A scheduled job will be run at start of each month to calculate the leave accruals.

Unpack the zip file to the plugins folder of Redmine.
Starting from version 1.2, it requires db migration.
So run the following command for db migration
Expand All @@ -73,20 +78,19 @@ If an apache passenger module is used then make sure the following settings are
b) RailsAppSpawnerIdleTime 0
c) PassengerPreStart http://rails-app-url/

Release Notes for 2.0
Release Notes for 2.1
The plugin name is renamed to Time & Attendance.

Features:
- Made compatible with Redmine 3.1.1.
- Reminder email for submission and approval can be send by manager and TE admins.
- TE admins are allowed to modify and approve time/expense.
- Added User custom field filters in list page.
- "Please select" option is added in Issue and Activity dropdown.
- In log time page, error message is shown for restricted tracker type and on TE related error, the Create/Save button will be disabled.
- A warning message is shown when the user leaves an unsaved time/expense sheet page.

- Made compatible with Redmine 3.2.0.
- Attendance module is introduced.
- German translation is included.
Bugs:
- Permission changes - When a Manager edits a Member T&E, his permission will be checked rather than the member's permission.
- Fixed SQL Server issue.
- Fixed time entering format issue.
- Fixed issue forbidden error on "Enter issue as ID/Subject".

We would like to thank Fotonation(http://www.fotonation.com) for sponsoring this release.
We would like to thank Luna Lenardi for contributing towards German Translation and for fixing a currency issue.

Customization:
For any Customization/Support, please contact us, our consulting team will be happy to help you
Expand Down
281 changes: 281 additions & 0 deletions app/controllers/wkattendance_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
class WkattendanceController < ApplicationController
unloadable

include WktimeHelper
include WkattendanceHelper

before_filter :require_login
before_filter :check_perm_and_redirect, :only => [:edit, :update]

def index
sqlStr = ""
if(Setting.plugin_redmine_wktime['wktime_leave'].blank?)
sqlStr = " select u.id as user_id, -1 as issue_id from users u where u.type = 'User' "
else
listboxArr = Setting.plugin_redmine_wktime['wktime_leave'][0].split('|')
issueId = listboxArr[0]
sqlStr = getQueryStr + " where i.id in (#{issueId}) and u.type = 'User'"
end
if !isAccountUser
sqlStr = sqlStr + " and u.id = #{User.current.id} "
end
findBySql(sqlStr)
end

def edit
sqlStr = getQueryStr + " where i.id in (#{getLeaveIssueIds}) and u.type = 'User' and u.id = #{params[:user_id]}"
@leave_details = WkUserLeave.find_by_sql(sqlStr)
render :action => 'edit'
end

def update
errorMsg =nil
wkuserleave = nil
ids = params[:ids]
newIssueIds = params[:new_issue_ids]
newIssueArr = newIssueIds.split(',')
userId = params[:user_id]
idArr = ids.split(',')
idArr.each do |id|
errorMsg =nil
wkuserleave = nil
wkuserleave = WkUserLeave.find(id)
wkuserleave.balance = params["balance_"+wkuserleave.issue_id.to_s]
wkuserleave.accrual = params["accrual_"+wkuserleave.issue_id.to_s]
wkuserleave.used = params["used_"+wkuserleave.issue_id.to_s]
if !wkuserleave.save()
errorMsg = wkuserleave.errors.full_messages.join('\n')
end
end

newIssueArr.each do |issueId|
errorMsg =nil
wkuserleave = nil
wkuserleave = WkUserLeave.new
wkuserleave.user_id = userId
wkuserleave.issue_id = issueId
wkuserleave.balance = params["balance_"+issueId]
wkuserleave.accrual = params["accrual_"+issueId]
wkuserleave.used = params["used_"+issueId]
wkuserleave.accrual_on = Date.civil(Date.today.year, Date.today.month, 1) -1
if !wkuserleave.save()
errorMsg = wkuserleave.errors.full_messages.join('\n')
end
end

if errorMsg.nil?
redirect_to :controller => 'wkattendance',:action => 'index' , :tab => 'wkattendance'
flash[:notice] = l(:notice_successful_update)
else
flash[:error] = errorMsg
redirect_to :action => 'edit'
end
end

def getLeaveIssueIds
issueIds = ''
if(Setting.plugin_redmine_wktime['wktime_leave'].blank?)
issueIds = '-1'
else
Setting.plugin_redmine_wktime['wktime_leave'].each do |element|
if issueIds!=''
issueIds = issueIds +','
end
listboxArr = element.split('|')
issueIds = issueIds + listboxArr[0]
end
end
issueIds
end

def getReportLeaveIssueIds
issueIds = ''
if(Setting.plugin_redmine_wktime['wktime_leave'].blank?)
issueIds = '-1'
else
Setting.plugin_redmine_wktime['wktime_leave'].each_with_index do |element,index|
if index < 3
if issueIds!=''
issueIds = issueIds +','
end
listboxArr = element.split('|')
issueIds = issueIds + listboxArr[0]
end
end
end
issueIds
end

def getQueryStr
queryStr = ''
queryStr = "select u.id as user_id, i.id as issue_id,w.balance, w.accrual, w.used, w.accrual_on, w.id from users u
cross join issues i left join (SELECT wl.* FROM wk_user_leaves wl inner join"
queryStr = queryStr + " ( select max(accrual_on) as accrual_on, user_id, issue_id from wk_user_leaves
group by user_id, issue_id) t"
queryStr = queryStr + " on wl.user_id = t.user_id and wl.issue_id = t.issue_id
and wl.accrual_on = t.accrual_on) w on w.user_id = u.id and w.issue_id = i.id"
queryStr
end

def report
retrieve_date_range
if params[:report_type] == 'attendance_report'
reportattn
end
end

def reportattn
dateStr = getConvertDateStr('start_time')
sqlStr = ""
if isAccountUser
@userlist = User.where("type = ?", 'User').order('id')
leave_data = WkUserLeave.where("issue_id in (#{getReportLeaveIssueIds}) and accrual_on between '#{@from}' and '#{@to}'")
leave_entry = TimeEntry.where("issue_id in (#{getLeaveIssueIds}) and spent_on between '#{@from}' and '#{@to}'")
sqlStr = "select user_id,#{dateStr} as spent_on,sum(hours) as hours from wk_attendances where start_time between '#{@from}' and '#{@to}' group by user_id,#{dateStr}"
else
@userlist = User.where("type = ? AND id = ?", 'User', User.current.id)
leave_data = WkUserLeave.where("issue_id in (#{getReportLeaveIssueIds}) and accrual_on between '#{@from}' and '#{@to}' and user_id = #{User.current.id} " )
leave_entry = TimeEntry.where("issue_id in (#{getLeaveIssueIds}) and spent_on between '#{@from}' and '#{@to}' and user_id = #{User.current.id} " )
sqlStr = "select user_id,#{dateStr} as spent_on,sum(hours) as hours from wk_attendances where start_time between '#{@from}' and '#{@to}' and user_id = #{User.current.id} group by user_id,#{dateStr}"
end
daily_entries = WkAttendance.find_by_sql(sqlStr)
@attendance_entries = Hash.new
if !leave_data.blank?
leave_data.each_with_index do |entry,index|
@attendance_entries[entry.user_id.to_s + '_' + entry.issue_id.to_s + '_balance'] = entry.balance
@attendance_entries[entry.user_id.to_s + '_' + entry.issue_id.to_s + '_used'] = entry.used
@attendance_entries[entry.user_id.to_s + '_' + entry.issue_id.to_s + '_accrual'] = entry.accrual
end
end
if !leave_entry.blank?
leave_entry.each_with_index do |entry,index|
@attendance_entries[entry.user_id.to_s + '_' + entry.spent_on.to_date.strftime("%d").to_i.to_s + '_leave'] = entry.issue_id
end
end
if !daily_entries.blank?
daily_entries.each_with_index do |entry,index|
@attendance_entries[entry.user_id.to_s + '_' + entry.spent_on.to_date.strftime("%d").to_i.to_s + '_hours'] = entry.hours
end
end
render :action => 'reportattn'
end

# Retrieves the date range based on predefined ranges or specific from/to param dates
def retrieve_date_range
@free_period = false
@from, @to = nil, nil
period_type = params[:period_type]
period = params[:period]
fromdate = params[:from]
todate = params[:to]

if (period_type == '1' || (period_type.nil? && !period.nil?))
case period.to_s
when 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
when 'last_month'
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
@to = (@from >> 1) - 1
end
elsif period_type == '2' || (period_type.nil? && (!fromdate.nil? || !todate.nil?))
begin; @from = Date.civil((fromdate.to_s.to_date).year,(fromdate.to_s.to_date).month, 1) unless fromdate.blank?; rescue; end
begin; @to = (@from >> 1) - 1 unless @from.blank?; rescue; end
if @from.blank?
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
end
@free_period = true
else
# default
# 'current_month'
@from = Date.civil(Date.today.year, Date.today.month, 1)
@to = (@from >> 1) - 1
end

@from, @to = @to, @from if @from && @to && @from > @to

end

def getIssuesByProject
issue_by_project=""
issueList=[]
issueList = getPrjIssues
issueList.each do |issue|
issue_by_project << issue.id.to_s() + ',' + issue.subject + "\n"
end
respond_to do |format|
format.text { render :text => issue_by_project }
end
end

def getPrjIssues
issueList = []
project_id = 0
if !params[:project_id].blank?
project_id = params[:project_id]
end
issueList = Issue.where(:project_id => project_id)
issueList
end

def check_perm_and_redirect
unless check_permission
render_403
return false
end
end

def check_permission
ret = false
ret = params[:user_id].to_i == User.current.id
return (ret || isAccountUser)
end

def getProjectByIssue
project_id=""
if !params[:issue_id].blank?
issue_id = params[:issue_id]
issues = Issue.where(:id => issue_id.to_i)
project_id = issues[0].project_id
end
respond_to do |format|
format.text { render :text => project_id }
end
end

def setLimitAndOffset
if api_request?
@offset, @limit = api_offset_and_limit
if !params[:limit].blank?
@limit = params[:limit]
end
if !params[:offset].blank?
@offset = params[:offset]
end
else
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
@limit = @entry_pages.per_page
@offset = @entry_pages.offset
end
end

def findBySql(query)
result = WkUserLeave.find_by_sql("select count(*) as id from (" + query + ") as v2")
@entry_count = result.blank? ? 0 : result[0].id
setLimitAndOffset()
rangeStr = formPaginationCondition()
@leave_entries = WkUserLeave.find_by_sql(query + " order by u.firstname " + rangeStr )
end

def formPaginationCondition
rangeStr = ""
if ActiveRecord::Base.connection.adapter_name == 'SQLServer'
rangeStr = " OFFSET " + @offset.to_s + " ROWS FETCH NEXT " + @limit.to_s + " ROWS ONLY "
else
rangeStr = " LIMIT " + @limit.to_s + " OFFSET " + @offset.to_s
end
rangeStr
end

end
17 changes: 10 additions & 7 deletions app/controllers/wkexpense_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def filterTrackerVisible
end

def getUnit(entry)
entry.nil? ? l('number.currency.format.unit') : entry[:currency]
entry.nil? ? number_currency_format_unit : entry[:currency]
end

def getUnitDDHTML
Expand Down Expand Up @@ -118,6 +118,10 @@ def deleteEntry
def textfield_size
6
end

def showClockInOut
false
end
private
def getSpecificField
"amount"
Expand All @@ -139,7 +143,6 @@ def getQuery(teQuery, ids, from, to, status)
query = query + " left outer join (select min( #{getDateSqlString('t.spent_on')} ) as min_spent_on, t.user_id as usrid from wk_expense_entries t, users u "
query = query + " where u.id = t.user_id and u.id in (#{ids}) group by t.user_id ) vw on vw.usrid = tmp3.user_id "
query = query + getWhereCond(status)
query = query + " order by tmp3.spent_on desc, tmp3.user_id "
end

def findBySql(query)
Expand All @@ -148,15 +151,15 @@ def findBySql(query)
@entry_count = result[0].id
setLimitAndOffset()
rangeStr = formPaginationCondition()
@entries = WkExpenseEntry.find_by_sql(query + rangeStr)
@unit = @entries.blank? ? l('number.currency.format.unit') : @entries[0][:currency]
@entries = WkExpenseEntry.find_by_sql(query + " order by tmp3.spent_on desc, tmp3.user_id " + rangeStr)
@unit = @entries.blank? ? number_currency_format_unit : @entries[0][:currency]
result = WkExpenseEntry.find_by_sql("select sum(v2." + spField + ") as " + spField + " from (" + query + ") as v2")
@total_hours = result[0].amount
end

def getTEAllTimeRange(ids)
teQuery = "select #{getDateSqlString('t.spent_on')} as startday " +
"from wk_expense_entries t where user_id in (#{ids}) group by startday order by startday"
def getTEAllTimeRange(ids)
teQuery = "select v.startday from (select #{getDateSqlString('t.spent_on')} as startday " +
"from wk_expense_entries t where user_id in (#{ids})) v group by v.startday order by v.startday"
teResult = WkExpenseEntry.find_by_sql(teQuery)
end

Expand Down
Loading

0 comments on commit 2b71327

Please sign in to comment.