annotate app/controllers/timelog_controller.rb @ 36:de76cd3e8c8e cc-branches

* Probably abortive experiments in extracting the branch from Hg
author Chris Cannam <chris.cannam@soundsoftware.ac.uk>
date Wed, 20 Oct 2010 10:07:29 +0100
parents 40f7cfd4df19
children 94944d00e43c
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 class TimelogController < ApplicationController
Chris@0 19 menu_item :issues
Chris@0 20 before_filter :find_project, :authorize, :only => [:edit, :destroy]
Chris@0 21 before_filter :find_optional_project, :only => [:report, :details]
Chris@0 22 before_filter :load_available_criterias, :only => [:report]
Chris@0 23
Chris@0 24 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
Chris@0 25
Chris@0 26 helper :sort
Chris@0 27 include SortHelper
Chris@0 28 helper :issues
Chris@0 29 include TimelogHelper
Chris@0 30 helper :custom_fields
Chris@0 31 include CustomFieldsHelper
Chris@0 32
Chris@0 33 def report
Chris@0 34 @criterias = params[:criterias] || []
Chris@0 35 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
Chris@0 36 @criterias.uniq!
Chris@0 37 @criterias = @criterias[0,3]
Chris@0 38
Chris@0 39 @columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month'
Chris@0 40
Chris@0 41 retrieve_date_range
Chris@0 42
Chris@0 43 unless @criterias.empty?
Chris@0 44 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
Chris@0 45 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
Chris@0 46 sql_condition = ''
Chris@0 47
Chris@0 48 if @project.nil?
Chris@0 49 sql_condition = Project.allowed_to_condition(User.current, :view_time_entries)
Chris@0 50 elsif @issue.nil?
Chris@0 51 sql_condition = @project.project_condition(Setting.display_subprojects_issues?)
Chris@0 52 else
Chris@0 53 sql_condition = "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
Chris@0 54 end
Chris@0 55
Chris@0 56 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours"
Chris@0 57 sql << " FROM #{TimeEntry.table_name}"
Chris@14 58 sql << time_report_joins
Chris@0 59 sql << " WHERE"
Chris@0 60 sql << " (%s) AND" % sql_condition
Chris@0 61 sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)]
Chris@0 62 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek, spent_on"
Chris@0 63
Chris@0 64 @hours = ActiveRecord::Base.connection.select_all(sql)
Chris@0 65
Chris@0 66 @hours.each do |row|
Chris@0 67 case @columns
Chris@0 68 when 'year'
Chris@0 69 row['year'] = row['tyear']
Chris@0 70 when 'month'
Chris@0 71 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
Chris@0 72 when 'week'
Chris@0 73 row['week'] = "#{row['tyear']}-#{row['tweek']}"
Chris@0 74 when 'day'
Chris@0 75 row['day'] = "#{row['spent_on']}"
Chris@0 76 end
Chris@0 77 end
Chris@0 78
Chris@0 79 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
Chris@0 80
Chris@0 81 @periods = []
Chris@0 82 # Date#at_beginning_of_ not supported in Rails 1.2.x
Chris@0 83 date_from = @from.to_time
Chris@0 84 # 100 columns max
Chris@0 85 while date_from <= @to.to_time && @periods.length < 100
Chris@0 86 case @columns
Chris@0 87 when 'year'
Chris@0 88 @periods << "#{date_from.year}"
Chris@0 89 date_from = (date_from + 1.year).at_beginning_of_year
Chris@0 90 when 'month'
Chris@0 91 @periods << "#{date_from.year}-#{date_from.month}"
Chris@0 92 date_from = (date_from + 1.month).at_beginning_of_month
Chris@0 93 when 'week'
Chris@0 94 @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
Chris@0 95 date_from = (date_from + 7.day).at_beginning_of_week
Chris@0 96 when 'day'
Chris@0 97 @periods << "#{date_from.to_date}"
Chris@0 98 date_from = date_from + 1.day
Chris@0 99 end
Chris@0 100 end
Chris@0 101 end
Chris@0 102
Chris@0 103 respond_to do |format|
Chris@0 104 format.html { render :layout => !request.xhr? }
Chris@0 105 format.csv { send_data(report_to_csv(@criterias, @periods, @hours), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
Chris@0 106 end
Chris@0 107 end
Chris@0 108
Chris@0 109 def details
Chris@0 110 sort_init 'spent_on', 'desc'
Chris@0 111 sort_update 'spent_on' => 'spent_on',
Chris@0 112 'user' => 'user_id',
Chris@0 113 'activity' => 'activity_id',
Chris@0 114 'project' => "#{Project.table_name}.name",
Chris@0 115 'issue' => 'issue_id',
Chris@0 116 'hours' => 'hours'
Chris@0 117
Chris@0 118 cond = ARCondition.new
Chris@0 119 if @project.nil?
Chris@0 120 cond << Project.allowed_to_condition(User.current, :view_time_entries)
Chris@0 121 elsif @issue.nil?
Chris@0 122 cond << @project.project_condition(Setting.display_subprojects_issues?)
Chris@0 123 else
Chris@0 124 cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}"
Chris@0 125 end
Chris@0 126
Chris@0 127 retrieve_date_range
Chris@0 128 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
Chris@0 129
Chris@0 130 TimeEntry.visible_by(User.current) do
Chris@0 131 respond_to do |format|
Chris@0 132 format.html {
Chris@0 133 # Paginate results
Chris@0 134 @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions)
Chris@0 135 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
Chris@0 136 @entries = TimeEntry.find(:all,
Chris@0 137 :include => [:project, :activity, :user, {:issue => :tracker}],
Chris@0 138 :conditions => cond.conditions,
Chris@0 139 :order => sort_clause,
Chris@0 140 :limit => @entry_pages.items_per_page,
Chris@0 141 :offset => @entry_pages.current.offset)
Chris@0 142 @total_hours = TimeEntry.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f
Chris@0 143
Chris@0 144 render :layout => !request.xhr?
Chris@0 145 }
Chris@0 146 format.atom {
Chris@0 147 entries = TimeEntry.find(:all,
Chris@0 148 :include => [:project, :activity, :user, {:issue => :tracker}],
Chris@0 149 :conditions => cond.conditions,
Chris@0 150 :order => "#{TimeEntry.table_name}.created_on DESC",
Chris@0 151 :limit => Setting.feeds_limit.to_i)
Chris@0 152 render_feed(entries, :title => l(:label_spent_time))
Chris@0 153 }
Chris@0 154 format.csv {
Chris@0 155 # Export all entries
Chris@0 156 @entries = TimeEntry.find(:all,
Chris@0 157 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
Chris@0 158 :conditions => cond.conditions,
Chris@0 159 :order => sort_clause)
Chris@0 160 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
Chris@0 161 }
Chris@0 162 end
Chris@0 163 end
Chris@0 164 end
Chris@0 165
Chris@0 166 def edit
Chris@0 167 (render_403; return) if @time_entry && !@time_entry.editable_by?(User.current)
Chris@0 168 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
Chris@0 169 @time_entry.attributes = params[:time_entry]
Chris@0 170
Chris@0 171 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
Chris@0 172
Chris@0 173 if request.post? and @time_entry.save
Chris@0 174 flash[:notice] = l(:notice_successful_update)
Chris@0 175 redirect_back_or_default :action => 'details', :project_id => @time_entry.project
Chris@0 176 return
Chris@0 177 end
Chris@0 178 end
Chris@0 179
Chris@0 180 def destroy
Chris@0 181 (render_404; return) unless @time_entry
Chris@0 182 (render_403; return) unless @time_entry.editable_by?(User.current)
Chris@0 183 if @time_entry.destroy && @time_entry.destroyed?
Chris@0 184 flash[:notice] = l(:notice_successful_delete)
Chris@0 185 else
Chris@0 186 flash[:error] = l(:notice_unable_delete_time_entry)
Chris@0 187 end
Chris@0 188 redirect_to :back
Chris@0 189 rescue ::ActionController::RedirectBackError
Chris@0 190 redirect_to :action => 'details', :project_id => @time_entry.project
Chris@0 191 end
Chris@0 192
Chris@0 193 private
Chris@0 194 def find_project
Chris@0 195 if params[:id]
Chris@0 196 @time_entry = TimeEntry.find(params[:id])
Chris@0 197 @project = @time_entry.project
Chris@0 198 elsif params[:issue_id]
Chris@0 199 @issue = Issue.find(params[:issue_id])
Chris@0 200 @project = @issue.project
Chris@0 201 elsif params[:project_id]
Chris@0 202 @project = Project.find(params[:project_id])
Chris@0 203 else
Chris@0 204 render_404
Chris@0 205 return false
Chris@0 206 end
Chris@0 207 rescue ActiveRecord::RecordNotFound
Chris@0 208 render_404
Chris@0 209 end
Chris@0 210
Chris@0 211 def find_optional_project
Chris@0 212 if !params[:issue_id].blank?
Chris@0 213 @issue = Issue.find(params[:issue_id])
Chris@0 214 @project = @issue.project
Chris@0 215 elsif !params[:project_id].blank?
Chris@0 216 @project = Project.find(params[:project_id])
Chris@0 217 end
Chris@0 218 deny_access unless User.current.allowed_to?(:view_time_entries, @project, :global => true)
Chris@0 219 end
Chris@0 220
Chris@0 221 # Retrieves the date range based on predefined ranges or specific from/to param dates
Chris@0 222 def retrieve_date_range
Chris@0 223 @free_period = false
Chris@0 224 @from, @to = nil, nil
Chris@0 225
Chris@0 226 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
Chris@0 227 case params[:period].to_s
Chris@0 228 when 'today'
Chris@0 229 @from = @to = Date.today
Chris@0 230 when 'yesterday'
Chris@0 231 @from = @to = Date.today - 1
Chris@0 232 when 'current_week'
Chris@0 233 @from = Date.today - (Date.today.cwday - 1)%7
Chris@0 234 @to = @from + 6
Chris@0 235 when 'last_week'
Chris@0 236 @from = Date.today - 7 - (Date.today.cwday - 1)%7
Chris@0 237 @to = @from + 6
Chris@0 238 when '7_days'
Chris@0 239 @from = Date.today - 7
Chris@0 240 @to = Date.today
Chris@0 241 when 'current_month'
Chris@0 242 @from = Date.civil(Date.today.year, Date.today.month, 1)
Chris@0 243 @to = (@from >> 1) - 1
Chris@0 244 when 'last_month'
Chris@0 245 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
Chris@0 246 @to = (@from >> 1) - 1
Chris@0 247 when '30_days'
Chris@0 248 @from = Date.today - 30
Chris@0 249 @to = Date.today
Chris@0 250 when 'current_year'
Chris@0 251 @from = Date.civil(Date.today.year, 1, 1)
Chris@0 252 @to = Date.civil(Date.today.year, 12, 31)
Chris@0 253 end
Chris@0 254 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
Chris@0 255 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
Chris@0 256 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
Chris@0 257 @free_period = true
Chris@0 258 else
Chris@0 259 # default
Chris@0 260 end
Chris@0 261
Chris@0 262 @from, @to = @to, @from if @from && @to && @from > @to
chris@22 263 @from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
chris@22 264 @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
Chris@0 265 end
Chris@0 266
Chris@0 267 def load_available_criterias
Chris@0 268 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
Chris@0 269 :klass => Project,
Chris@0 270 :label => :label_project},
Chris@0 271 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
Chris@0 272 :klass => Version,
Chris@0 273 :label => :label_version},
Chris@0 274 'category' => {:sql => "#{Issue.table_name}.category_id",
Chris@0 275 :klass => IssueCategory,
Chris@0 276 :label => :field_category},
Chris@0 277 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
Chris@0 278 :klass => User,
Chris@0 279 :label => :label_member},
Chris@0 280 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
Chris@0 281 :klass => Tracker,
Chris@0 282 :label => :label_tracker},
Chris@0 283 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
Chris@0 284 :klass => TimeEntryActivity,
Chris@0 285 :label => :label_activity},
Chris@0 286 'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
Chris@0 287 :klass => Issue,
Chris@0 288 :label => :label_issue}
Chris@0 289 }
Chris@0 290
Chris@0 291 # Add list and boolean custom fields as available criterias
Chris@0 292 custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
Chris@0 293 custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
Chris@0 294 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id)",
Chris@0 295 :format => cf.field_format,
Chris@0 296 :label => cf.name}
Chris@0 297 end if @project
Chris@0 298
Chris@0 299 # Add list and boolean time entry custom fields
Chris@0 300 TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
Chris@0 301 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id)",
Chris@0 302 :format => cf.field_format,
Chris@0 303 :label => cf.name}
Chris@0 304 end
Chris@0 305
Chris@0 306 # Add list and boolean time entry activity custom fields
Chris@0 307 TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
Chris@0 308 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id)",
Chris@0 309 :format => cf.field_format,
Chris@0 310 :label => cf.name}
Chris@0 311 end
Chris@0 312
Chris@0 313 call_hook(:controller_timelog_available_criterias, { :available_criterias => @available_criterias, :project => @project })
Chris@0 314 @available_criterias
Chris@0 315 end
Chris@14 316
Chris@14 317 def time_report_joins
Chris@14 318 sql = ''
Chris@14 319 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
Chris@14 320 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
Chris@14 321 call_hook(:controller_timelog_time_report_joins, {:sql => sql} )
Chris@14 322 sql
Chris@14 323 end
Chris@0 324 end