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?) %>
<%= link_to l(:"button_new_#{controller_name}"), url_for(:controller => controller_name, :action => 'new'), :class => 'icon icon-time-add' %>
+<% end %> +<% if !@manage_projects.blank? %> + +
+
+

+

+ <%= radio_button_tag 'reminder', '1' %> + <%= l(:label_wk_sub_reminder) %>

+ <%= radio_button_tag 'reminder', '2' %> + <%= l(:label_wk_appr_reminder) %> +
+
+ <% end %> <%= form_tag({:controller => controller_name, :action => 'index'}, :method => :get, :id => 'query_form') do %> @@ -33,11 +50,13 @@ projGrp = true end %> + <% filters = call_hook(:view_te_filter) %> <% if !filters.blank? %> <%= filters %> <% else %> <% if !@manage_view_spenttime_projects.blank? %> +