##// END OF EJS Templates
Added issue subject to the time entries view and subject + tracker to the csv export (#616). Default order on date column set to desc....
Jean-Philippe Lang -
r1128:4dc7f662e3d9
parent child
Show More
@@ -1,173 +1,177
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 layout 'base'
19 layout 'base'
20 menu_item :issues
20 menu_item :issues
21 before_filter :find_project, :authorize
21 before_filter :find_project, :authorize
22
22
23 helper :sort
23 helper :sort
24 include SortHelper
24 include SortHelper
25 helper :issues
25 helper :issues
26
26
27 def report
27 def report
28 @available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
28 @available_criterias = { 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
29 :values => @project.versions,
29 :values => @project.versions,
30 :label => :label_version},
30 :label => :label_version},
31 'category' => {:sql => "#{Issue.table_name}.category_id",
31 'category' => {:sql => "#{Issue.table_name}.category_id",
32 :values => @project.issue_categories,
32 :values => @project.issue_categories,
33 :label => :field_category},
33 :label => :field_category},
34 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
34 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
35 :values => @project.users,
35 :values => @project.users,
36 :label => :label_member},
36 :label => :label_member},
37 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
37 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
38 :values => Tracker.find(:all),
38 :values => Tracker.find(:all),
39 :label => :label_tracker},
39 :label => :label_tracker},
40 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
40 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
41 :values => Enumeration::get_values('ACTI'),
41 :values => Enumeration::get_values('ACTI'),
42 :label => :label_activity}
42 :label => :label_activity}
43 }
43 }
44
44
45 @criterias = params[:criterias] || []
45 @criterias = params[:criterias] || []
46 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
46 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
47 @criterias.uniq!
47 @criterias.uniq!
48
48
49 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
49 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
50
50
51 if params[:date_from]
51 if params[:date_from]
52 begin; @date_from = params[:date_from].to_date; rescue; end
52 begin; @date_from = params[:date_from].to_date; rescue; end
53 end
53 end
54 if params[:date_to]
54 if params[:date_to]
55 begin; @date_to = params[:date_to].to_date; rescue; end
55 begin; @date_to = params[:date_to].to_date; rescue; end
56 end
56 end
57 @date_from ||= Date.civil(Date.today.year, 1, 1)
57 @date_from ||= Date.civil(Date.today.year, 1, 1)
58 @date_to ||= (Date.civil(Date.today.year, Date.today.month, 1) >> 1) - 1
58 @date_to ||= (Date.civil(Date.today.year, Date.today.month, 1) >> 1) - 1
59
59
60 unless @criterias.empty?
60 unless @criterias.empty?
61 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
61 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
62 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
62 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
63
63
64 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
64 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
65 sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
65 sql << " FROM #{TimeEntry.table_name} LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
66 sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
66 sql << " WHERE #{TimeEntry.table_name}.project_id = %s" % @project.id
67 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
67 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
68 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
68 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
69
69
70 @hours = ActiveRecord::Base.connection.select_all(sql)
70 @hours = ActiveRecord::Base.connection.select_all(sql)
71
71
72 @hours.each do |row|
72 @hours.each do |row|
73 case @columns
73 case @columns
74 when 'year'
74 when 'year'
75 row['year'] = row['tyear']
75 row['year'] = row['tyear']
76 when 'month'
76 when 'month'
77 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
77 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
78 when 'week'
78 when 'week'
79 row['week'] = "#{row['tyear']}-#{row['tweek']}"
79 row['week'] = "#{row['tyear']}-#{row['tweek']}"
80 end
80 end
81 end
81 end
82 end
82 end
83
83
84 @periods = []
84 @periods = []
85 date_from = @date_from
85 date_from = @date_from
86 # 100 columns max
86 # 100 columns max
87 while date_from < @date_to && @periods.length < 100
87 while date_from < @date_to && @periods.length < 100
88 case @columns
88 case @columns
89 when 'year'
89 when 'year'
90 @periods << "#{date_from.year}"
90 @periods << "#{date_from.year}"
91 date_from = date_from >> 12
91 date_from = date_from >> 12
92 when 'month'
92 when 'month'
93 @periods << "#{date_from.year}-#{date_from.month}"
93 @periods << "#{date_from.year}-#{date_from.month}"
94 date_from = date_from >> 1
94 date_from = date_from >> 1
95 when 'week'
95 when 'week'
96 @periods << "#{date_from.year}-#{date_from.cweek}"
96 @periods << "#{date_from.year}-#{date_from.cweek}"
97 date_from = date_from + 7
97 date_from = date_from + 7
98 end
98 end
99 end
99 end
100
100
101 render :layout => false if request.xhr?
101 render :layout => false if request.xhr?
102 end
102 end
103
103
104 def details
104 def details
105 sort_init 'spent_on', 'desc'
105 sort_init 'spent_on', 'desc'
106 sort_update
106 sort_update
107
107
108 @entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
108 @entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause)
109
109
110 @total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
110 @total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours }
111 @owner_id = User.current.id
111 @owner_id = User.current.id
112
112
113 send_csv and return if 'csv' == params[:export]
113 send_csv and return if 'csv' == params[:export]
114 render :action => 'details', :layout => false if request.xhr?
114 render :action => 'details', :layout => false if request.xhr?
115 end
115 end
116
116
117 def edit
117 def edit
118 render_404 and return if @time_entry && @time_entry.user != User.current
118 render_404 and return if @time_entry && @time_entry.user != User.current
119 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
119 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
120 @time_entry.attributes = params[:time_entry]
120 @time_entry.attributes = params[:time_entry]
121 if request.post? and @time_entry.save
121 if request.post? and @time_entry.save
122 flash[:notice] = l(:notice_successful_update)
122 flash[:notice] = l(:notice_successful_update)
123 redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
123 redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue
124 return
124 return
125 end
125 end
126 @activities = Enumeration::get_values('ACTI')
126 @activities = Enumeration::get_values('ACTI')
127 end
127 end
128
128
129 private
129 private
130 def find_project
130 def find_project
131 if params[:id]
131 if params[:id]
132 @time_entry = TimeEntry.find(params[:id])
132 @time_entry = TimeEntry.find(params[:id])
133 @project = @time_entry.project
133 @project = @time_entry.project
134 elsif params[:issue_id]
134 elsif params[:issue_id]
135 @issue = Issue.find(params[:issue_id])
135 @issue = Issue.find(params[:issue_id])
136 @project = @issue.project
136 @project = @issue.project
137 elsif params[:project_id]
137 elsif params[:project_id]
138 @project = Project.find(params[:project_id])
138 @project = Project.find(params[:project_id])
139 else
139 else
140 render_404
140 render_404
141 return false
141 return false
142 end
142 end
143 end
143 end
144
144
145 def send_csv
145 def send_csv
146 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
146 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
147 export = StringIO.new
147 export = StringIO.new
148 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
148 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
149 # csv header fields
149 # csv header fields
150 headers = [l(:field_spent_on),
150 headers = [l(:field_spent_on),
151 l(:field_user),
151 l(:field_user),
152 l(:field_activity),
152 l(:field_activity),
153 l(:field_issue),
153 l(:field_issue),
154 l(:field_tracker),
155 l(:field_subject),
154 l(:field_hours),
156 l(:field_hours),
155 l(:field_comments)
157 l(:field_comments)
156 ]
158 ]
157 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
159 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
158 # csv lines
160 # csv lines
159 @entries.each do |entry|
161 @entries.each do |entry|
160 fields = [l_date(entry.spent_on),
162 fields = [l_date(entry.spent_on),
161 entry.user.name,
163 entry.user,
162 entry.activity.name,
164 entry.activity,
163 (entry.issue ? entry.issue.id : nil),
165 (entry.issue ? entry.issue.id : nil),
166 (entry.issue ? entry.issue.tracker : nil),
167 (entry.issue ? entry.issue.subject : nil),
164 entry.hours,
168 entry.hours,
165 entry.comments
169 entry.comments
166 ]
170 ]
167 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
171 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
168 end
172 end
169 end
173 end
170 export.rewind
174 export.rewind
171 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
175 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
172 end
176 end
173 end
177 end
@@ -1,51 +1,51
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
2 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>
3 </div>
3 </div>
4
4
5 <h2><%= l(:label_spent_time) %></h2>
5 <h2><%= l(:label_spent_time) %></h2>
6
6
7 <h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) if @project %>
7 <h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) if @project %>
8 <%= "/ " + link_to_issue(@issue) + h(": #{@issue.subject}") if @issue %></h3>
8 <%= "/ " + link_to_issue(@issue) + h(": #{@issue.subject}") if @issue %></h3>
9
9
10 <h3 class="textright"><%= l(:label_total) %>: <%= lwr(:label_f_hour, @total_hours) %></h3>
10 <h3 class="textright"><%= l(:label_total) %>: <%= lwr(:label_f_hour, @total_hours) %></h3>
11
11
12 <% unless @entries.empty? %>
12 <% unless @entries.empty? %>
13 <table class="list">
13 <table class="list">
14 <thead>
14 <thead>
15 <%= sort_header_tag('spent_on', :caption => l(:label_date)) %>
15 <%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %>
16 <%= sort_header_tag('user_id', :caption => l(:label_member)) %>
16 <%= sort_header_tag('user_id', :caption => l(:label_member)) %>
17 <%= sort_header_tag('activity_id', :caption => l(:label_activity)) %>
17 <%= sort_header_tag('activity_id', :caption => l(:label_activity)) %>
18 <%= sort_header_tag('issue_id', :caption => l(:label_issue)) %>
18 <%= sort_header_tag('issue_id', :caption => l(:label_issue)) %>
19 <th><%= l(:field_comments) %></th>
19 <th><%= l(:field_comments) %></th>
20 <%= sort_header_tag('hours', :caption => l(:field_hours)) %>
20 <%= sort_header_tag('hours', :caption => l(:field_hours)) %>
21 <th></th>
21 <th></th>
22 </thead>
22 </thead>
23 <tbody>
23 <tbody>
24 <% @entries.each do |entry| %>
24 <% @entries.each do |entry| %>
25 <tr class="<%= cycle("odd", "even") %>">
25 <tr class="<%= cycle("odd", "even") %>">
26 <td align="center"><%= format_date(entry.spent_on) %></td>
26 <td align="center"><%= format_date(entry.spent_on) %></td>
27 <td align="center"><%= entry.user.name %></td>
27 <td align="center"><%= entry.user.name %></td>
28 <td align="center"><%= entry.activity.name %></td>
28 <td align="center"><%= entry.activity.name %></td>
29 <td align="center">
29 <td>
30 <% if entry.issue %>
30 <% if entry.issue %>
31 <div class="tooltip">
31 <div class="tooltip">
32 <%= link_to_issue entry.issue %>
32 <%= link_to_issue entry.issue %>: <%= h(truncate(entry.issue.subject, 50)) %>
33 <span class="tip">
33 <span class="tip">
34 <%= render_issue_tooltip entry.issue %>
34 <%= render_issue_tooltip entry.issue %>
35 </span>
35 </span>
36 </div>
36 </div>
37 <% end %>
37 <% end %>
38 </td>
38 </td>
39 <td><%=h entry.comments %></td>
39 <td><%=h entry.comments %></td>
40 <td align="center"><strong><%= entry.hours %></strong></td>
40 <td align="center"><strong><%= entry.hours %></strong></td>
41 <td align="center"><%= link_to_if_authorized(l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => entry}, :class => "icon icon-edit") if entry.user_id == @owner_id %></td>
41 <td align="center"><%= link_to_if_authorized(l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => entry}, :class => "icon icon-edit") if entry.user_id == @owner_id %></td>
42 </tr>
42 </tr>
43 <% end %>
43 <% end %>
44 </tbdoy>
44 </tbdoy>
45 </table>
45 </table>
46
46
47 <div class="contextual">
47 <div class="contextual">
48 <%= l(:label_export_to) %>
48 <%= l(:label_export_to) %>
49 <%= link_to 'CSV', params.update(:export => 'csv'), :class => 'icon icon-csv' %>
49 <%= link_to 'CSV', params.update(:export => 'csv'), :class => 'icon icon-csv' %>
50 </div>
50 </div>
51 <% end %> No newline at end of file
51 <% end %>
General Comments 0
You need to be logged in to leave comments. Login now