Project

General

Profile

Feature #1692 ยป time_report.rb

File with code at note-6 - Krzysztof Irzyk, 2020-12-18 00:39

 
1
# frozen_string_literal: true
2

    
3
# Redmine - project management software
4
# Copyright (C) 2006-2020  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

    
20
module Redmine
21
  module Helpers
22
    class TimeReport
23
      attr_reader :criteria, :columns, :hours, :total_hours, :periods
24

    
25
      def initialize(project, issue, criteria, columns, time_entry_scope)
26
        @project = project
27
        @issue = issue
28

    
29
        @criteria = criteria || []
30
        @criteria = @criteria.select{|criteria| available_criteria.has_key? criteria}
31
        @criteria.uniq!
32
        @criteria = @criteria[0,3]
33

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

    
37
        run
38
      end
39

    
40
      def available_criteria
41
        @available_criteria || load_available_criteria
42
      end
43

    
44
      private
45

    
46
      def run
47
        unless @criteria.empty?
48
          time_columns = %w(tyear tmonth tweek spent_on)
49
          @hours = []
50
          @scope.includes(:activity).
51
              reorder(nil).
52
              group(@criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).
53
              joins(@criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).
54
              sum(:hours).each do |hash, hours|
55
            h = {'hours' => hours}
56
            (@criteria + time_columns).each_with_index do |name, i|
57
              h[name] = hash[i]
58
            end
59
            @hours << h
60
          end
61

    
62
          @hours.each do |row|
63
            case @columns
64
            when 'year'
65
              row['year'] = row['tyear']
66
            when 'month'
67
              row['month'] = "#{row['tyear']}-#{row['tmonth']}"
68
            when 'week'
69
              row['week'] = "#{row['spent_on'].cwyear}-#{row['tweek']}"
70
            when 'day'
71
              row['day'] = "#{row['spent_on']}"
72
            end
73
          end
74

    
75
          min = @hours.collect {|row| row['spent_on']}.min
76
          @from = min ? min.to_date : User.current.today
77

    
78
          max = @hours.collect {|row| row['spent_on']}.max
79
          @to = max ? max.to_date : User.current.today
80

    
81
          @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
82

    
83
          @periods = []
84
          # Date#at_beginning_of_ not supported in Rails 1.2.x
85
          date_from = @from.to_time
86
          # 100 columns max
87
          while date_from <= @to.to_time && @periods.length < 100
88
            case @columns
89
            when 'year'
90
              @periods << "#{date_from.year}"
91
              date_from = (date_from + 1.year).at_beginning_of_year
92
            when 'month'
93
              @periods << "#{date_from.year}-#{date_from.month}"
94
              date_from = (date_from + 1.month).at_beginning_of_month
95
            when 'week'
96
              @periods << "#{date_from.to_date.cwyear}-#{date_from.to_date.cweek}"
97
              date_from = (date_from + 7.day).at_beginning_of_week
98
            when 'day'
99
              @periods << "#{date_from.to_date}"
100
              date_from = date_from + 1.day
101
            end
102
          end
103
        end
104
      end
105

    
106
      def load_available_criteria
107
        @available_criteria = {
108
          'project' => {:sql => "#{TimeEntry.table_name}.project_id",
109
                        :klass => Project,
110
                        :label => :label_project},
111
          'status' => {:sql => "#{Issue.table_name}.status_id",
112
                       :klass => IssueStatus,
113
                       :label => :field_status},
114
          'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
115
                        :klass => ::Version,
116
                        :label => :label_version},
117
          'category' => {:sql => "#{Issue.table_name}.category_id",
118
                         :klass => IssueCategory,
119
                         :label => :field_category},
120
          'user' => {:sql => "#{TimeEntry.table_name}.user_id",
121
                     :klass => User,
122
                     :label => :label_user},
123
          'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
124
                        :klass => Tracker,
125
                        :label => :label_tracker},
126
          'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
127
                         :klass => TimeEntryActivity,
128
                         :label => :field_activity},
129
          'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
130
                      :klass => Issue,
131
                      :label => :label_issue}
132
        }
133

    
134
        # Add time entry custom fields
135
        custom_fields = TimeEntryCustomField.visible
136
        # Add project custom fields
137
        custom_fields += ProjectCustomField.visible
138
        # Add issue custom fields
139
        custom_fields += @project.nil? ? IssueCustomField.visible.for_all : @project.all_issue_custom_fields.visible
140
        # Add time entry activity custom fields
141
        custom_fields += TimeEntryActivityCustomField.visible
142
		# Add string custom fields as available criteria - https://www.redmine.org/issues/1692#note-6
143
        custom_fields.select {|cf| %w(text) }.each do |cf|
144
          @available_criteria["cf_#{cf.id}"] = {:sql => cf.order_statement,
145
                                                   :joins => cf.join_for_order_statement,
146
                                                   :format => cf.field_format,
147
                                                 :custom_field => cf,
148
                                                 :label => cf.name}
149
        end
150
        # Add list and boolean custom fields as available criteria
151
        custom_fields.select {|cf| %w(list bool).include?(cf.field_format) && !cf.multiple?}.each do |cf|
152
          @available_criteria["cf_#{cf.id}"] = {:sql => cf.group_statement,
153
                                                 :joins => cf.join_for_order_statement,
154
                                                 :format => cf.field_format,
155
                                                 :custom_field => cf,
156
                                                 :label => cf.name}
157
        end
158

    
159
        @available_criteria
160
      end
161
    end
162
  end
163
end
    (1-1/1)