diff --git a/README.rdoc b/README.rdoc
index 6bacf5903..5a677a1af 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -73,20 +73,34 @@ 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 1.9
+Release Notes for 2.0
Features:
- - Permission changes
- -- The project member with 'Edit time logs' can edit other member time/expense sheet with his permission.
- - TE Admin users is introduced
- - Empty time/expense sheet can be viewed.
- - Sorting of timesheet rows.
- - Allow entering time in different format.
- - Non empty icon if detail time entry popup has no values.
- - Comment box size increased.
- - Name is shown for closed or inactive project, issue and activity.
-
- Bugs:
- - Error message is shown if issue is blank when "Allow blank issue" is OFF.
- - Closed date is considered instead of updated date for closed issues.
+ - 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.
+
+ Bugs:
+ - Permission changes - When a Manager edits a Member T&E, his permission will be checked rather than the member's permission.
+
+We would like to thank Fotonation(http://www.fotonation.com) for sponsoring this release.
+
+Customization:
+ For any Customization/Support, please contact us, our consulting team will be happy to help you
+
+ Adhi Software Pvt Ltd
+ http://www.adhisoftware.co.in
+ info@adhisoftware.co.in
+ +1 732 661 8294
+ +91 44 27470401
+ +91 44 27470402
+
+ Here are the Customizations we have done for our clients:
+ 1. Monthly Calendar - Puny Human
+ 2. Supervisor Approvals - Fotonation
+
Please provide your rating at http://www.redmine.org/plugins/wk-time
diff --git a/app/controllers/wkexpense_controller.rb b/app/controllers/wkexpense_controller.rb
index f7517e62d..49f45b6df 100644
--- a/app/controllers/wkexpense_controller.rb
+++ b/app/controllers/wkexpense_controller.rb
@@ -127,20 +127,23 @@ def getEntityNames
["#{Wkexpense.table_name}", "#{WkExpenseEntry.table_name}"]
end
- def findBySql(selectStr,sqlStr,wkSelectStr,wkSqlStr, status, ids)
- spField = getSpecificField()
- dtRangeForUsrSqlStr = "(" + getAllWeekSql(@from, @to) + ") tmp1"
- teSqlStr = "(" + wkSelectStr + " ,exp.currency" + sqlStr + " inner join wk_expense_entries exp on v1.id = exp.id " + wkSqlStr + ") tmp2"
- query = "select tmp3.user_id, tmp3.spent_on, tmp3.#{spField}, tmp3.status, tmp3.status_updater, tmp3.created_on, tmp3.currency from (select tmp1.id as user_id, tmp1.created_on, tmp1.selected_date as spent_on, " +
+ def getQuery(teQuery, ids, from, to, status)
+ spField = getSpecificField()
+ dtRangeForUsrSqlStr = "(" + getAllWeekSql(from, to) + ") tmp1"
+ teSqlStr = "(" + teQuery + ") tmp2"
+ query = "select tmp3.user_id, tmp3.spent_on, tmp3.#{spField}, tmp3.status, tmp3.status_updater, tmp3.created_on, tmp3.currency from (select tmp1.id as user_id, tmp1.created_on, tmp1.selected_date as spent_on, " +
"case when tmp2.#{spField} is null then 0 else tmp2.#{spField} end as #{spField}, " +
"case when tmp2.status is null then 'e' else tmp2.status end as status, tmp2.currency, tmp2.status_updater from "
- query = query + dtRangeForUsrSqlStr + " left join " + teSqlStr
- query = query + " on tmp1.id = tmp2.user_id and tmp1.selected_date = tmp2.spent_on where tmp1.id in (#{ids}) ) tmp3 "
- 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 "
-
+ query = query + dtRangeForUsrSqlStr + " left join " + teSqlStr
+ query = query + " on tmp1.id = tmp2.user_id and tmp1.selected_date = tmp2.spent_on where tmp1.id in (#{ids}) ) tmp3 "
+ 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)
+ spField = getSpecificField()
result = WkExpenseEntry.find_by_sql("select count(*) as id from (" + query + ") as v2")
@entry_count = result[0].id
setLimitAndOffset()
@@ -237,4 +240,38 @@ def expense_entry_scope(options={})
end
scope
end
+
+ def getTELabel
+ l(:label_wk_expensesheet)
+ end
+
+ def findTEEntryBySql(query)
+ WkExpenseEntry.find_by_sql(query)
+ end
+
+ def formQuery(wkSelectStr, sqlStr, wkSqlStr)
+ query = wkSelectStr + " ,exp.currency" + sqlStr + " inner join wk_expense_entries exp on v1.id = exp.id " + wkSqlStr
+ end
+
+ def getUserCFFromSession
+ #return user custom field filters from session
+ session[:wkexpense][:filters]
+ end
+
+ def getUserIdFromSession
+ #return user_id from session
+ session[:wkexpense][:user_id]
+ end
+
+ def getStatusFromSession
+ session[:wkexpense][:status]
+ end
+
+ def setUserIdsInSession(ids)
+ session[:wkexpense][:all_user_ids] = ids
+ end
+
+ def getUserIdsFromSession
+ session[:wkexpense][:all_user_ids]
+ end
end
diff --git a/app/controllers/wktime_controller.rb b/app/controllers/wktime_controller.rb
index b2337ae4f..da506a9c9 100644
--- a/app/controllers/wktime_controller.rb
+++ b/app/controllers/wktime_controller.rb
@@ -12,44 +12,60 @@ class WktimeController < ApplicationController
accept_api_auth :index, :edit, :update, :destroy, :deleteEntries
helper :custom_fields
+helper :queries
+include QueriesHelper
- def index
+ def index
+ user_custom_fields = CustomField.where(['is_filter = ? AND type = ?', true, "UserCustomField"])
+ @query = nil
+ unless user_custom_fields.blank?
+ @query = WkTimeEntryQuery.build_from_params(params, :project => nil, :name => '_')
+ end
+
set_filter_session
retrieve_date_range
@from = getStartDay(@from)
@to = getEndDay(@to)
- # Paginate results
- #user_id = params[:user_id]
- #group_id = params[:group_id]
if !params[:tab].blank? && params[:tab] =='wkexpense'
user_id = session[:wkexpense][:user_id]
group_id = session[:wkexpense][:group_id]
status = session[:wkexpense][:status]
+ userfilter = getValidUserCF(session[:wkexpense][:filters], user_custom_fields)
else
- user_id = session[:wktimes][:user_id]#params[:user_id]
- group_id = session[:wktimes][:group_id]#group_id = params[:group_id]
+ user_id = session[:wktimes][:user_id]
+ group_id = session[:wktimes][:group_id]
status = session[:wktimes][:status]
+ userfilter = getValidUserCF(session[:wktimes][:filters], user_custom_fields)
+ end
+
+ unless userfilter.blank? || @query.blank?
+ @query.filters = userfilter
end
set_user_projects
if (!@manage_view_spenttime_projects.blank? && @manage_view_spenttime_projects.size > 0)
@selected_project = getSelectedProject(@manage_view_spenttime_projects, false)
end
setMembers
- ids = nil
+ ids = nil
if user_id.blank?
+ user_id = @currentUser_loggable_projects.blank? ? '-1' : User.current.id.to_s
+ end
+ #if user_id.blank?
#ids = is_member_of_any_project() ? User.current.id.to_s : '0'
- ids = User.current.id.to_s
- elsif user_id.to_i == 0
+ # ids = User.current.id.to_s
+ #elsif user_id.to_i == 0
+ if user_id.to_i == 0
unless @members.blank?
- @members.each_with_index do |users,i|
+ @members.each_with_index do |users,i|
if i == 0
ids = users[1].to_s
else
ids +=',' + users[1].to_s
end
- end
- end
+ end
+ end
ids = '0' if ids.nil?
+ setUserIdsInSession(ids) #set user ids in session if "All User" is chosen
else
ids = user_id
end
@@ -57,45 +73,9 @@ def index
if @from.blank? && @to.blank?
getAllTimeRange(ids)
end
-
- spField = getSpecificField()
- entityNames = getEntityNames()
- teSelectStr = "select v1.user_id, v1.startday as spent_on, v1." + spField
- wkSelectStr = teSelectStr + ", case when w.status is null then 'n' else w.status end as status "
- sqlStr = " from "
- sDay = getDateSqlString('t.spent_on')
- #Martin Dube contribution: 'start of the week' configuration
- if ActiveRecord::Base.connection.adapter_name == 'SQLServer'
- wkSelectStr = wkSelectStr +", un.firstname + ' ' + un.lastname as status_updater "
- sqlStr += "(select ROW_NUMBER() OVER (ORDER BY " + sDay + " desc, user_id) AS rownum," + sDay + " as startday, "
- sqlStr += " t.user_id, sum(t." + spField + ") as " + spField + " ,max(t.id) as id" + " from " + entityNames[1] + " t, users u" +
- " where u.id = t.user_id and u.id in (#{ids})"
- sqlStr += " and t.spent_on between '#{@from}' and '#{@to}'" unless @from.blank? && @to.blank?
- sqlStr += " group by " + sDay + ", user_id ) as v1"
- else
- if ActiveRecord::Base.connection.adapter_name == 'SQLite'
- wkSelectStr = wkSelectStr +", un.firstname || ' ' || un.lastname as status_updater "
- else
- wkSelectStr = wkSelectStr +", concat(un.firstname,' ' ,un.lastname) as status_updater "
- end
- sqlStr += "(select " + sDay + " as startday, "
- sqlStr += " t.user_id, sum(t." + spField + ") as " + spField + " ,max(t.id) as id" + " from " + entityNames[1] + " t, users u" +
- " where u.id = t.user_id and u.id in (#{ids})"
- sqlStr += " and t.spent_on between '#{@from}' and '#{@to}'" unless @from.blank? && @to.blank?
- sqlStr += " group by startday, user_id order by startday desc, user_id ) as v1"
-
- end
-
- wkSqlStr = " left outer join " + entityNames[0] + " w on v1.startday = w.begin_date and v1.user_id = w.user_id left outer join users un on un.id = w.statusupdater_id"
- #status = params[:status]
- #if !status.blank?
- # wkSqlStr += " WHERE w.status in ('#{status.join("','")}')"
- # if status.include?('n')
- # wkSqlStr += " OR w.status IS NULL"
- # end
- #end
-
- findBySql(teSelectStr,sqlStr,wkSelectStr,wkSqlStr, status, ids)
+ teQuery = getTEQuery(@from, @to, ids)
+ query = getQuery(teQuery, ids, @from, @to, status)
+ findBySql(query)
respond_to do |format|
format.html {
render :layout => !request.xhr?
@@ -117,6 +97,25 @@ def edit
@editable = false if @locked
set_edit_time_logs
@entries = findEntries()
+ if !$tempEntries.blank?
+ newEntries = $tempEntries - @entries
+ if !newEntries.blank?
+ $tempEntries = $tempEntries - newEntries
+ newEntries.each do |entry|
+ entry.id = ""
+ $tempEntries << entry
+ end
+ end
+ end
+ isError = params[:isError].blank? ? false : to_boolean(params[:isError])
+ if (!$tempEntries.blank? && isError)
+ @entries.each do |entry|
+ if !entry.editable_by?(User.current) && !isAccountUser
+ $tempEntries << entry
+ end
+ end
+ @entries = $tempEntries
+ end
set_project_issues(@entries)
if @entries.blank? && !params[:prev_template].blank?
@prev_entries = prevTemplate(@user.id)
@@ -176,22 +175,20 @@ def update
if (!entry.id.blank? && !entry.editable_by?(User.current))
allowSave = false
end
- if to_boolean(@edittimelogs)
- allowSave = true
- end
- if !((Setting.plugin_redmine_wktime['wktime_allow_blank_issue'].blank? ||
- Setting.plugin_redmine_wktime['wktime_allow_blank_issue'].to_i == 0) &&
- entry.issue.blank?)
+ allowSave = true if (to_boolean(@edittimelogs) || isAccountUser)
+ #if !((Setting.plugin_redmine_wktime['wktime_allow_blank_issue'].blank? ||
+ # Setting.plugin_redmine_wktime['wktime_allow_blank_issue'].to_i == 0) &&
+ # entry.issue.blank?)
if allowSave
errorMsg = updateEntry(entry)
else
errorMsg = l(:error_not_permitted_save) if !api_request?
end
break unless errorMsg.blank?
- else
- errorMsg = "#{l(:field_issue)} #{l('activerecord.errors.messages.blank')} "
- break unless errorMsg.blank?
- end
+ #else
+ # errorMsg = "#{l(:field_issue)} #{l('activerecord.errors.messages.blank')} "
+ # break unless errorMsg.blank?
+ #end
end
if !params[:wktime_submit].blank? && useApprovalSystem
@wktime.submitted_on = Date.today
@@ -231,7 +228,9 @@ def update
elsif !params[:wktime_unapprove].blank? && !@wktime.nil? && @wktime.status == 'a' && allowApprove
errorMsg = updateStatus(:s)
elsif !params[:wktime_submit].blank? && !@wktime.nil? && ( @wktime.status == 'n' || @wktime.status == 'r')
- #if TE sheet is read only mode with submit button
+ #if TE sheet is read only mode with submit button
+ @wktime.submitted_on = Date.today
+ @wktime.submitter_id = User.current.id
if !Setting.plugin_redmine_wktime['wktime_uuto_approve'].blank? &&
Setting.plugin_redmine_wktime['wktime_uuto_approve'].to_i == 1
errorMsg = updateStatus(:a)
@@ -262,6 +261,7 @@ def update
format.html {
if errorMsg.nil?
flash[:notice] = respMsg
+ $tempEntries = nil
#redirect_back_or_default :action => 'index'
#redirect_to :action => 'index' , :tab => params[:tab]
if params[:wktime_save_continue]
@@ -271,11 +271,12 @@ def update
end
else
flash[:error] = respMsg
+ $tempEntries = @entries
if !params[:enter_issue_id].blank? && params[:enter_issue_id].to_i == 1
- redirect_to :action => 'edit', :user_id => params[:user_id], :startday => @startday,
+ redirect_to :action => 'edit', :user_id => params[:user_id], :startday => @startday, :isError => true,
:enter_issue_id => 1
else
- redirect_to :action => 'edit', :user_id => params[:user_id], :startday => @startday
+ redirect_to :action => 'edit', :user_id => params[:user_id], :startday => @startday, :isError => true
end
end
}
@@ -495,7 +496,7 @@ def getactivities
end
actStr =""
project.activities.each do |a|
- actStr << project_id.to_s() + '|' + a.id.to_s() + '|' + a.name + "\n"
+ actStr << project_id.to_s() + '|' + a.id.to_s() + '|' + a.is_default.to_s() + '|' + a.name + "\n"
end
respond_to do |format|
@@ -616,8 +617,12 @@ def total_all(total)
html_hours(l_hours(total))
end
- def getStatus
- status = getTimeEntryStatus(params[:startDate].to_date,params[:user_id])
+ def getStatus
+ if !params[:startDate].blank?
+ status = getTimeEntryStatus(params[:startDate].to_date,params[:user_id])
+ else
+ status = nil
+ end
respond_to do |format|
format.text { render :text => status }
end
@@ -669,6 +674,7 @@ def check_approvable_status
if !hookPerm.blank?
ret = hookPerm[0]
end
+ ret = true if isAccountUser
ret = ((ret || !te_projects.blank?) && (@user.id != User.current.id || (!Setting.plugin_redmine_wktime[:wktime_own_approval].blank? &&
Setting.plugin_redmine_wktime[:wktime_own_approval].to_i == 1 )))? true: false
@@ -698,8 +704,194 @@ def checkDDWidth
end
ret
end
+
+ def getTracker
+ ret = false;
+ tracker = getTrackerbyIssue(params[:issue_id])
+ settingstracker = Setting.plugin_redmine_wktime[getTFSettingName()]
+ allowtracker = Setting.plugin_redmine_wktime['wktime_allow_user_filter_tracker'].to_i
+ if settingstracker != ["0"]
+ if ((["#{tracker}"] == settingstracker) || (tracker == '0'))
+ ret = true
+ end
+ else
+ ret = true
+ end
+
+ if allowtracker == 1
+ ret = true
+ end
+
+ respond_to do |format|
+ format.text { render :text => ret }
+ end
+ end
+
+ def sendSubReminderEmail
+ userList = ""
+ weekHash = Hash.new
+ userHash = Hash.new
+ mngrHash = Hash.new
+ respMsg = "OK"
+ allowedStatus = ['e','r','n'];
+ pStatus = getStatusFromSession #params[:status].split(',')
+ status = pStatus.blank? ? allowedStatus : (allowedStatus & pStatus)
+ wkentries = nil
+ if !status.blank?
+ ids = getUserIds
+ setUserCFQuery
+ label_te = getTELabel
+ teQuery = getTEQuery(params[:from].to_date, params[:to].to_date, ids)
+ query = getQuery(teQuery, ids, params[:from].to_date, params[:to].to_date, status) #['e','r','n']
+
+ wkentries = findTEEntryBySql(query)
+ wkentries.each do |entries|
+ user = entries.user
+ if !userHash.has_key?(user.id)
+ userHash[user.id] = user
+ weekHash[user.id] = [entries.spent_on]
+ hookMgr = call_hook(:controller_get_manager, {:user => user, :approver => false})
+ mngrArr = hookMgr.blank? ? nil : hookMgr[0]
+ mngrHash[user.id] = mngrArr
+ else
+ weekArr = weekHash[user.id]
+ weekHash[user.id] = weekArr << entries.spent_on
+ end
+ end
+ userHash.each_key do |key|
+ user = userHash[key]
+ errMsg = ""
+ begin
+ WkMailer.submissionReminder(user, mngrHash[key], weekHash[key], params[:email_notes], label_te).deliver
+ rescue Exception => e
+ errMsg = e.message
+ end
+ userList += user.name + "\n" if errMsg.blank?
+ end
+ WkMailer.sendConfirmationMail(userList, true, label_te).deliver if !userList.blank?
+ end
+ respMsg = l(:text_wk_no_reminder) if (wkentries.blank? || (!wkentries.blank? && wkentries.size == 0))
+ respond_to do |format|
+ format.text { render :text => respMsg }
+ end
+ end
+
+ def sendApprReminderEmail
+ mgrList = ""
+ userHash = Hash.new
+ mgrHash = Hash.new
+ respMsg = "OK"
+ allowedStatus = ['s'];
+ pStatus = getStatusFromSession
+ status = pStatus.blank? ? allowedStatus : (allowedStatus & pStatus)
+ users = nil
+ if !status.blank?
+ entityNames = getEntityNames
+ ids = getUserIds
+ setUserCFQuery
+ label_te = getTELabel
+ user_cf_sql = @query.user_cf_statement('u') if !@query.blank?
+ queryStr = "select distinct u.* from users u " +
+ "left outer join #{entityNames[0]} w on u.id = w.user_id " +
+ "and (w.begin_date between '#{params[:from]}}' and '#{params[:to]}') " #+
+ #"where u.id in (#{ids}) and w.status = 's'"
+ queryStr += " #{user_cf_sql} " if !user_cf_sql.blank?
+ queryStr += (!user_cf_sql.blank? ? " AND " : " WHERE ") + " u.id in (#{ids}) and w.status = 's' "
+
+ users = User.find_by_sql(queryStr)
+ users.each do |user|
+ mngrArr = getManager(user, true)
+ if !mngrArr.blank?
+ mngrArr.each do |m|
+ userArr = []
+ if mgrHash.has_key?(m.id)
+ userArr = userHash[m.id]
+ userHash[m.id] = userArr << user
+ else
+ mgrHash[m.id] = m
+ userHash[m.id] = [user]
+ end
+ end
+ end
+ end
+ if !mgrHash.blank?
+ mgrHash.each_key do |key|
+ userList = []
+ subOrd = userHash[key]
+ subOrd.each do |user|
+ userList << user.name
+ end
+ errMsg = ""
+ begin
+ WkMailer.approvalReminder(mgrHash[key], userList.uniq.join("\n"), params[:email_notes], label_te).deliver
+ rescue Exception => e
+ errMsg = e.message
+ end
+ mgrList += mgrHash[key].name + "\n" if errMsg.blank?
+ end
+ WkMailer.sendConfirmationMail(mgrList, false, label_te).deliver if !mgrList.blank?
+ end
+ end
+ respMsg = l(:text_wk_no_reminder) if (users.blank? || (!users.blank? && users.size == 0))
+ respond_to do |format|
+ format.text { render :text => respMsg }
+ end
+ end
+
+
private
+ def getManager(user, approver)
+ hookMgr = call_hook(:controller_get_manager, {:user => user, :approver => approver})
+ mngrArr = [] #nil
+ if !hookMgr.blank?
+ mngrArr = hookMgr[0] if !hookMgr[0].blank?
+ else
+ #includeAppr = (!Setting.plugin_redmine_wktime[:wktime_own_approval].blank? && Setting.plugin_redmine_wktime[:wktime_own_approval].to_i == 1 )
+ apprPerm = "and r.permissions like '%approve_time_entries%'"
+ queryStr = "select distinct u.* from projects p" +
+ " inner join members m on p.id = m.project_id and p.status = 1 " +
+ #" #{!includeAppr ? ('and m.user_id <> ' + user.id.to_s) : ''}" +
+ " inner join member_roles mr on m.id = mr.member_id" +
+ " inner join roles r on mr.role_id = r.id and (r.permissions like '%edit_time_entries%' " +
+ " #{approver ? apprPerm : ''}" + ')' +
+ " inner join users u on m.user_id = u.id" +
+ " inner join members m1 on p.id = m1.project_id and m1.user_id = #{user.id}"
+
+ mngrs = User.find_by_sql(queryStr)
+ mngrs.each do |m|
+ mngrArr << m
+ end
+ end
+ mngrArr
+ end
+
+ def setUserCFQuery
+ userfilters = getUserCFFromSession
+ user_custom_fields = CustomField.where(['is_filter = ? AND type = ?', true, "UserCustomField"])
+ @query = nil
+ unless user_custom_fields.blank?
+ @query = WkTimeEntryQuery.build_from_params(params, :project => nil, :name => '_')
+ @query.filters = userfilters if !@query.blank?
+ end
+ end
+
+ def getUserIds
+ user_id = getUserIdFromSession
+ label_te = getTELabel
+
+ ids = nil
+ if user_id.blank?
+ ids = User.current.id.to_s
+ elsif user_id.to_i == 0
+ ids = getUserIdsFromSession
+ ids = '0' if ids.nil?
+ else
+ ids = user_id
+ end
+ ids
+ end
+
def check_permission
ret = false
@@ -708,7 +900,7 @@ def check_permission
status = getTimeEntryStatus(@startday, @user_id)
approve_projects = @approvable_projects & @logtime_projects
if (status != 'n' && (!approve_projects.blank? && approve_projects.size > 0))
- #for approver
+ #for approver
ret = true
else
if !@manage_projects.blank? && @manage_projects.size > 0
@@ -726,7 +918,7 @@ def check_permission
if !editPermission.blank?
ret = editPermission[0] || (@user.id == User.current.id && @logtime_projects.size > 0)
end
- return ret
+ return (ret || isAccountUser)
end
def getGrpMembers
@@ -841,6 +1033,7 @@ def gatherEntries
teEntry.attributes = entry
# since project_id and user_id is protected
teEntry.project_id = entry['project_id']
+ teEntry.issue_id = nil if entry['issue_id'].blank?
teEntry.user_id = @user.id
teEntry.spent_on = @startday + k
#for one comment, it will be automatically loaded into the object
@@ -1036,7 +1229,7 @@ def can_log_time?(project_id)
def check_editperm_redirect
hookPerm = call_hook(:controller_edit_timelog_permission, {:params => params})
if !hookPerm.blank?
- allow = hookPerm[0] || (check_editPermission && @user.id == User.current.id)
+ allow = hookPerm[0] || (check_editPermission && @user.id == User.current.id) || isAccountUser
else
allow = check_editPermission
end
@@ -1066,6 +1259,7 @@ def check_editPermission
break
end
end
+ allowed = true if isAccountUser
return allowed
end
@@ -1084,8 +1278,33 @@ def updateEntry(entry)
#if id is there it should be update otherwise create
#the UI disables editing of
if can_log_time?(entry.project_id) || to_boolean(@edittimelogs)
+ issueId = entry.issue_id
+ if entry.issue_id == -1
+ entry.issue_id = ''
+ end
+
+ activityId = entry.activity_id
+ if entry.activity_id == -1
+ entry.activity_id = ''
+ end
+
+ if ((Setting.plugin_redmine_wktime['wktime_allow_blank_issue'].blank? ||
+ Setting.plugin_redmine_wktime['wktime_allow_blank_issue'].to_i == 0) &&
+ entry.issue.blank?)
+ errorMsg = "#{l(:field_issue)} #{l('activerecord.errors.messages.blank')} "
+ end
+
if !entry.save()
- errorMsg = entry.errors.full_messages.join('\n')
+ errorMsg = errorMsg.blank? ? entry.errors.full_messages : entry.errors.full_messages.unshift(errorMsg)
+ errorMsg = errorMsg.join("
")
+ end
+
+ if issueId == -1
+ entry.issue_id = -1
+ end
+
+ if activityId == -1
+ entry.activity_id = -1
end
else
errorMsg = l(:error_not_permitted_save)
@@ -1111,7 +1330,7 @@ def updateStatus(status)
errorMsg = nil
if @wktimes.blank?
errorMsg = l(:error_wktime_save_nothing)
- else
+ else
@wktime.statusupdater_id = User.current.id
@wktime.statusupdate_on = Date.today
@wktime.status = status
@@ -1263,7 +1482,11 @@ def set_managed_projects
if !mng_projects.blank?
@manage_projects = mng_projects[0].blank? ? nil : mng_projects[0]
else
- @manage_projects ||= Project.where(Project.allowed_to_condition(User.current, :edit_time_entries)).order('name')
+ if isAccountUser
+ @manage_projects = getAccountUserProjects
+ else
+ @manage_projects ||= Project.where(Project.allowed_to_condition(User.current, :edit_time_entries)).order('name')
+ end
end
@manage_projects = setTEProjects(@manage_projects)
@@ -1274,7 +1497,6 @@ def set_managed_projects
@manage_view_spenttime_projects = view_projects[0].blank? ? nil : view_projects[0]
else
if isAccountUser
- #@manage_view_spenttime_projects = Project.all
@manage_view_spenttime_projects = getAccountUserProjects
else
view_spenttime_projects ||= Project.where(Project.allowed_to_condition(User.current, :view_time_entries)).order('name')
@@ -1297,10 +1519,24 @@ def set_loggable_projects
u_id = params[:user_id]
end
if !u_id.blank? && u_id.to_i != 0
- @user ||= User.find(u_id)
- #@logtime_projects ||= Project.find(:all, :order => 'name', :conditions => Project.allowed_to_condition(@user, :log_time))
- @logtime_projects ||= Project.where(Project.allowed_to_condition(@user, :log_time)).order('name')
- @logtime_projects = setTEProjects(@logtime_projects)
+ @user ||= User.find(u_id)
+ if User.current == @user
+ @logtime_projects ||= Project.where(Project.allowed_to_condition(@user, :log_time)).order('name')
+ else
+ hookProjs = call_hook(:controller_get_permissible_projs, {:user => @user})
+ if !hookProjs.blank?
+ @logtime_projects = hookProjs[0].blank? ? [] : hookProjs[0]
+ else
+ user_projects ||= Project
+ .joins("INNER JOIN #{EnabledModule.table_name} ON projects.id = enabled_modules.project_id and enabled_modules.name='time_tracking'")
+ .joins("INNER JOIN #{Member.table_name} ON projects.id = members.project_id")
+ .where("#{Member.table_name}.user_id = #{@user.id} AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
+ logtime_projects ||= Project.where(Project.allowed_to_condition(@user, :log_time)).order('name')
+ @logtime_projects = logtime_projects | user_projects
+ end
+ end
+ #@logtime_projects = @logtime_projects & @manage_projects if !@manage_projects.blank?
+ @logtime_projects = setTEProjects(@logtime_projects)
end
end
@@ -1366,10 +1602,44 @@ def getEntityNames
["#{Wktime.table_name}", "#{TimeEntry.table_name}"]
end
- def findBySql(selectStr,sqlStr,wkSelectStr,wkSqlStr, status, ids)
- spField = getSpecificField()
- dtRangeForUsrSqlStr = "(" + getAllWeekSql(@from, @to) + ") tmp1"
- teSqlStr = "(" + wkSelectStr + sqlStr + wkSqlStr + ") tmp2"
+ def getTEQuery(from, to, ids)
+ spField = getSpecificField()
+ entityNames = getEntityNames()
+ teSelectStr = "select v1.user_id, v1.startday as spent_on, v1." + spField
+ wkSelectStr = teSelectStr + ", case when w.status is null then 'n' else w.status end as status "
+ sqlStr = " from "
+ sDay = getDateSqlString('t.spent_on')
+ #Martin Dube contribution: 'start of the week' configuration
+ if ActiveRecord::Base.connection.adapter_name == 'SQLServer'
+ wkSelectStr = wkSelectStr +", un.firstname + ' ' + un.lastname as status_updater "
+ sqlStr += "(select ROW_NUMBER() OVER (ORDER BY " + sDay + " desc, user_id) AS rownum," + sDay + " as startday, "
+ sqlStr += " t.user_id, sum(t." + spField + ") as " + spField + " ,max(t.id) as id" + " from " + entityNames[1] + " t, users u" +
+ " where u.id = t.user_id and u.id in (#{ids})"
+ sqlStr += " and t.spent_on between '#{from}' and '#{to}'" unless from.blank? && to.blank?
+ sqlStr += " group by " + sDay + ", user_id ) as v1"
+ else
+ if ActiveRecord::Base.connection.adapter_name == 'SQLite'
+ wkSelectStr = wkSelectStr +", un.firstname || ' ' || un.lastname as status_updater "
+ else
+ wkSelectStr = wkSelectStr +", concat(un.firstname,' ' ,un.lastname) as status_updater "
+ end
+ sqlStr += "(select " + sDay + " as startday, "
+ sqlStr += " t.user_id, sum(t." + spField + ") as " + spField + " ,max(t.id) as id" + " from " + entityNames[1] + " t, users u" +
+ " where u.id = t.user_id and u.id in (#{ids})"
+ sqlStr += " and t.spent_on between '#{from}' and '#{to}'" unless from.blank? && to.blank?
+ sqlStr += " group by startday, user_id order by startday desc, user_id ) as v1"
+ end
+
+ wkSqlStr = " left outer join " + entityNames[0] + " w on v1.startday = w.begin_date and v1.user_id = w.user_id " +
+ "left outer join users un on un.id = w.statusupdater_id"
+
+ query = formQuery(wkSelectStr, sqlStr, wkSqlStr)
+ end
+
+ def getQuery(teQuery, ids, from, to, status)
+ spField = getSpecificField()
+ dtRangeForUsrSqlStr = "(" + getAllWeekSql(from, to) + ") tmp1"
+ teSqlStr = "(" + teQuery + ") tmp2"
query = "select tmp3.user_id, tmp3.spent_on, tmp3.#{spField}, tmp3.status, tmp3.status_updater, tmp3.created_on from (select tmp1.id as user_id, tmp1.created_on, tmp1.selected_date as spent_on, " +
"case when tmp2.#{spField} is null then 0 else tmp2.#{spField} end as #{spField}, " +
@@ -1380,7 +1650,10 @@ def findBySql(selectStr,sqlStr,wkSelectStr,wkSqlStr, status, ids)
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)
+ spField = getSpecificField()
result = TimeEntry.find_by_sql("select count(*) as id from (" + query + ") as v2")
@entry_count = result[0].id
setLimitAndOffset()
@@ -1401,14 +1674,15 @@ def getWhereCond(status)
query = query + "and tmp3.status <> 'e') "
query = query + "OR (tmp3.spent_on > '#{current_date}' and tmp3.status <> 'e'))) "
if !status.blank?
- query += " and tmp3.status in ('#{status.join("','")}') "
+ query += " and tmp3.status in ('#{status.join("','")}') "
end
query
end
def getAllWeekSql(from, to)
- #to = to.blank? || to > Date.today ? getEndDay(Date.today) : to
- entityNames = getEntityNames()
+ entityNames = getEntityNames()
+ user_cf_sql = @query.user_cf_statement('u') if !@query.blank?
+
noOfDays = 't4.i*7*10000 + t3.i*7*1000 + t2.i*7*100 + t1.i*7*10 + t0.i*7'
sqlStr = "select u.id, u.created_on, v.selected_date from " +
"(select " + getAddDateStr(from, noOfDays) + " selected_date from " +
@@ -1417,8 +1691,9 @@ def getAllWeekSql(from, to)
"(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2, " +
"(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3, " +
"(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v, " +
- "(select distinct u.id, u.created_on from users u) u " +
- "where selected_date between '#{from}' and '#{to}'"
+ "(select u.id, u.created_on from users u) u "
+ sqlStr += " #{user_cf_sql} " if !user_cf_sql.blank?
+ sqlStr += (!user_cf_sql.blank? ? " AND " : " WHERE ") + " v.selected_date between '#{from}' and '#{to}' "
end
def getTEAllTimeRange(ids)
@@ -1636,12 +1911,21 @@ def is_member_of_any_project
ret = projMember.size > 0
end
+ def getTrackerbyIssue(issue_id)
+ result = Issue.where(['id = ?',issue_id]) if !issue_id.blank?
+ tracker = !result.blank? ? (result[0].blank? ? '0' : result[0].tracker_id if !result.blank?) : '0'
+ tracker
+ end
+
def set_filter_session
if params[:searchlist].blank? && (session[:wktimes].nil? || session[:wkexpense].nil?)
-
- session[:wktimes] = {:period_type => params[:period_type], :period => params[:period],:from => params[:from],:to => params[:to],:project_id => params[:project_id], :filter_type => params[:filter_type],:user_id => params[:user_id],:status => params[:status],:group_id => params[:group_id] }
- session[:wkexpense] = {:period_type => params[:period_type], :period => params[:period],:from => params[:from],:to => params[:to],:project_id => params[:project_id], :filter_type => params[:filter_type],:user_id => params[:user_id],:status => params[:status],:group_id => params[:group_id] }
+ session[:wktimes] = {:period_type => params[:period_type], :period => params[:period],:from => params[:from],:to => params[:to],
+ :project_id => params[:project_id], :filter_type => params[:filter_type],:user_id => params[:user_id],:status => params[:status],
+ :group_id => params[:group_id], :filters => @query.blank? ? nil : @query.filters }
+ session[:wkexpense] = {:period_type => params[:period_type], :period => params[:period],:from => params[:from],:to => params[:to],
+ :project_id => params[:project_id], :filter_type => params[:filter_type],:user_id => params[:user_id],:status => params[:status],
+ :group_id => params[:group_id], :filters => @query.blank? ? nil : @query.filters }
#session[:wkexpense] = session[:wktimes]
elsif params[:searchlist] =='wktime' || api_request?
session[:wktimes][:period_type] = params[:period_type]
@@ -1653,6 +1937,7 @@ def set_filter_session
session[:wktimes][:user_id] = params[:user_id]
session[:wktimes][:status] = params[:status]
session[:wktimes][:group_id] = params[:group_id]
+ session[:wktimes][:filters] = @query.blank? ? nil : @query.filters
elsif params[:searchlist] =='wkexpense' || api_request?
session[:wkexpense][:period_type] = params[:period_type]
session[:wkexpense][:period] = params[:period]
@@ -1663,6 +1948,40 @@ def set_filter_session
session[:wkexpense][:user_id] = params[:user_id]
session[:wkexpense][:status] = params[:status]
session[:wkexpense][:group_id] = params[:group_id]
+ session[:wkexpense][:filters] = @query.blank? ? nil : @query.filters
end
- end
+ end
+
+ def getTELabel
+ l(:label_wk_timesheet)
+ end
+
+ def findTEEntryBySql(query)
+ TimeEntry.find_by_sql(query)
+ end
+
+ def formQuery(wkSelectStr, sqlStr, wkSqlStr)
+ query = wkSelectStr + sqlStr + wkSqlStr
+ end
+
+ def getUserCFFromSession
+ session[:wktimes][:filters]
+ end
+
+ def getUserIdFromSession
+ #return user_id from session
+ session[:wktimes][:user_id]
+ end
+
+ def getStatusFromSession
+ session[:wktimes][:status]
+ end
+
+ def setUserIdsInSession(ids)
+ session[:wktimes][:all_user_ids] = ids
+ end
+
+ def getUserIdsFromSession
+ session[:wktimes][:all_user_ids]
+ end
end
\ No newline at end of file
diff --git a/app/helpers/wktime_helper.rb b/app/helpers/wktime_helper.rb
index 09833fb2a..1d158d312 100644
--- a/app/helpers/wktime_helper.rb
+++ b/app/helpers/wktime_helper.rb
@@ -63,7 +63,7 @@ def wktime_to_csv(entries, user, startday, unitLabel)
decimal_separator = l(:general_csv_decimal_separator)
#custom_fields = WktimeCustomField.find(:all)
custom_fields = WktimeCustomField.all
- export = CSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
+ export = Redmine::Export::CSV.generate do |csv|
# csv header fields
headers = [l(:field_user),
l(:field_project),
@@ -200,7 +200,7 @@ def wktime_to_pdf(entries, user, startday, unitLabel)
pdf.AddPage(orientation)
if !logo.blank? && (File.exist? (Redmine::Plugin.public_directory + "/redmine_wktime/images/" + logo))
- pdf.Image(Redmine::Plugin.public_directory + "/redmine_wktime/images/" + logo, page_width-10-20, 10)
+ pdf.Image(Redmine::Plugin.public_directory + "/redmine_wktime/images/" + logo, page_width-50, 10,40,25)
end
render_header(pdf, entries, user, startday, row_height,title)
@@ -283,16 +283,16 @@ def wktime_to_pdf_write_cells(pdf, col_values, col_widths,
def render_newpage(pdf,orientation,logo,page_width)
pdf.AddPage(orientation)
if !logo.blank? && (File.exist? (Redmine::Plugin.public_directory + "/redmine_wktime/images/" + logo))
- pdf.Image(Redmine::Plugin.public_directory + "/redmine_wktime/images/" + logo, page_width-10-20, 10)
+ pdf.Image(Redmine::Plugin.public_directory + "/redmine_wktime/images/" + logo, page_width-50, 10,40,25)
pdf.Ln
- pdf.SetY(pdf.GetY+10)
+ pdf.SetY(pdf.GetY+25)
end
end
def getKey(entry,unitLabel)
cf_in_row1_value = nil
cf_in_row2_value = nil
- key = entry.project.id.to_s + (entry.issue.blank? ? '' : entry.issue.id.to_s) + entry.activity.id.to_s + (unitLabel.blank? ? '' : entry.currency)
+ key = entry.project.id.to_s + (entry.issue.blank? ? '' : entry.issue.id.to_s) + (entry.activity.blank? ? '' : entry.activity.id.to_s) + (unitLabel.blank? ? '' : entry.currency)
entry.custom_field_values.each do |custom_value|
custom_field = custom_value.custom_field
if (!Setting.plugin_redmine_wktime['wktime_enter_cf_in_row1'].blank? && Setting.plugin_redmine_wktime['wktime_enter_cf_in_row1'].to_i == custom_field.id)
@@ -382,7 +382,7 @@ def getColumnValues(matrix, totals, unitLabel,rowNumberRequired, j=0)
if !issueWritten
col_values[k] = entry.project.name
col_values[k+1] = entry.issue.blank? ? "" : entry.issue.subject
- col_values[k+2] = entry.activity.name
+ col_values[k+2] = entry.activity.blank? ? "" : entry.activity.name
if !unitLabel.blank?
col_values[k+3]= entry.currency
end
@@ -793,4 +793,24 @@ def getAddDateStr(dtfield,noOfDays)
end
dateSqlStr
end
+
+ def getValidUserCF(userCFHash, userCF)
+ tmpUserCFHash = userCFHash
+ if !userCF.blank? && !userCFHash.blank?
+ cfHash = Hash.new
+ userCF.each do |cf|
+ cfHash["user.cf_#{cf.id}"] = "#{cf.name}"
+ end
+ userCFHash.each_key do |key|
+ if !cfHash.has_key?(key)
+ tmpUserCFHash.delete(key)
+ end
+ end
+ end
+ tmpUserCFHash
+ end
+
+ #def getAllowedTrackerId
+ # Setting.plugin_redmine_wktime['wktime_issues_filter_tracker']
+ #end
end
\ No newline at end of file
diff --git a/app/models/wk_mailer.rb b/app/models/wk_mailer.rb
index a89a82f5c..ce49c2020 100644
--- a/app/models/wk_mailer.rb
+++ b/app/models/wk_mailer.rb
@@ -32,6 +32,39 @@ def nonSubmissionNotification(user,startDate)
body += "\n #{l(:field_name)} : #{user.firstname} #{user.lastname} "
body += "\n #{ l(:label_week) }" + " : " + startDate.to_s + " - " + (startDate+6).to_s
- mail :from => Setting.mail_from ,:to => user.mail, :subject => subject,:body => body
+ mail :from => Setting.mail_from, :to => user.mail, :subject => subject, :body => body
+ end
+
+ def submissionReminder(user, mngrArr, weeks, emailNotes, label_te)
+ set_language_if_valid(user.language)
+
+ subject = l(:wk_submission_reminder, label_te)
+ body = l(:wk_sub_reminder_text, label_te) + "\n" + weeks.join("\n")
+ body += "\n" + emailNotes if !emailNotes.blank?
+ body += "\n"
+
+ mail :from => User.current.mail, :to => user.mail, :reply_to => User.current.mail,
+ :cc => (mngrArr.blank? ? nil : (mngrArr[0].blank? ? nil : mngrArr[0].mail)), :subject => subject, :body => body
+ end
+
+ def approvalReminder(mgr, userList, emailNotes, label_te)
+ set_language_if_valid(mgr.language)
+
+ subject = l(:wk_approval_reminder, label_te)
+ body = "#{l(:wk_appr_reminder_text, label_te)}" + "\n"
+ body += userList
+ body += "\n" + emailNotes if !emailNotes.blank?
+ body += "\n"
+
+ mail :from => User.current.mail, :to => mgr.mail, :reply_to => User.current.mail, :subject => subject, :body => body
+ end
+
+ def sendConfirmationMail(userList, isSub, label_te)
+ set_language_if_valid(User.current.language)
+
+ subject = isSub ? l(:wk_submission_reminder, label_te) : l(:wk_approval_reminder, label_te)
+ body = (isSub ? "#{l(:wk_sub_confirmation_text, label_te)}" : "#{l(:wk_appr_confirmation_text, label_te)}" ) + "\n" + userList
+
+ mail :from => User.current.mail, :to => User.current.mail, :subject => subject, :body => body
end
end
diff --git a/app/models/wk_time_entry_query.rb b/app/models/wk_time_entry_query.rb
new file mode 100644
index 000000000..d487f8c66
--- /dev/null
+++ b/app/models/wk_time_entry_query.rb
@@ -0,0 +1,80 @@
+class WkTimeEntryQuery < Query
+ self.queried_class = TimeEntry
+
+ def initialize(attributes=nil, *args)
+ super attributes
+ self.filters ||= {}
+ #add_filter('spent_on', '*') unless filters.present?
+ end
+
+ def initialize_available_filters
+ #add_available_filter "spent_on", :type => :date_past
+ add_associations_custom_fields_filters :user
+ end
+
+ def user_cf_statement(tblAlias)
+ # filters clauses
+ #filters_clauses = []
+ leftJoinClause = []
+ whereClause = []
+ sqlClause = []
+ i = 0
+ filters.each_key do |field|
+ #next if field == "subproject_id"
+ v = values_for(field).clone
+ next unless v and !v.empty?
+ operator = operator_for(field)
+ i = i + 1
+ if field =~ /cf_(\d+)$/
+ # custom field
+ sqlClause = sql_for_user_custom_field(field, operator, v, $1, i, tblAlias)
+ leftJoinClause << sqlClause[0]
+ whereClause << sqlClause[1]
+ end
+ end if filters and valid?
+
+ leftJoinClause.reject!(&:blank?)
+ strLeftJoinClause = leftJoinClause.any? ? leftJoinClause.join(' ') : nil
+
+ whereClause.reject!(&:blank?)
+ strWhereClause = whereClause.any? ? whereClause.join(' AND ') : nil
+
+ strUserCf = !strLeftJoinClause.blank? ? (strLeftJoinClause + (!strWhereClause.blank? ? (' WHERE ' + strWhereClause) : '')) : ''
+
+ end
+
+ private
+
+ def sql_for_user_custom_field(field, operator, value, custom_field_id, alias_num, tblAlias)
+ db_table = CustomValue.table_name
+ db_field = 'value'
+ filter = @available_filters[field]
+ return nil unless filter
+ if filter[:field].format.target_class && filter[:field].format.target_class <= User
+ if value.delete('me')
+ value.push User.current.id.to_s
+ end
+ end
+ #not_in = nil
+ #if operator == '!'
+ # Makes ! operator work for custom fields with multiple values
+ # operator = '!'
+ # not_in = 'NOT'
+ #end
+ customized_key = "id"
+ customized_class = queried_class
+ if field =~ /^(.+)\.cf_/
+ assoc = $1
+ customized_key = "#{assoc}_id"
+ customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
+ raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
+ end
+ where = sql_for_field(field, operator, value, db_table, db_field, true)
+ if operator =~ /[<>]/
+ where = "(#{where}) AND #{db_table}.#{db_field} <> ''"
+ end
+ leftJoinClause = " LEFT OUTER JOIN #{db_table} cf#{alias_num} ON cf#{alias_num}.customized_type='#{customized_class}' AND cf#{alias_num}.customized_id=#{tblAlias}.id AND cf#{alias_num}.custom_field_id=#{custom_field_id}"
+ whereClause = " (#{where.gsub(db_table,'cf' + alias_num.to_s)} AND (#{filter[:field].visibility_by_project_condition}))"
+ return [leftJoinClause, whereClause]
+ end
+end
\ No newline at end of file
diff --git a/app/models/wktime.rb b/app/models/wktime.rb
index c3fd11cd7..b063e9096 100644
--- a/app/models/wktime.rb
+++ b/app/models/wktime.rb
@@ -3,8 +3,8 @@ class Wktime < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :user
- belongs_to :user, :class_name => 'User', :foreign_key => 'submitter_id'
- belongs_to :user, :class_name => 'User', :foreign_key => 'statusupdater_id'
+ belongs_to :submitter, :class_name => 'User', :foreign_key => 'submitter_id'
+ belongs_to :updater, :class_name => 'User', :foreign_key => 'statusupdater_id'
acts_as_customizable
diff --git a/app/views/wkexpense/edit.html.erb b/app/views/wkexpense/edit.html.erb
index 61182cc20..7fac338f6 100644
--- a/app/views/wkexpense/edit.html.erb
+++ b/app/views/wkexpense/edit.html.erb
@@ -3,6 +3,8 @@
<%= javascript_include_tag 'edit', :plugin => "redmine_wktime" %>
<% if (!@currentUser_loggable_projects.blank? || !@manage_projects.blank?) %>