timesheet_plugin 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYRIGHT.txt +16 -0
- data/CREDITS.txt +21 -0
- data/GPL.txt +339 -0
- data/README.rdoc +56 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/app/controllers/timesheet_controller.rb +145 -0
- data/app/helpers/timesheet_helper.rb +42 -0
- data/app/models/timesheet.rb +277 -0
- data/app/views/settings/_timesheet_settings.rhtml +4 -0
- data/app/views/timesheet/_by_issue.rhtml +35 -0
- data/app/views/timesheet/_form.rhtml +90 -0
- data/app/views/timesheet/_issue_time_entries.rhtml +43 -0
- data/app/views/timesheet/_time_entry.rhtml +31 -0
- data/app/views/timesheet/_timesheet_group.rhtml +21 -0
- data/app/views/timesheet/context_menu.html.erb +4 -0
- data/app/views/timesheet/index.rhtml +11 -0
- data/app/views/timesheet/no_projects.rhtml +5 -0
- data/app/views/timesheet/report.rhtml +52 -0
- data/app/views/timesheet/timelog.rhtml +5 -0
- data/assets/images/toggle-arrow-closed.gif +0 -0
- data/assets/images/toggle-arrow-open.gif +0 -0
- data/config/locales/ca.yml +9 -0
- data/config/locales/cs.yml +7 -0
- data/config/locales/da.yml +10 -0
- data/config/locales/de.yml +10 -0
- data/config/locales/en.yml +10 -0
- data/config/locales/es.yml +9 -0
- data/config/locales/fr.yml +10 -0
- data/config/locales/hu.yml +10 -0
- data/config/locales/it.yml +7 -0
- data/config/locales/lt.yml +9 -0
- data/config/locales/pl.yml +10 -0
- data/config/locales/ru.yml +9 -0
- data/init.rb +2 -0
- data/lang/ca.yml +8 -0
- data/lang/cs.yml +6 -0
- data/lang/da.yml +9 -0
- data/lang/de.yml +9 -0
- data/lang/en.yml +9 -0
- data/lang/es.yml +8 -0
- data/lang/fr.yml +9 -0
- data/lang/hu.yml +9 -0
- data/lang/it.yml +6 -0
- data/lang/lt.yml +8 -0
- data/lang/pl.yml +9 -0
- data/lang/ru.yml +8 -0
- data/lib/tasks/plugin_stat.rake +38 -0
- data/lib/timesheet_compatibility.rb +14 -0
- data/rails/init.rb +29 -0
- data/spec/controllers/timesheet_controller_spec.rb +263 -0
- data/spec/models/timesheet_spec.rb +537 -0
- data/spec/sanity_spec.rb +7 -0
- data/spec/spec_helper.rb +40 -0
- metadata +107 -0
@@ -0,0 +1,277 @@
|
|
1
|
+
class Timesheet
|
2
|
+
attr_accessor :date_from, :date_to, :projects, :activities, :users, :allowed_projects, :period, :period_type
|
3
|
+
|
4
|
+
# Time entries on the Timesheet in the form of:
|
5
|
+
# project.name => {:logs => [time entries], :users => [users shown in logs] }
|
6
|
+
# project.name => {:logs => [time entries], :users => [users shown in logs] }
|
7
|
+
# project.name could be the parent project name also
|
8
|
+
attr_accessor :time_entries
|
9
|
+
|
10
|
+
# Array of TimeEntry ids to fetch
|
11
|
+
attr_accessor :potential_time_entry_ids
|
12
|
+
|
13
|
+
# Sort time entries by this field
|
14
|
+
attr_accessor :sort
|
15
|
+
ValidSortOptions = {
|
16
|
+
:project => 'Project',
|
17
|
+
:user => 'User',
|
18
|
+
:issue => 'Issue'
|
19
|
+
}
|
20
|
+
|
21
|
+
ValidPeriodType = {
|
22
|
+
:free_period => 0,
|
23
|
+
:default => 1
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize(options = { })
|
27
|
+
self.projects = [ ]
|
28
|
+
self.time_entries = options[:time_entries] || { }
|
29
|
+
self.potential_time_entry_ids = options[:potential_time_entry_ids] || [ ]
|
30
|
+
self.allowed_projects = options[:allowed_projects] || [ ]
|
31
|
+
|
32
|
+
unless options[:activities].nil?
|
33
|
+
self.activities = options[:activities].collect { |a| a.to_i }
|
34
|
+
else
|
35
|
+
self.activities = TimesheetCompatibility::Enumeration::activities.collect { |a| a.id.to_i }
|
36
|
+
end
|
37
|
+
|
38
|
+
unless options[:users].nil?
|
39
|
+
self.users = options[:users].collect { |u| u.to_i }
|
40
|
+
else
|
41
|
+
self.users = User.find(:all).collect(&:id)
|
42
|
+
end
|
43
|
+
|
44
|
+
if !options[:sort].nil? && options[:sort].respond_to?(:to_sym) && ValidSortOptions.keys.include?(options[:sort].to_sym)
|
45
|
+
self.sort = options[:sort].to_sym
|
46
|
+
else
|
47
|
+
self.sort = :project
|
48
|
+
end
|
49
|
+
|
50
|
+
self.date_from = options[:date_from] || Date.today.to_s
|
51
|
+
self.date_to = options[:date_to] || Date.today.to_s
|
52
|
+
|
53
|
+
if options[:period_type] && ValidPeriodType.values.include?(options[:period_type].to_i)
|
54
|
+
self.period_type = options[:period_type].to_i
|
55
|
+
else
|
56
|
+
self.period_type = ValidPeriodType[:free_period]
|
57
|
+
end
|
58
|
+
self.period = options[:period] || nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# Gets all the time_entries for all the projects
|
62
|
+
def fetch_time_entries
|
63
|
+
self.time_entries = { }
|
64
|
+
case self.sort
|
65
|
+
when :project
|
66
|
+
fetch_time_entries_by_project
|
67
|
+
when :user
|
68
|
+
fetch_time_entries_by_user
|
69
|
+
when :issue
|
70
|
+
fetch_time_entries_by_issue
|
71
|
+
else
|
72
|
+
fetch_time_entries_by_project
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def period=(period)
|
77
|
+
return if self.period_type == Timesheet::ValidPeriodType[:free_period]
|
78
|
+
# Stolen from the TimelogController
|
79
|
+
case period.to_s
|
80
|
+
when 'today'
|
81
|
+
self.date_from = self.date_to = Date.today
|
82
|
+
when 'yesterday'
|
83
|
+
self.date_from = self.date_to = Date.today - 1
|
84
|
+
when 'current_week' # Mon -> Sun
|
85
|
+
self.date_from = Date.today - (Date.today.cwday - 1)%7
|
86
|
+
self.date_to = self.date_from + 6
|
87
|
+
when 'last_week'
|
88
|
+
self.date_from = Date.today - 7 - (Date.today.cwday - 1)%7
|
89
|
+
self.date_to = self.date_from + 6
|
90
|
+
when '7_days'
|
91
|
+
self.date_from = Date.today - 7
|
92
|
+
self.date_to = Date.today
|
93
|
+
when 'current_month'
|
94
|
+
self.date_from = Date.civil(Date.today.year, Date.today.month, 1)
|
95
|
+
self.date_to = (self.date_from >> 1) - 1
|
96
|
+
when 'last_month'
|
97
|
+
self.date_from = Date.civil(Date.today.year, Date.today.month, 1) << 1
|
98
|
+
self.date_to = (self.date_from >> 1) - 1
|
99
|
+
when '30_days'
|
100
|
+
self.date_from = Date.today - 30
|
101
|
+
self.date_to = Date.today
|
102
|
+
when 'current_year'
|
103
|
+
self.date_from = Date.civil(Date.today.year, 1, 1)
|
104
|
+
self.date_to = Date.civil(Date.today.year, 12, 31)
|
105
|
+
when 'all'
|
106
|
+
self.date_from = self.date_to = nil
|
107
|
+
end
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
def conditions(users)
|
114
|
+
if self.potential_time_entry_ids.empty?
|
115
|
+
if self.date_from && self.date_to
|
116
|
+
conditions = ["spent_on >= (?) AND spent_on <= (?) AND #{TimeEntry.table_name}.project_id IN (?) AND activity_id IN (?) AND user_id IN (?)",
|
117
|
+
self.date_from, self.date_to, self.projects, self.activities, users ]
|
118
|
+
else # All time
|
119
|
+
conditions = ["#{TimeEntry.table_name}.project_id IN (?) AND activity_id IN (?) AND user_id IN (?)",
|
120
|
+
self.projects, self.activities, users ]
|
121
|
+
end
|
122
|
+
else
|
123
|
+
conditions = ["user_id IN (?) AND #{TimeEntry.table_name}.id IN (?)",
|
124
|
+
users, self.potential_time_entry_ids ]
|
125
|
+
end
|
126
|
+
|
127
|
+
Redmine::Hook.call_hook(:plugin_timesheet_model_timesheet_conditions, { :timesheet => self, :conditions => conditions})
|
128
|
+
return conditions
|
129
|
+
end
|
130
|
+
|
131
|
+
def includes
|
132
|
+
includes = [:activity, :user, :project, {:issue => [:tracker, :assigned_to, :priority]}]
|
133
|
+
Redmine::Hook.call_hook(:plugin_timesheet_model_timesheet_includes, { :timesheet => self, :includes => includes})
|
134
|
+
return includes
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
|
140
|
+
def time_entries_for_all_users(project)
|
141
|
+
return project.time_entries.find(:all,
|
142
|
+
:conditions => self.conditions(self.users),
|
143
|
+
:include => self.includes,
|
144
|
+
:order => "spent_on ASC")
|
145
|
+
end
|
146
|
+
|
147
|
+
def time_entries_for_current_user(project)
|
148
|
+
return project.time_entries.find(:all,
|
149
|
+
:conditions => self.conditions(User.current.id),
|
150
|
+
:include => self.includes,
|
151
|
+
:include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
|
152
|
+
:order => "spent_on ASC")
|
153
|
+
end
|
154
|
+
|
155
|
+
def issue_time_entries_for_all_users(issue)
|
156
|
+
return issue.time_entries.find(:all,
|
157
|
+
:conditions => self.conditions(self.users),
|
158
|
+
:include => self.includes,
|
159
|
+
:include => [:activity, :user],
|
160
|
+
:order => "spent_on ASC")
|
161
|
+
end
|
162
|
+
|
163
|
+
def issue_time_entries_for_current_user(issue)
|
164
|
+
return issue.time_entries.find(:all,
|
165
|
+
:conditions => self.conditions(User.current.id),
|
166
|
+
:include => self.includes,
|
167
|
+
:include => [:activity, :user],
|
168
|
+
:order => "spent_on ASC")
|
169
|
+
end
|
170
|
+
|
171
|
+
def time_entries_for_user(user)
|
172
|
+
return TimeEntry.find(:all,
|
173
|
+
:conditions => self.conditions([user]),
|
174
|
+
:include => self.includes,
|
175
|
+
:order => "spent_on ASC"
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
def fetch_time_entries_by_project
|
180
|
+
self.projects.each do |project|
|
181
|
+
logs = []
|
182
|
+
users = []
|
183
|
+
if User.current.admin?
|
184
|
+
# Administrators can see all time entries
|
185
|
+
logs = time_entries_for_all_users(project)
|
186
|
+
users = logs.collect(&:user).uniq.sort
|
187
|
+
elsif User.current.allowed_to?(:see_project_timesheets, project)
|
188
|
+
# Users with the Role and correct permission can see all time entries
|
189
|
+
logs = time_entries_for_all_users(project)
|
190
|
+
users = logs.collect(&:user).uniq.sort
|
191
|
+
elsif User.current.allowed_to?(:view_time_entries, project)
|
192
|
+
# Users with permission to see their time entries
|
193
|
+
logs = time_entries_for_current_user(project)
|
194
|
+
users = logs.collect(&:user).uniq.sort
|
195
|
+
else
|
196
|
+
# Rest can see nothing
|
197
|
+
end
|
198
|
+
|
199
|
+
# Append the parent project name
|
200
|
+
if project.parent.nil?
|
201
|
+
unless logs.empty?
|
202
|
+
self.time_entries[project.name] = { :logs => logs, :users => users }
|
203
|
+
end
|
204
|
+
else
|
205
|
+
unless logs.empty?
|
206
|
+
self.time_entries[project.parent.name + ' / ' + project.name] = { :logs => logs, :users => users }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def fetch_time_entries_by_user
|
213
|
+
self.users.each do |user_id|
|
214
|
+
logs = []
|
215
|
+
if User.current.admin?
|
216
|
+
# Administrators can see all time entries
|
217
|
+
logs = time_entries_for_user(user_id)
|
218
|
+
elsif User.current.id == user_id
|
219
|
+
# Users can see their own their time entries
|
220
|
+
logs = time_entries_for_user(user_id)
|
221
|
+
else
|
222
|
+
# Rest can see nothing
|
223
|
+
end
|
224
|
+
|
225
|
+
unless logs.empty?
|
226
|
+
user = User.find_by_id(user_id)
|
227
|
+
self.time_entries[user.name] = { :logs => logs } unless user.nil?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# project => { :users => [users shown in logs],
|
233
|
+
# :issues =>
|
234
|
+
# { issue => {:logs => [time entries],
|
235
|
+
# issue => {:logs => [time entries],
|
236
|
+
# issue => {:logs => [time entries]}
|
237
|
+
#
|
238
|
+
def fetch_time_entries_by_issue
|
239
|
+
self.projects.each do |project|
|
240
|
+
logs = []
|
241
|
+
users = []
|
242
|
+
project.issues.each do |issue|
|
243
|
+
if User.current.admin?
|
244
|
+
# Administrators can see all time entries
|
245
|
+
logs << issue_time_entries_for_all_users(issue)
|
246
|
+
elsif User.current.allowed_to?(:see_project_timesheets, project)
|
247
|
+
# Users with the Role and correct permission can see all time entries
|
248
|
+
logs << issue_time_entries_for_all_users(issue)
|
249
|
+
elsif User.current.allowed_to?(:view_time_entries, project)
|
250
|
+
# Users with permission to see their time entries
|
251
|
+
logs << issue_time_entries_for_current_user(issue)
|
252
|
+
else
|
253
|
+
# Rest can see nothing
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
logs.flatten! if logs.respond_to?(:flatten!)
|
258
|
+
logs.uniq! if logs.respond_to?(:uniq!)
|
259
|
+
|
260
|
+
unless logs.empty?
|
261
|
+
users << logs.collect(&:user).uniq.sort
|
262
|
+
|
263
|
+
|
264
|
+
issues = logs.collect(&:issue).uniq
|
265
|
+
issue_logs = { }
|
266
|
+
issues.each do |issue|
|
267
|
+
issue_logs[issue] = logs.find_all {|time_log| time_log.issue == issue } # TimeEntry is for this issue
|
268
|
+
end
|
269
|
+
|
270
|
+
# TODO: TE without an issue
|
271
|
+
|
272
|
+
self.time_entries[project] = { :issues => issue_logs, :users => users}
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<table class="list issues">
|
2
|
+
<thead>
|
3
|
+
<th width="2%">
|
4
|
+
<%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "table")); return false;',
|
5
|
+
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}", :class => 'toggle-all' %>
|
6
|
+
</th>
|
7
|
+
<th width="2%"> </th>
|
8
|
+
<th width="8%"><%= l(:label_date) %></th>
|
9
|
+
<th width="10%"><%= l(:label_member) %></th>
|
10
|
+
<th width="45%"><%= l(:label_issue) %> / <%= l(:field_comments) %></th>
|
11
|
+
<th width="10%"><%= l(:field_hours) %>
|
12
|
+
<%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_group_header, { }) %>
|
13
|
+
<th align="center"></td>
|
14
|
+
</thead>
|
15
|
+
<tbody>
|
16
|
+
<%= render :partial => "issue_time_entries", :collection => entry[:issues] %>
|
17
|
+
</tbody>
|
18
|
+
</table>
|
19
|
+
<br />
|
20
|
+
<script type="text/javascript">
|
21
|
+
function toggleTimeEntries(id) {
|
22
|
+
$$('.issue-time-entry-' + id).each(function(ele) { ele.toggle();} )
|
23
|
+
$$('.toggle-' + id).each(function(ele) { ele.toggle();} )
|
24
|
+
}
|
25
|
+
|
26
|
+
/*
|
27
|
+
* Checks all the Time Entries under issue_id
|
28
|
+
*/
|
29
|
+
function toggleTimeEntriesSelection(issue_id) {
|
30
|
+
$$('.issue-time-entry-' + issue_id).each(function(ele) {
|
31
|
+
toggleIssuesSelection(ele);
|
32
|
+
});
|
33
|
+
}
|
34
|
+
|
35
|
+
</script>
|
@@ -0,0 +1,90 @@
|
|
1
|
+
<div id="timesheet-form">
|
2
|
+
<fieldset>
|
3
|
+
<% form_for :timesheet, :url =>{:action => 'report'} do |f| %>
|
4
|
+
|
5
|
+
<p id="date-options">
|
6
|
+
<label><%= l(:label_date)%>:</label><br />
|
7
|
+
<%= radio_button_tag 'timesheet[period_type]', '1', @timesheet.period_type == Timesheet::ValidPeriodType[:default] %>
|
8
|
+
<%= select_tag 'timesheet[period]', options_for_period_select((params[:timesheet].nil? ? nil : params[:timesheet][:period])),
|
9
|
+
:onfocus => '$("timesheet_period_type_1").checked = true;' %>
|
10
|
+
<br /><br />
|
11
|
+
|
12
|
+
<%= radio_button_tag 'timesheet[period_type]', '2', @timesheet.period_type == Timesheet::ValidPeriodType[:free_period] %>
|
13
|
+
<span onclick="$('timesheet_period_type_2').checked = true;">
|
14
|
+
<label for="timesheet_date_from"><%= l(:timesheet_date_from_label)%>:</label><br />
|
15
|
+
<%= f.text_field "date_from", :size => 10 %><%= calendar_for('timesheet_date_from') %><br />
|
16
|
+
|
17
|
+
<label for="timesheet_date_to"><%= l(:timesheet_date_to_label)%>:</label><br />
|
18
|
+
<%= f.text_field "date_to", :size => 10 %><%= calendar_for('timesheet_date_to') %><br /><br />
|
19
|
+
</span>
|
20
|
+
</p>
|
21
|
+
|
22
|
+
<p>
|
23
|
+
<label for="timesheet_sort"><%= l(:timesheet_group_by) %>:</label><br />
|
24
|
+
<%= select_tag("timesheet[sort]", options_for_select(Timesheet::ValidSortOptions.invert, @timesheet.sort)) %>
|
25
|
+
|
26
|
+
</p>
|
27
|
+
|
28
|
+
<p>
|
29
|
+
<label for="timesheet[projects][]" class="select_all"><%= l(:timesheet_project_label)%>:</label><br />
|
30
|
+
<%= select_tag 'timesheet[projects][]',
|
31
|
+
options_from_collection_for_select(@timesheet.allowed_projects, :id, :name, @timesheet.projects.collect(&:id)),
|
32
|
+
{ :multiple => true, :size => @list_size}
|
33
|
+
%>
|
34
|
+
</p>
|
35
|
+
|
36
|
+
|
37
|
+
<p>
|
38
|
+
<label for="timesheet[activities][]" class="select_all"><%= l(:timesheet_activities_label)%>:</label><br />
|
39
|
+
<%= select_tag 'timesheet[activities][]',
|
40
|
+
options_from_collection_for_select(@activities, :id, :name, @timesheet.activities),
|
41
|
+
{ :multiple => true, :size => @list_size}
|
42
|
+
%>
|
43
|
+
</p>
|
44
|
+
|
45
|
+
<p>
|
46
|
+
<label for="timesheet[users][]" class="select_all"><%= l(:timesheet_users_label)%>:</label><br />
|
47
|
+
<%= select_tag 'timesheet[users][]',
|
48
|
+
options_from_collection_for_select(User.find(:all, :conditions => ['status = ?', User::STATUS_ACTIVE]).sort { |a,b| a.to_s.downcase <=> b.to_s.downcase }, :id, :name, @timesheet.users),
|
49
|
+
{ :multiple => true, :size => @list_size}
|
50
|
+
|
51
|
+
%>
|
52
|
+
</p>
|
53
|
+
|
54
|
+
<%# TODO: Typo on hook %>
|
55
|
+
<%= call_hook(:plugin_timesheet_view_timesheet_form, { :timesheet => @timesheet, :params => params, :list_size => @list_size }) %>
|
56
|
+
<%= call_hook(:plugin_timesheet_views_timesheet_form, { :timesheet => @timesheet, :params => params, :list_size => @list_size }) %>
|
57
|
+
|
58
|
+
<div class="clear"></div>
|
59
|
+
<%= submit_tag l(:button_apply),:class => 'button-small' -%>
|
60
|
+
|
61
|
+
<% end %>
|
62
|
+
</fieldset>
|
63
|
+
</div>
|
64
|
+
|
65
|
+
<% content_for(:header_tags) do %>
|
66
|
+
<style type="text/css">
|
67
|
+
#date-options { margin-left: 10px; }
|
68
|
+
#date-options input[type='radio'] { margin-left: -20px; }
|
69
|
+
</style>
|
70
|
+
<script type="text/javascript">
|
71
|
+
|
72
|
+
function targetField(label_element) {
|
73
|
+
return $(label_element.attributes.for.value);
|
74
|
+
}
|
75
|
+
|
76
|
+
function selectAllOptions(element) {
|
77
|
+
for (var i = 0; i < element.options.length; i++) {
|
78
|
+
element.options[i].selected = true;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
Event.observe(window, 'load',
|
83
|
+
function() {
|
84
|
+
$$('label.select_all').each(function(element) {
|
85
|
+
Event.observe(element, 'click', function (e) { selectAllOptions(targetField(this)); });
|
86
|
+
});
|
87
|
+
}
|
88
|
+
);
|
89
|
+
</script>
|
90
|
+
<% end %>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<% issue = issue_time_entries[0] %>
|
2
|
+
<% time_entries = issue_time_entries[1] %>
|
3
|
+
<% unless issue.nil? %>
|
4
|
+
<tr class="<%= cycle("odd", "even") %>">
|
5
|
+
<td align="center">
|
6
|
+
<%= link_to image_tag('toggle_check.png'), {}, :onclick => "toggleTimeEntriesSelection('#{issue.id}'); return false;",
|
7
|
+
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}", :class => 'toggle-all' %>
|
8
|
+
</td>
|
9
|
+
<td align="center">
|
10
|
+
<%= toggle_issue_arrows(issue.id) %>
|
11
|
+
</td>
|
12
|
+
<td align="center"></td>
|
13
|
+
<td align="center"><%= issue.assigned_to.to_s %></td>
|
14
|
+
<td><%= link_to("##{issue.id}", :controller => 'issues', :action => 'show', :id => issue.id) %>: <%= h issue.subject %></td>
|
15
|
+
<td align="center"><strong><%= number_with_precision(displayed_time_entries_for_issue(time_entries), @precision) %></strong></td>
|
16
|
+
<%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_time_entry_sum, {:time_entries => time_entries, :precision => @precision }) %>
|
17
|
+
<td align="center"></td>
|
18
|
+
</tr>
|
19
|
+
<% time_entries.each do |time_entry| %>
|
20
|
+
<%# TODO: Typo on hook %>
|
21
|
+
<tr class="<%= cycle("odd", "even") %> issue-time-entry-<%= issue.id -%> hascontextmenu <%= call_hook(:plugin_timesheet_view_timesheets_time_entry_row_class, {:time_entry => time_entry }) %> <%= call_hook(:plugin_timesheet_views_timesheets_time_entry_row_class, {:time_entry => time_entry }) %>" style="display:none;">
|
22
|
+
<td align="center">
|
23
|
+
<%= check_box_tag 'ids[]', time_entry.id, false, { :class => 'checkbox' } %>
|
24
|
+
</td>
|
25
|
+
<td align="center"></td>
|
26
|
+
<td align="center"><%= format_date(time_entry.spent_on) %></td>
|
27
|
+
<td align="center"><%= time_entry.user.name %></td>
|
28
|
+
<td><%= h time_entry.comments %></td>
|
29
|
+
<td align="center"><strong><%= number_with_precision(time_entry.hours, @precision) %></strong></td>
|
30
|
+
<%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_time_entry, {:time_entry => time_entry, :precision => @precision }) %>
|
31
|
+
<td align="center">
|
32
|
+
<% if time_entry.editable_by?(User.current) -%>
|
33
|
+
<%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => time_entry},
|
34
|
+
:title => l(:button_edit) %>
|
35
|
+
<%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => time_entry},
|
36
|
+
:confirm => l(:text_are_you_sure),
|
37
|
+
:method => :post,
|
38
|
+
:title => l(:button_delete) %>
|
39
|
+
<% end -%>
|
40
|
+
</td>
|
41
|
+
</tr>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<%# TODO: Typo on hook %>
|
2
|
+
<tr id="time_entry_<%= time_entry.id %>" class="time_entry <%= cycle("odd", "even") %> hascontextmenu <%= call_hook(:plugin_timesheet_view_timesheets_time_entry_row_class, {:time_entry => time_entry }) %> <%= call_hook(:plugin_timesheet_views_timesheets_time_entry_row_class, {:time_entry => time_entry }) %>">
|
3
|
+
<td align="center"><%= check_box_tag 'ids[]', time_entry.id, false, { :class => 'checkbox' } %></td>
|
4
|
+
<td align="center"><%= format_date(time_entry.spent_on) %></td>
|
5
|
+
<td align="center"><%= time_entry.user.name %></td>
|
6
|
+
<td align="center"><%= time_entry.activity.name %></td>
|
7
|
+
<td align="center"><%= time_entry.project.name %></td>
|
8
|
+
<td align="center">
|
9
|
+
<% if time_entry.issue %>
|
10
|
+
<div class="tooltip">
|
11
|
+
<%= link_to_issue time_entry.issue %>
|
12
|
+
<span class="tip">
|
13
|
+
<%= render_issue_tooltip time_entry.issue %>
|
14
|
+
</span>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
17
|
+
</td>
|
18
|
+
<td><%=h time_entry.comments %></td>
|
19
|
+
<td align="center"><strong><%= number_with_precision(time_entry.hours, @precision) %></strong></td>
|
20
|
+
<%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_time_entry, {:time_entry => time_entry, :precision => @precision }) %>
|
21
|
+
<td align="center">
|
22
|
+
<% if time_entry.editable_by?(User.current) -%>
|
23
|
+
<%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => time_entry},
|
24
|
+
:title => l(:button_edit) %>
|
25
|
+
<%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => time_entry},
|
26
|
+
:confirm => l(:text_are_you_sure),
|
27
|
+
:method => :post,
|
28
|
+
:title => l(:button_delete) %>
|
29
|
+
<% end -%>
|
30
|
+
</td>
|
31
|
+
</tr>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<table class="list issues">
|
2
|
+
<thead>
|
3
|
+
<th width="2%">
|
4
|
+
<%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "table")); return false;',
|
5
|
+
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}", :class => 'toggle-all' %>
|
6
|
+
</th>
|
7
|
+
<th width="8%"><%= l(:label_date) %></th>
|
8
|
+
<th width="10%"><%= l(:label_member) %></th>
|
9
|
+
<th width="15%"><%= l(:label_activity) %></th>
|
10
|
+
<th width="15%"><%= l(:label_project) %></th>
|
11
|
+
<th width="10%"><%= l(:label_issue) %></th>
|
12
|
+
<th width="25%"><%= l(:field_comments) %></th>
|
13
|
+
<th width="10%"><%= l(:field_hours) %>
|
14
|
+
<%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_group_header, { }) %>
|
15
|
+
<th></th>
|
16
|
+
</thead>
|
17
|
+
<tbody>
|
18
|
+
<%= render :partial => "time_entry", :collection => entry[:logs] %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
21
|
+
<br />
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<%# TODO: Typo on hook %>
|
2
|
+
<% entries = call_hook(:plugin_timesheet_view_timesheets_context_menu, { :time_entries => @time_entries }) %>
|
3
|
+
<% entries += call_hook(:plugin_timesheet_views_timesheets_context_menu, { :time_entries => @time_entries }) %>
|
4
|
+
<%= content_tag(:ul, entries) unless entries.empty? %>
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<div class="contextual">
|
2
|
+
<%= permalink_to_timesheet(@timesheet) %>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
<h2><%= l(:timesheet_title)%></h2>
|
6
|
+
|
7
|
+
<%= render :partial => 'form' %>
|
8
|
+
|
9
|
+
<%= call_hook(:plugin_timesheet_views_timesheets_report_before_time_entries, { :timesheet => @timesheet }) %>
|
10
|
+
|
11
|
+
<% form_tag({}, { :id => 'time_entries'}) do -%>
|
12
|
+
<% if @timesheet.time_entries.length > 0 %>
|
13
|
+
<h2><%= l(:label_spent_time) %> (<%= h(number_with_precision(@grand_total, @precision)) -%> <%= h(l(:field_hours)) -%>)</h2>
|
14
|
+
|
15
|
+
<% @timesheet.time_entries.each do |entryname,entry|
|
16
|
+
case @timesheet.sort
|
17
|
+
when :user %>
|
18
|
+
<h3><%= h entryname -%> (<%= h number_with_precision(@total[entryname], @precision) %> <%= h(l(:field_hours)) -%>)</h3>
|
19
|
+
<%= render :partial => 'timesheet_group', :locals => {:entry => entry, :name => entryname, :total => @total[entryname] } %>
|
20
|
+
<% when :issue %>
|
21
|
+
<h3><%= h entryname -%> (<%= h number_with_precision(@total[entryname], @precision) %> <%= h(l(:field_hours)) -%>)</h3>
|
22
|
+
<%= render :partial => 'by_issue', :locals => {:entry => entry, :name => entryname, :total => 0 } %>
|
23
|
+
<% else %>
|
24
|
+
<%# Default to :project %>
|
25
|
+
<h3><%= h entryname -%> (<%= h number_with_precision(@total[entryname], @precision) %> <%= h(l(:field_hours)) -%>) <%= showing_users(entry[:users]) %></h3>
|
26
|
+
<%= render :partial => 'timesheet_group', :locals => {:entry => entry, :name => entryname, :total => @total[entryname] } %>
|
27
|
+
|
28
|
+
<% end
|
29
|
+
end # each
|
30
|
+
end # length
|
31
|
+
end # form_tag
|
32
|
+
-%>
|
33
|
+
|
34
|
+
<% content_for(:header_tags) do %>
|
35
|
+
<%= javascript_include_tag 'context_menu' %>
|
36
|
+
<%= stylesheet_link_tag 'context_menu' %>
|
37
|
+
<style type="text/css">
|
38
|
+
|
39
|
+
div#timesheet-form p { padding:0px 10px; float:left; }
|
40
|
+
|
41
|
+
</style>
|
42
|
+
<%# TODO: Typo on hook %>
|
43
|
+
<%= call_hook(:plugin_timesheet_view_timesheets_report_header_tags, { :timesheet => @timesheet }) %>
|
44
|
+
<%= call_hook(:plugin_timesheet_views_timesheets_report_header_tags, { :timesheet => @timesheet }) %>
|
45
|
+
<% end %>
|
46
|
+
|
47
|
+
<div id="context-menu" style="display: none;"></div>
|
48
|
+
<%= javascript_tag "new ContextMenu('#{url_for(:controller => 'timesheet', :action => 'context_menu')}')" %>
|
49
|
+
|
50
|
+
<%# TODO: Typo on hook %>
|
51
|
+
<%= call_hook(:plugin_timesheet_view_timesheets_report_bottom, { :timesheet => @timesheet }) %>
|
52
|
+
<%= call_hook(:plugin_timesheet_views_timesheets_report_bottom, { :timesheet => @timesheet }) %>
|
Binary file
|
Binary file
|
@@ -0,0 +1,9 @@
|
|
1
|
+
ca:
|
2
|
+
timesheet_title: Full de temps
|
3
|
+
timesheet_date_from_label: De
|
4
|
+
timesheet_date_to_label: A
|
5
|
+
timesheet_project_label: Projecte
|
6
|
+
timesheet_activities_label: Activitat
|
7
|
+
timesheet_users_label: Usuaris
|
8
|
+
timesheet_showing_users: 'mostrant temps per a: '
|
9
|
+
timesheet_permalink: "(Enllaç permanent a aquest full de temps)"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
da:
|
2
|
+
timesheet_title: Timeseddel
|
3
|
+
timesheet_date_from_label: Fra
|
4
|
+
timesheet_date_to_label: Til
|
5
|
+
timesheet_project_label: Projekt
|
6
|
+
timesheet_activities_label: Aktivitet
|
7
|
+
timesheet_users_label: Brugere
|
8
|
+
timesheet_showing_users: 'viser tidsregistreringer for: '
|
9
|
+
timesheet_permalink: '(Permanent link til denne timeseddel)'
|
10
|
+
timesheet_group_by: Gruppér efter
|
@@ -0,0 +1,10 @@
|
|
1
|
+
de:
|
2
|
+
timesheet_title: Stundenzettel
|
3
|
+
timesheet_date_from_label: Von
|
4
|
+
timesheet_date_to_label: Bis
|
5
|
+
timesheet_project_label: Projekt
|
6
|
+
timesheet_activities_label: Aktivität
|
7
|
+
timesheet_users_label: Benutzer
|
8
|
+
timesheet_showing_users: 'zeige Zeit für: '
|
9
|
+
timesheet_permalink: '(Permalink zu diesem Stundenzettel)'
|
10
|
+
timesheet_group_by: Gruppiere nach
|
@@ -0,0 +1,10 @@
|
|
1
|
+
en:
|
2
|
+
timesheet_title: Timesheet
|
3
|
+
timesheet_date_from_label: From
|
4
|
+
timesheet_date_to_label: To
|
5
|
+
timesheet_project_label: Project
|
6
|
+
timesheet_activities_label: Activity
|
7
|
+
timesheet_users_label: Users
|
8
|
+
timesheet_showing_users: 'showing time for: '
|
9
|
+
timesheet_permalink: '(Permalink to this timesheet)'
|
10
|
+
timesheet_group_by: Group by
|
@@ -0,0 +1,9 @@
|
|
1
|
+
es:
|
2
|
+
timesheet_title: Hoja de tiempos
|
3
|
+
timesheet_date_from_label: De
|
4
|
+
timesheet_date_to_label: A
|
5
|
+
timesheet_project_label: Proyecto
|
6
|
+
timesheet_activities_label: Actividad
|
7
|
+
timesheet_users_label: Usuarios
|
8
|
+
timesheet_showing_users: 'mostrando tiempos para: '
|
9
|
+
timesheet_permalink: '(Permalink a esta hoja de tiempos)'
|