Revision 1298:4f746d8966dd lib/redmine/helpers

View differences:

lib/redmine/helpers/calendar.rb
1 1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
2
# Copyright (C) 2006-2013  Jean-Philippe Lang
3 3
#
4 4
# This program is free software; you can redistribute it and/or
5 5
# modify it under the terms of the GNU General Public License
lib/redmine/helpers/diff.rb
1 1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
2
# Copyright (C) 2006-2013  Jean-Philippe Lang
3 3
#
4 4
# This program is free software; you can redistribute it and/or
5 5
# modify it under the terms of the GNU General Public License
lib/redmine/helpers/gantt.rb
1 1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
2
# Copyright (C) 2006-2013  Jean-Philippe Lang
3 3
#
4 4
# This program is free software; you can redistribute it and/or
5 5
# modify it under the terms of the GNU General Public License
......
23 23
      include Redmine::I18n
24 24
      include Redmine::Utils::DateCalculation
25 25

  
26
      # Relation types that are rendered
27
      DRAW_TYPES = {
28
        IssueRelation::TYPE_BLOCKS   => { :landscape_margin => 16, :color => '#F34F4F' },
29
        IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' }
30
      }.freeze
31

  
26 32
      # :nodoc:
27 33
      # Some utility methods for the PDF export
28 34
      class PDF
......
136 142
        )
137 143
      end
138 144

  
145
      # Returns a hash of the relations between the issues that are present on the gantt
146
      # and that should be displayed, grouped by issue ids.
147
      def relations
148
        return @relations if @relations
149
        if issues.any?
150
          issue_ids = issues.map(&:id)
151
          @relations = IssueRelation.
152
            where(:issue_from_id => issue_ids, :issue_to_id => issue_ids, :relation_type => DRAW_TYPES.keys).
153
            group_by(&:issue_from_id)
154
        else
155
          @relations = {}
156
        end
157
      end
158

  
139 159
      # Return all the project nodes that will be displayed
140 160
      def projects
141 161
        return @projects if @projects
......
277 297
            pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
278 298
          end
279 299
        else
280
          ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
281 300
          ''
282 301
        end
283 302
      end
......
289 308
          html_class << 'icon icon-package '
290 309
          html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " "
291 310
          html_class << (version.overdue? ? 'version-overdue' : '')
311
          html_class << ' version-closed' unless version.open?
312
          if version.start_date && version.due_date && version.completed_pourcent
313
            progress_date = calc_progress_date(version.start_date,
314
                                               version.due_date, version.completed_pourcent)
315
            html_class << ' behind-start-date' if progress_date < self.date_from
316
            html_class << ' over-end-date' if progress_date > self.date_to
317
          end
292 318
          s = view.link_to_version(version).html_safe
293 319
          subject = view.content_tag(:span, s,
294 320
                                     :class => html_class).html_safe
295
          html_subject(options, subject, :css => "version-name")
321
          html_subject(options, subject, :css => "version-name",
322
                       :id => "version-#{version.id}")
296 323
        when :image
297 324
          image_subject(options, version.to_s_with_project)
298 325
        when :pdf
......
303 330

  
304 331
      def line_for_version(version, options)
305 332
        # Skip versions that don't have a start_date
306
        if version.is_a?(Version) && version.start_date && version.due_date
333
        if version.is_a?(Version) && version.due_date && version.start_date
307 334
          options[:zoom] ||= 1
308 335
          options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
309 336
          coords = coordinates(version.start_date,
310
                               version.due_date, version.completed_pourcent,
337
                               version.due_date, version.completed_percent,
311 338
                               options[:zoom])
312
          label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%"
339
          label = "#{h version} #{h version.completed_percent.to_i.to_s}%"
313 340
          label = h("#{version.project} -") + label unless @project && @project == version.project
314 341
          case options[:format]
315 342
          when :html
316
            html_task(options, coords, :css => "version task", :label => label, :markers => true)
343
            html_task(options, coords, :css => "version task",
344
                      :label => label, :markers => true, :version => version)
317 345
          when :image
318 346
            image_task(options, coords, :label => label, :markers => true, :height => 3)
319 347
          when :pdf
320 348
            pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
321 349
          end
322 350
        else
323
          ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
324 351
          ''
325 352
        end
326 353
      end
......
336 363
          css_classes << ' issue-overdue' if issue.overdue?
337 364
          css_classes << ' issue-behind-schedule' if issue.behind_schedule?
338 365
          css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
366
          css_classes << ' issue-closed' if issue.closed?
367
          if issue.start_date && issue.due_before && issue.done_ratio
368
            progress_date = calc_progress_date(issue.start_date,
369
                                               issue.due_before, issue.done_ratio)
370
            css_classes << ' behind-start-date' if progress_date < self.date_from
371
            css_classes << ' over-end-date' if progress_date > self.date_to
372
          end
339 373
          s = "".html_safe
340 374
          if issue.assigned_to.present?
341 375
            assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
......
347 381
          s << view.link_to_issue(issue).html_safe
348 382
          subject = view.content_tag(:span, s, :class => css_classes).html_safe
349 383
          html_subject(options, subject, :css => "issue-subject",
350
                       :title => issue.subject) + "\n"
384
                       :title => issue.subject, :id => "issue-#{issue.id}") + "\n"
351 385
        when :image
352 386
          image_subject(options, issue.subject)
353 387
        when :pdf
......
378 412
            pdf_task(options, coords, :label => label)
379 413
        end
380 414
        else
381
          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
382 415
          ''
383 416
        end
384 417
      end
......
611 644
            coords[:bar_end] = self.date_to - self.date_from + 1
612 645
          end
613 646
          if progress
614
            progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
647
            progress_date = calc_progress_date(start_date, end_date, progress)
615 648
            if progress_date > self.date_from && progress_date > start_date
616 649
              if progress_date < self.date_to
617 650
                coords[:bar_progress_end] = progress_date - self.date_from
......
638 671
        coords
639 672
      end
640 673

  
641
      # Sorts a collection of issues by start_date, due_date, id for gantt rendering
674
      def calc_progress_date(start_date, end_date, progress)
675
        start_date + (end_date - start_date + 1) * (progress / 100.0)
676
      end
677

  
678
      # TODO: Sorts a collection of issues by start_date, due_date, id for gantt rendering
642 679
      def sort_issues!(issues)
643 680
        issues.sort! { |a, b| gantt_issue_compare(a, b) }
644 681
      end
......
678 715
      def html_subject(params, subject, options={})
679 716
        style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
680 717
        style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
681
        output = view.content_tag('div', subject,
718
        output = view.content_tag(:div, subject,
682 719
                                  :class => options[:css], :style => style,
683
                                  :title => options[:title])
720
                                  :title => options[:title],
721
                                  :id => options[:id])
684 722
        @subjects << output
685 723
        output
686 724
      end
......
705 743
        params[:image].text(params[:indent], params[:top] + 2, subject)
706 744
      end
707 745

  
746
      def issue_relations(issue)
747
        rels = {}
748
        if relations[issue.id]
749
          relations[issue.id].each do |relation|
750
            (rels[relation.relation_type] ||= []) << relation.issue_to_id
751
          end
752
        end
753
        rels
754
      end
755

  
708 756
      def html_task(params, coords, options={})
709 757
        output = ''
710 758
        # Renders the task bar, with progress and late
......
714 762
          style << "top:#{params[:top]}px;"
715 763
          style << "left:#{coords[:bar_start]}px;"
716 764
          style << "width:#{width}px;"
717
          output << view.content_tag(:div, '&nbsp;'.html_safe,
718
                                     :style => style,
719
                                     :class => "#{options[:css]} task_todo")
765
          html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue]
766
          html_id = "task-todo-version-#{options[:version].id}" if options[:version]
767
          content_opt = {:style => style,
768
                         :class => "#{options[:css]} task_todo",
769
                         :id => html_id}
770
          if options[:issue]
771
            rels = issue_relations(options[:issue])
772
            if rels.present?
773
              content_opt[:data] = {"rels" => rels.to_json}
774
            end
775
          end
776
          output << view.content_tag(:div, '&nbsp;'.html_safe, content_opt)
720 777
          if coords[:bar_late_end]
721 778
            width = coords[:bar_late_end] - coords[:bar_start] - 2
722 779
            style = ""
......
733 790
            style << "top:#{params[:top]}px;"
734 791
            style << "left:#{coords[:bar_start]}px;"
735 792
            style << "width:#{width}px;"
793
            html_id = "task-done-issue-#{options[:issue].id}" if options[:issue]
794
            html_id = "task-done-version-#{options[:version].id}" if options[:version]
736 795
            output << view.content_tag(:div, '&nbsp;'.html_safe,
737 796
                                       :style => style,
738
                                       :class => "#{options[:css]} task_done")
797
                                       :class => "#{options[:css]} task_done",
798
                                       :id => html_id)
739 799
          end
740 800
        end
741 801
        # Renders the markers
lib/redmine/helpers/time_report.rb
1 1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
2
# Copyright (C) 2006-2013  Jean-Philippe Lang
3 3
#
4 4
# This program is free software; you can redistribute it and/or
5 5
# modify it under the terms of the GNU General Public License
......
18 18
module Redmine
19 19
  module Helpers
20 20
    class TimeReport
21
      attr_reader :criteria, :columns, :from, :to, :hours, :total_hours, :periods
21
      attr_reader :criteria, :columns, :hours, :total_hours, :periods
22 22

  
23
      def initialize(project, issue, criteria, columns, from, to)
23
      def initialize(project, issue, criteria, columns, time_entry_scope)
24 24
        @project = project
25 25
        @issue = issue
26 26

  
......
30 30
        @criteria = @criteria[0,3]
31 31

  
32 32
        @columns = (columns && %w(year month week day).include?(columns)) ? columns : 'month'
33
        @from = from
34
        @to = to
33
        @scope = time_entry_scope
35 34

  
36 35
        run
37 36
      end
......
44 43

  
45 44
      def run
46 45
        unless @criteria.empty?
47
          scope = TimeEntry.visible.spent_between(@from, @to)
48
          if @issue
49
            scope = scope.on_issue(@issue)
50
          elsif @project
51
            scope = scope.on_project(@project, Setting.display_subprojects_issues?)
52
          end
53 46
          time_columns = %w(tyear tmonth tweek spent_on)
54 47
          @hours = []
55
          scope.sum(:hours, :include => :issue, :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).each do |hash, hours|
48
          @scope.sum(:hours,
49
              :include => [:issue, :activity],
50
              :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns,
51
              :joins => @criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).each do |hash, hours|
56 52
            h = {'hours' => hours}
57 53
            (@criteria + time_columns).each_with_index do |name, i|
58 54
              h[name] = hash[i]
......
67 63
            when 'month'
68 64
              row['month'] = "#{row['tyear']}-#{row['tmonth']}"
69 65
            when 'week'
70
              row['week'] = "#{row['tyear']}-#{row['tweek']}"
66
              row['week'] = "#{row['spent_on'].cwyear}-#{row['tweek']}"
71 67
            when 'day'
72 68
              row['day'] = "#{row['spent_on']}"
73 69
            end
74 70
          end
75 71
          
76
          if @from.nil?
77
            min = @hours.collect {|row| row['spent_on']}.min
78
            @from = min ? min.to_date : Date.today
79
          end
72
          min = @hours.collect {|row| row['spent_on']}.min
73
          @from = min ? min.to_date : Date.today
80 74

  
81
          if @to.nil?
82
            max = @hours.collect {|row| row['spent_on']}.max
83
            @to = max ? max.to_date : Date.today
84
          end
75
          max = @hours.collect {|row| row['spent_on']}.max
76
          @to = max ? max.to_date : Date.today
85 77
          
86 78
          @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
87 79

  
......
98 90
              @periods << "#{date_from.year}-#{date_from.month}"
99 91
              date_from = (date_from + 1.month).at_beginning_of_month
100 92
            when 'week'
101
              @periods << "#{date_from.year}-#{date_from.to_date.cweek}"
93
              @periods << "#{date_from.to_date.cwyear}-#{date_from.to_date.cweek}"
102 94
              date_from = (date_from + 7.day).at_beginning_of_week
103 95
            when 'day'
104 96
              @periods << "#{date_from.to_date}"
......
121 113
                                 'category' => {:sql => "#{Issue.table_name}.category_id",
122 114
                                                :klass => IssueCategory,
123 115
                                                :label => :field_category},
124
                                 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
116
                                 'user' => {:sql => "#{TimeEntry.table_name}.user_id",
125 117
                                             :klass => User,
126
                                             :label => :label_member},
118
                                             :label => :label_user},
127 119
                                 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
128 120
                                              :klass => Tracker,
129 121
                                              :label => :label_tracker},
......
135 127
                                             :label => :label_issue}
136 128
                               }
137 129

  
130
        # Add time entry custom fields
131
        custom_fields = TimeEntryCustomField.all
132
        # Add project custom fields
133
        custom_fields += ProjectCustomField.all
134
        # Add issue custom fields
135
        custom_fields += (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
136
        # Add time entry activity custom fields
137
        custom_fields += TimeEntryActivityCustomField.all
138

  
138 139
        # Add list and boolean custom fields as available criteria
139
        custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields)
140 140
        custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
141
          @available_criteria["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 ORDER BY c.value LIMIT 1)",
142
                                                 :format => cf.field_format,
143
                                                 :label => cf.name}
144
        end if @project
145

  
146
        # Add list and boolean time entry custom fields
147
        TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
148
          @available_criteria["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 ORDER BY c.value LIMIT 1)",
149
                                                 :format => cf.field_format,
150
                                                 :label => cf.name}
151
        end
152

  
153
        # Add list and boolean time entry activity custom fields
154
        TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
155
          @available_criteria["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 ORDER BY c.value LIMIT 1)",
141
          @available_criteria["cf_#{cf.id}"] = {:sql => "#{cf.join_alias}.value",
142
                                                 :joins => cf.join_for_order_statement,
156 143
                                                 :format => cf.field_format,
157 144
                                                 :label => cf.name}
158 145
        end

Also available in: Unified diff