timesheet_plugin 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)'
|