File: app/controllers/timelog_controller.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  class: TimelogController#18
includes
  CustomFieldsHelper   
  SortHelper   
  TimelogHelper   
inherits from
  ApplicationController   
has properties
method: index #39
method: report #101
method: show #111
method: new #119
method: create #124
method: edit #154
method: update #158
method: bulk_edit #179
method: bulk_update #184
method: destroy #201
method: find_time_entry #230
method: find_time_entries #241
method: set_flash_from_bulk_time_entry_save / 2 #250
method: find_project #261
method: find_optional_project #277
method: retrieve_date_range #287
method: parse_params_for_bulk_time_entry_attributes / 1 #330

Class Hierarchy

Code

   1  # Redmine - project management software
   2  # Copyright (C) 2006-2011  Jean-Philippe Lang
   3  #
   4  # This program is free software; you can redistribute it and/or
   5  # modify it under the terms of the GNU General Public License
   6  # as published by the Free Software Foundation; either version 2
   7  # of the License, or (at your option) any later version.
   8  #
   9  # This program is distributed in the hope that it will be useful,
  10  # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  # GNU General Public License for more details.
  13  #
  14  # You should have received a copy of the GNU General Public License
  15  # along with this program; if not, write to the Free Software
  16  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  17 
  18  class TimelogController < ApplicationController
  19    menu_item :issues
  20 
  21    before_filter :find_project, :only => [:create]
  22    before_filter :find_time_entry, :only => [:show, :edit, :update]
  23    before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
  24    before_filter :authorize, :except => [:new, :index, :report]
  25 
  26    before_filter :find_optional_project, :only => [:new, :index, :report]
  27    before_filter :authorize_global, :only => [:new, :index, :report]
  28 
  29    accept_rss_auth :index
  30    accept_api_auth :index, :show, :create, :update, :destroy
  31 
  32    helper :sort
  33    include SortHelper
  34    helper :issues
  35    include TimelogHelper
  36    helper :custom_fields
  37    include CustomFieldsHelper
  38 
  39    def index
  40      sort_init 'spent_on', 'desc'
  41      sort_update 'spent_on' => 'spent_on',
  42                  'user' => 'user_id',
  43                  'activity' => 'activity_id',
  44                  'project' => "#{Project.table_name}.name",
  45                  'issue' => 'issue_id',
  46                  'hours' => 'hours'
  47 
  48      retrieve_date_range
  49 
  50      scope = TimeEntry.visible.spent_between(@from, @to)
  51      if @issue
  52        scope = scope.on_issue(@issue)
  53      elsif @project
  54        scope = scope.on_project(@project, Setting.display_subprojects_issues?)
  55      end
  56 
  57      respond_to do |format|
  58        format.html {
  59          # Paginate results
  60          @entry_count = scope.count
  61          @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
  62          @entries = scope.all(
  63            :include => [:project, :activity, :user, {:issue => :tracker}],
  64            :order => sort_clause,
  65            :limit  =>  @entry_pages.items_per_page,
  66            :offset =>  @entry_pages.current.offset
  67          )
  68          @total_hours = scope.sum(:hours).to_f
  69 
  70          render :layout => !request.xhr?
  71        }
  72        format.api  {
  73          @entry_count = scope.count
  74          @offset, @limit = api_offset_and_limit
  75          @entries = scope.all(
  76            :include => [:project, :activity, :user, {:issue => :tracker}],
  77            :order => sort_clause,
  78            :limit  => @limit,
  79            :offset => @offset
  80          )
  81        }
  82        format.atom {
  83          entries = scope.all(
  84            :include => [:project, :activity, :user, {:issue => :tracker}],
  85            :order => "#{TimeEntry.table_name}.created_on DESC",
  86            :limit => Setting.feeds_limit.to_i
  87          )
  88          render_feed(entries, :title => l(:label_spent_time))
  89        }
  90        format.csv {
  91          # Export all entries
  92          @entries = scope.all(
  93            :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
  94            :order => sort_clause
  95          )
  96          send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
  97        }
  98      end
  99    end
 100 
 101    def report
 102      retrieve_date_range
 103      @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to)
 104 
 105      respond_to do |format|
 106        format.html { render :layout => !request.xhr? }
 107        format.csv  { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
 108      end
 109    end
 110 
 111    def show
 112      respond_to do |format|
 113        # TODO: Implement html response
 114        format.html { render :nothing => true, :status => 406 }
 115        format.api
 116      end
 117    end
 118 
 119    def new
 120      @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
 121      @time_entry.safe_attributes = params[:time_entry]
 122    end
 123 
 124    def create
 125      @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
 126      @time_entry.safe_attributes = params[:time_entry]
 127 
 128      call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
 129 
 130      if @time_entry.save
 131        respond_to do |format|
 132          format.html {
 133            flash[:notice] = l(:notice_successful_create)
 134            if params[:continue]
 135              if params[:project_id]
 136                redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue, :back_url => params[:back_url]
 137              else
 138                redirect_to :action => 'new', :back_url => params[:back_url]
 139              end
 140            else
 141              redirect_back_or_default :action => 'index', :project_id => @time_entry.project
 142            end
 143          }
 144          format.api  { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
 145        end
 146      else
 147        respond_to do |format|
 148          format.html { render :action => 'new' }
 149          format.api  { render_validation_errors(@time_entry) }
 150        end
 151      end
 152    end
 153 
 154    def edit
 155      @time_entry.safe_attributes = params[:time_entry]
 156    end
 157 
 158    def update
 159      @time_entry.safe_attributes = params[:time_entry]
 160 
 161      call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
 162 
 163      if @time_entry.save
 164        respond_to do |format|
 165          format.html {
 166            flash[:notice] = l(:notice_successful_update)
 167            redirect_back_or_default :action => 'index', :project_id => @time_entry.project
 168          }
 169          format.api  { head :ok }
 170        end
 171      else
 172        respond_to do |format|
 173          format.html { render :action => 'edit' }
 174          format.api  { render_validation_errors(@time_entry) }
 175        end
 176      end
 177    end
 178 
 179    def bulk_edit
 180      @available_activities = TimeEntryActivity.shared.active
 181      @custom_fields = TimeEntry.first.available_custom_fields
 182    end
 183 
 184    def bulk_update
 185      attributes = parse_params_for_bulk_time_entry_attributes(params)
 186 
 187      unsaved_time_entry_ids = []
 188      @time_entries.each do |time_entry|
 189        time_entry.reload
 190        time_entry.safe_attributes = attributes
 191        call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
 192        unless time_entry.save
 193          # Keep unsaved time_entry ids to display them in flash error
 194          unsaved_time_entry_ids << time_entry.id
 195        end
 196      end
 197      set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
 198      redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first})
 199    end
 200 
 201    def destroy
 202      destroyed = TimeEntry.transaction do
 203        @time_entries.each do |t|
 204          unless t.destroy && t.destroyed?
 205            raise ActiveRecord::Rollback
 206          end
 207        end
 208      end
 209 
 210      respond_to do |format|
 211        format.html {
 212          if destroyed
 213            flash[:notice] = l(:notice_successful_delete)
 214          else
 215            flash[:error] = l(:notice_unable_delete_time_entry)
 216          end
 217          redirect_back_or_default(:action => 'index', :project_id => @projects.first)
 218        }
 219        format.api  {
 220          if destroyed
 221            head :ok
 222          else
 223            render_validation_errors(@time_entries)
 224          end
 225        }
 226      end
 227    end
 228 
 229  private
 230    def find_time_entry
 231      @time_entry = TimeEntry.find(params[:id])
 232      unless @time_entry.editable_by?(User.current)
 233        render_403
 234        return false
 235      end
 236      @project = @time_entry.project
 237    rescue ActiveRecord::RecordNotFound
 238      render_404
 239    end
 240 
 241    def find_time_entries
 242      @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
 243      raise ActiveRecord::RecordNotFound if @time_entries.empty?
 244      @projects = @time_entries.collect(&:project).compact.uniq
 245      @project = @projects.first if @projects.size == 1
 246    rescue ActiveRecord::RecordNotFound
 247      render_404
 248    end
 249 
 250    def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
 251      if unsaved_time_entry_ids.empty?
 252        flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
 253      else
 254        flash[:error] = l(:notice_failed_to_save_time_entries,
 255                          :count => unsaved_time_entry_ids.size,
 256                          :total => time_entries.size,
 257                          :ids => '#' + unsaved_time_entry_ids.join(', #'))
 258      end
 259    end
 260 
 261    def find_project
 262      if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
 263        @project = Project.find(project_id)
 264      end
 265      if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
 266        @issue = Issue.find(issue_id)
 267        @project ||= @issue.project
 268      end
 269      if @project.nil?
 270        render_404
 271        return false
 272      end
 273    rescue ActiveRecord::RecordNotFound
 274      render_404
 275    end
 276 
 277    def find_optional_project
 278      if !params[:issue_id].blank?
 279        @issue = Issue.find(params[:issue_id])
 280        @project = @issue.project
 281      elsif !params[:project_id].blank?
 282        @project = Project.find(params[:project_id])
 283      end
 284    end
 285 
 286    # Retrieves the date range based on predefined ranges or specific from/to param dates
 287    def retrieve_date_range
 288      @free_period = false
 289      @from, @to = nil, nil
 290 
 291      if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
 292        case params[:period].to_s
 293        when 'today'
 294          @from = @to = Date.today
 295        when 'yesterday'
 296          @from = @to = Date.today - 1
 297        when 'current_week'
 298          @from = Date.today - (Date.today.cwday - 1)%7
 299          @to = @from + 6
 300        when 'last_week'
 301          @from = Date.today - 7 - (Date.today.cwday - 1)%7
 302          @to = @from + 6
 303        when '7_days'
 304          @from = Date.today - 7
 305          @to = Date.today
 306        when 'current_month'
 307          @from = Date.civil(Date.today.year, Date.today.month, 1)
 308          @to = (@from >> 1) - 1
 309        when 'last_month'
 310          @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
 311          @to = (@from >> 1) - 1
 312        when '30_days'
 313          @from = Date.today - 30
 314          @to = Date.today
 315        when 'current_year'
 316          @from = Date.civil(Date.today.year, 1, 1)
 317          @to = Date.civil(Date.today.year, 12, 31)
 318        end
 319      elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
 320        begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
 321        begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
 322        @free_period = true
 323      else
 324        # default
 325      end
 326 
 327      @from, @to = @to, @from if @from && @to && @from > @to
 328    end
 329 
 330    def parse_params_for_bulk_time_entry_attributes(params)
 331      attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
 332      attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
 333      attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
 334      attributes
 335    end
 336  end