backlog 0.29.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,28 @@
1
+ == 0.30.0 2008-04-08
2
+
3
+ === Features
4
+
5
+ * Added two new shortcuts: ALT-SHIFT-D to go to the Daily Work Sheet
6
+ and ALT-SHIFT-W to go to the Weekly Work Sheet.
7
+
8
+ === Fixes
9
+
10
+ * Add 8 hours of work time for public holidays.
11
+ * Fixed bad rounding of work record durations.
12
+ * Fixed so that the "task" field gets focus after the "done" field has changed.
13
+ * Fixed wrong label for "Sick with doctor's note" radio button in the Daily Work Sheet.
14
+ It would select the "Sick day" button instead.
15
+ * Fixed parsing error for time of day: 930, 815 etc.
16
+
17
+ === Internal
18
+
19
+ * Renamed the weekly work sheet view from weekly_work_sheet_by_work_account to weekly_work_sheet for shorter URLs.
20
+ * Moved search call from works_controller to Work model.
21
+ * Removed non-use of client side JRuby applet.
22
+ * Extracted shortcut code into partials for use in all layouts.
23
+ * Removed unused absences layout.
24
+ * Removed duplicate code for parsing time of day.
25
+
1
26
  == 0.29.0 2008-04-07
2
27
 
3
28
  === Features
@@ -148,6 +148,8 @@ class ApplicationController < ActionController::Base
148
148
  {:key => 'Alt-Shift-N', :function => :new_period, :options => {:controller => 'periods', :action => 'new'}},
149
149
  {:key => 'Alt-Ctrl-N', :function => :new_backlog, :options => {:controller => 'backlogs', :action => 'new'}},
150
150
  {:key => 'Alt-Ctrl-G', :function => :new_group, :options => {:controller => 'groups', :action => :new}},
151
+ {:key => 'Alt-Shift-D', :function => :daily_work_sheet, :options => {:controller => 'works', :action => :daily_work_sheet}},
152
+ {:key => 'Alt-Shift-W', :function => :weekly_work_sheet, :options => {:controller => 'works', :action => :weekly_work_sheet}},
151
153
  ]
152
154
  end
153
155
 
@@ -61,7 +61,7 @@ class WorkLocksController < ApplicationController
61
61
  user_subscribers = current_user.work_lock_subscribers
62
62
  notify_users = (work_account_subscribers + user_subscribers).uniq
63
63
  notify_users.each do |user|
64
- week_url = url_for :controller => 'works', :action => :weekly_work_sheet_by_work_account, :year => @year, :week => @week, :user_id => current_user.id
64
+ week_url = url_for :controller => 'works', :action => :weekly_work_sheet, :year => @year, :week => @week, :user_id => current_user.id
65
65
  spreadsheet_url = url_for :controller => 'works', :action => :timeliste, :year => @year, :week => @week, :user_id => current_user.id
66
66
  WorkLockNotify.deliver_lock(user, current_user, @week, week_url, spreadsheet_url)
67
67
  end
@@ -191,7 +191,7 @@ class WorksController < ApplicationController
191
191
  render :layout => 'wide'
192
192
  end
193
193
 
194
- def weekly_work_sheet
194
+ def weekly_work_sheet_details
195
195
  @year = (params[:year] && params[:year].to_i) || Date.today.year
196
196
  @week = (params[:week] && params[:week].to_i) || Date.today.cweek
197
197
  @rows = Work.works_for_week(@year, @week)
@@ -201,18 +201,16 @@ class WorksController < ApplicationController
201
201
  render :layout => 'wide'
202
202
  end
203
203
 
204
- def weekly_work_sheet_by_work_account
204
+ def weekly_work_sheet
205
205
  @year = (params[:year] && params[:year].to_i) || Date.today.year
206
206
  @week = (params[:week] && params[:week].to_i) || Date.today.cweek
207
207
  @user = params[:user_id] ? User.find(params[:user_id]) : current_user
208
208
  @work_accounts = Work.works_for_week_by_work_account(@year, @week, @user)
209
209
  @first_date = Date.commercial(@year, @week, 1)
210
210
  @last_date = @first_date + 6
211
- @lock = WorkLock.find(:first, :conditions => ['user_id = ? AND start_on <= ? AND end_on >= ?', current_user.id, @first_date, @last_date])
212
- @absences = Absence.find(:all, :conditions => ['"on" BETWEEN ? AND ?', @first_date, @last_date], :order => '"on"')
213
- (0..6).each do |day|
214
- @absences.insert(day, nil) if @absences[day] && @absences[day].on > @first_date + day
215
- end
211
+ @lock = WorkLock.find_by_week(@year, @week)
212
+ @absences = Absence.find_by_week(@year, @week)
213
+ @public_holidays = PublicHoliday.find_by_week(@year, @week)
216
214
  render :layout => 'wide'
217
215
  end
218
216
 
@@ -286,7 +284,7 @@ class WorksController < ApplicationController
286
284
  raise "Unknown time format: #{params[:hours_time]}" unless convert_hours_param
287
285
  @work = Work.new(params[:work])
288
286
  @updated_fields = [@field]
289
- if @work.hours == 0 && @work.started_on && @work.start_time && @work.completed_at
287
+ if @work.hours == 0 && @work.started_on && @work.start_time && @work.completed_at
290
288
  @work.calculate_hours!
291
289
  @updated_fields << :hours_time
292
290
  end
@@ -59,6 +59,8 @@ module ApplicationHelper
59
59
  end
60
60
 
61
61
  def t(time_as_float)
62
+ return '' unless time_as_float
63
+ time_as_float = BigDecimal(time_as_float.to_s) unless time_as_float.is_a? BigDecimal
62
64
  "#{time_as_float.to_i}:#{'%02d' % (time_as_float.frac * 60).round}"
63
65
  end
64
66
 
@@ -1,4 +1,6 @@
1
1
  class Absence < ActiveRecord::Base
2
+ extend UserSystem
3
+
2
4
  validates_presence_of :user_id
3
5
  validates_presence_of :on
4
6
  validates_presence_of :reason
@@ -14,4 +16,15 @@ class Absence < ActiveRecord::Base
14
16
  end
15
17
  end
16
18
 
19
+ # Return an array of either an absence or nil for each day of the given week.
20
+ def self.find_by_week(year, week)
21
+ first_date = Date.commercial(year, week, 1)
22
+ last_date = first_date + 6
23
+ results = self.find(:all, :conditions => ['"on" BETWEEN ? AND ? AND user_id = ?', first_date, last_date, current_user && current_user.id], :order => '"on"')
24
+ (0..6).each do |day|
25
+ results.insert(day, nil) if results[day] && results[day].on > first_date + day
26
+ end
27
+ results
28
+ end
29
+
17
30
  end
@@ -1,2 +1,13 @@
1
1
  class PublicHoliday < ActiveRecord::Base
2
+ # Return an array of either a public holiday or nil for each day of the given week.
3
+ def self.find_by_week(year, week)
4
+ first_date = Date.commercial(year, week, 1)
5
+ last_date = first_date + 6
6
+ results = self.find(:all, :conditions => ['"on" BETWEEN ? AND ?', first_date, last_date], :order => '"on"')
7
+ (0..6).each do |day|
8
+ results.insert(day, nil) if results[day] && results[day].on > first_date + day
9
+ end
10
+ results
11
+ end
12
+
2
13
  end
@@ -139,10 +139,9 @@ class Work < ActiveRecord::Base
139
139
  self.completed_at = nil
140
140
  return
141
141
  end
142
- raise "invalid time format: #{new_value}" unless new_value =~ /^(\d{0,2}):?(\d{2})?$/
143
- new_hour, new_minutes = $1, $2
144
- t = started_on || completed_at || Time.now
145
- self.completed_at = Time.local(t.year, t.month, t.day, new_hour, new_minutes)
142
+ time_of_day = TimeOfDay.parse(new_value)
143
+ date = started_on || completed_at.to_date || Date.today
144
+ self.completed_at = date.at time_of_day
146
145
  end
147
146
 
148
147
  def calculate_hours!
@@ -151,7 +150,7 @@ class Work < ActiveRecord::Base
151
150
  end
152
151
 
153
152
  def calculate_hours
154
- BigDecimal(((completed_at - started_at) / 3600).to_s).round(2)
153
+ BigDecimal(((completed_at - started_at) / 3600).to_s).round(3)
155
154
  end
156
155
 
157
156
  end
@@ -1,3 +1,11 @@
1
1
  class WorkLock < ActiveRecord::Base
2
+ extend UserSystem
3
+
2
4
  belongs_to :user
5
+
6
+ def self.find_by_week(year, week)
7
+ first_date = Date.commercial(year, week, 1)
8
+ last_date = first_date + 6
9
+ result = self.find(:first, :conditions => ['user_id = ? AND start_on <= ? AND end_on >= ?', current_user.id, first_date, last_date])
10
+ end
3
11
  end
@@ -13,11 +13,11 @@ class WorkLockNagger
13
13
  app_url = config[:app_url]
14
14
  end
15
15
  if app_url
16
- url = app_url + 'works/weekly_work_sheet_by_work_account'
16
+ url = app_url + 'works/weekly_work_sheet'
17
17
  else
18
18
  host = Socket::gethostname
19
19
  port = config[:port] || 3000
20
- url = url_for(:host => host, :port => port, :controller => 'works', :action => :weekly_work_sheet_by_work_account)
20
+ url = url_for(:host => host, :port => port, :controller => 'works', :action => :weekly_work_sheet)
21
21
  end
22
22
  rescue Exception => e
23
23
  puts "Work Lock Nagger Exception"
@@ -20,4 +20,4 @@
20
20
  <%= javascript_include_tag "effects" %>
21
21
  <%= javascript_include_tag "controls" %>
22
22
  <%= javascript_include_tag "dragdrop" %>
23
- <%= javascript_include_tag "ruby" %>
23
+ <%#= javascript_include_tag "ruby" %>
@@ -0,0 +1,16 @@
1
+ <% if @shortcuts %>
2
+ <div class="rblock">
3
+ <div class="btitle">
4
+ <h4><%=l :shortcuts %></h4>
5
+ </div>
6
+ <table>
7
+ <% @shortcuts.each_with_index do |shortcut, index| %>
8
+ <% if shortcut[:options] %>
9
+ <tr><td valign="top"><%= shortcut[:key]%></td><td><%=detour_to l(shortcut[:function]), shortcut[:options], :id => shortcut[:function], :tabindex => index+100 %></td></tr>
10
+ <% else %>
11
+ <tr><td valign="top"><%= shortcut[:key]%></td><td><%=l shortcut[:function] %></td></tr>
12
+ <% end %>
13
+ <% end %>
14
+ </table>
15
+ </div>
16
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <script type="text/javascript">
2
+ function handlePageEvent(event) {
3
+ if (event.altKey && event.shiftKey && event.charCode == 110) {
4
+ window.location = '<%=url_for(:controller => 'periods', :action => :new)%>';
5
+ } else if (event.altKey && event.ctrlKey && event.charCode == 110) {
6
+ window.location = '<%=url_for(:controller => 'backlogs', :action => :new ) %>';
7
+ } else if (event.altKey && event.charCode == 110) {
8
+ window.location = '<%=url_for(:controller => 'tasks', :action => :new, :task => {:backlog_id => (@backlog ? @backlog.id : nil), :period_id => (@period ? @period.id : nil)} ) %>';
9
+ } else if (event.altKey && event.ctrlKey && event.charCode == 103) {
10
+ window.location = '<%=url_for(:controller => 'groups', :action => :new ) %>';
11
+ } else if (event.shiftKey && event.altKey && event.charCode == 100) {
12
+ window.location = '<%=url_for(:controller => 'works', :action => :daily_work_sheet ) %>';
13
+ } else if (event.shiftKey && event.altKey && event.charCode == 119) {
14
+ window.location = '<%=url_for(:controller => 'works', :action => :weekly_work_sheet ) %>';
15
+ } else {
16
+ //alert("shift: " + event.shiftKey + ", alt: " + event.altKey + ", ctrl: " + event.ctrlKey + ", " + event.charCode + ", " + event.keyCode);
17
+ }
18
+ }
19
+ </script>
@@ -6,21 +6,7 @@
6
6
  <%=render :partial => '/layouts/headers' %>
7
7
  </head>
8
8
 
9
- <script type="text/javascript">
10
- function handlePageEvent(event) {
11
- if (event.altKey && event.shiftKey && event.charCode == 110) {
12
- window.location = '<%=url_for(:controller => 'periods', :action => :new)%>';
13
- } else if (event.altKey && event.ctrlKey && event.charCode == 110) {
14
- window.location = '<%=url_for(:controller => 'backlogs', :action => :new ) %>';
15
- } else if (event.altKey && event.charCode == 110) {
16
- window.location = '<%=url_for(:controller => 'tasks', :action => :new, :task => {:backlog_id => (@backlog ? @backlog.id : nil), :period_id => (@period ? @period.id : nil)} ) %>';
17
- } else if (event.altKey && event.ctrlKey && event.charCode == 103) {
18
- window.location = '<%=url_for(:controller => 'groups', :action => :new ) %>';
19
- } else {
20
- // alert("shift: " + event.shiftKey + ", alt: " + event.altKey + ", ctrl: " + event.ctrlKey + ", " + event.charCode + ", " + event.keyCode);
21
- }
22
- }
23
- </script>
9
+ <%=render :partial => '/layouts/shortcuts_js'%>
24
10
 
25
11
  <body onkeypress="handlePageEvent(event);">
26
12
  <div id="wrap">
@@ -31,22 +17,7 @@ function handlePageEvent(event) {
31
17
  </div>
32
18
  </div>
33
19
  <div id="rightcol">
34
- <% if @shortcuts %>
35
- <div class="rblock">
36
- <div class="btitle">
37
- <h4><%=l :shortcuts %></h4>
38
- </div>
39
- <table>
40
- <% @shortcuts.each_with_index do |shortcut, index| %>
41
- <% if shortcut[:options] %>
42
- <tr><td valign="top"><%= shortcut[:key]%></td><td><%=detour_to l(shortcut[:function]), shortcut[:options], :id => shortcut[:function], :tabindex => index+100 %></td></tr>
43
- <% else %>
44
- <tr><td valign="top"><%= shortcut[:key]%></td><td><%=l shortcut[:function] %></td></tr>
45
- <% end %>
46
- <% end %>
47
- </table>
48
- </div>
49
- <% end %>
20
+ <%=render :partial => '/layouts/shortcuts'%>
50
21
 
51
22
  <% if @sidebars %>
52
23
  <% @sidebars.each do |sidebar| %>
@@ -8,10 +8,14 @@
8
8
  <head>
9
9
  <%=render :partial => '/layouts/headers' %>
10
10
  </head>
11
- <body>
11
+
12
+ <%=render :partial => '/layouts/shortcuts_js'%>
13
+
14
+ <body onkeypress="handlePageEvent(event);">
12
15
 
13
16
  <div width="793" align="center">
14
17
  <%=render :partial => '/layouts/left_top'%>
18
+ <%=render :partial => '/layouts/shortcuts'%>
15
19
  <%= yield %>
16
20
  </div>
17
21
 
@@ -1,6 +1,6 @@
1
- <%=image_detour_to('hammer.png', l(:weekly_work_sheet), :controller => 'works', :action => :weekly_work_sheet_by_work_account)%>
2
- <!--
3
1
  <%=image_detour_to('hammer.png', l(:weekly_work_sheet), :controller => 'works', :action => :weekly_work_sheet)%>
2
+ <!--
3
+ <%=image_detour_to('hammer.png', l(:weekly_work_sheet), :controller => 'works', :action => :weekly_work_sheet_details)%>
4
4
  -->
5
5
  <%=link_to(image_tag(url_for("hammer.png"), :alt => l(:daily_work_sheet), :title => l(:daily_work_sheet), :class => 'image-submit'), :controller => 'works', :action => :daily_work_sheet)%>
6
6
  <!--
@@ -67,7 +67,7 @@
67
67
  :next_field => 'hours_time', :next_row_id => next_row_id, :previous_row_id => previous_row_id} %>
68
68
  </td>
69
69
  <td align="right">
70
- <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :next_field => :invoice} do |f|%>
70
+ <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :next_field => :task_id} do |f|%>
71
71
  <%=f.text_field :hours_time, :value => (@work.hours && @work.hours > 0 ? t(@work.hours) : ''), :id => "work_#{@work.id}_hours_time", :class => 'task_hours',
72
72
  :onkeypress => "
73
73
  if(event.keyCode == 40) {
@@ -79,7 +79,7 @@
79
79
  e.focus();
80
80
  e.select();
81
81
  }",
82
- :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=invoice', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"%>
82
+ :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=task_id', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"%>
83
83
  <% end %>
84
84
  </td>
85
85
  <td align="right">
@@ -5,10 +5,10 @@
5
5
  <% lock_action = @lock ? :unlock : :lock %>
6
6
  [<%=link_to l(lock_action), with_detour(:controller => 'work_locks', :action => lock_action, :year => @year, :week => @week) %>]
7
7
  <%unless controller.action_name == 'weekly_work_sheet'%>
8
- [<%=link_to l(:details), :action => :weekly_work_sheet, :year => @year, :week => @week %>]
8
+ [<%=link_to l(:totals), :action => :weekly_work_sheet, :year => @year, :week => @week %>]
9
9
  <% end %>
10
- <%unless controller.action_name == 'weekly_work_sheet_by_work_account'%>
11
- [<%=link_to l(:totals), :action => :weekly_work_sheet_by_work_account, :year => @year, :week => @week %>]
10
+ <%unless controller.action_name == 'weekly_work_sheet_details'%>
11
+ [<%=link_to l(:details), :action => :weekly_work_sheet_details, :year => @year, :week => @week %>]
12
12
  <% end %>
13
13
  <% if @lock %>
14
14
  [<%=link_to l(:spreadsheet), :action => :timeliste, :year => @year, :week => @week %>]
@@ -4,7 +4,7 @@
4
4
 
5
5
  <div style="float: left"><%=image_link_to('arrow_left.png', l(:previous_week), {:id => @date - 7}, :class => nil)%></div>
6
6
  <div style="float: left"><%=image_link_to('arrow_left.png', l(:previous_day), {:id => @date-1}, :class => nil)%></div>
7
- <%=link_to l(:weekly_work_sheet), :action => :weekly_work_sheet_by_work_account, :year => @year, :week => @week%>
7
+ <%=link_to l(:weekly_work_sheet), :action => :weekly_work_sheet, :year => @year, :week => @week%>
8
8
  <div style="float: right"><%=image_link_to('arrow_right.png', l(:next_week), {:id => @date + 7}, :class => nil)%></div>
9
9
  <div style="float: right"><%=image_link_to('arrow_right.png', l(:next_day), {:id => @date+1}, :class => nil)%></div>
10
10
 
@@ -21,7 +21,7 @@
21
21
  <%=f.radio_button :reason, '', :disabled => work_disabled, :checked => @absence.nil? && (!@works.empty? || @public_holiday.nil?), :onchange => 'form.submit()' %><label for="absence_reason_">Work day</label>
22
22
  <%=f.radio_button :reason, 'HOLIDAY', :disabled => absence_disabled, :onchange => 'form.submit()' %><label for="absence_reason_holiday"><%=l :holiday%></label>
23
23
  <%=f.radio_button :reason, 'SICK', :disabled => absence_disabled, :onchange => 'form.submit()' %><label for="absence_reason_sick">Sick day</label>
24
- <%=f.radio_button :reason, 'SICK_WITH_DOCTOR', :disabled => absence_disabled, :onchange => 'form.submit()' %><label for="absence_reason_sick">Sick day with doctor's note</label>
24
+ <%=f.radio_button :reason, 'SICK_WITH_DOCTOR', :disabled => absence_disabled, :onchange => 'form.submit()' %><label for="absence_reason_sick_with_doctor">Sick day with doctor's note</label>
25
25
  <% end %>
26
26
  </div>
27
27
  </td>
@@ -16,9 +16,8 @@ if @work && @work.errors.empty?
16
16
  end
17
17
  page["work_" + field_name.to_s].value = value
18
18
  end
19
- #page.replace "work_#{@work.id}", :partial => 'row', :object => @work
20
- #page["work_#{@work.id}_#{@next_field}"].select
21
- #page["work_#{@work.id}_#{@next_field}"].focus
19
+ #page["work_#{@next_field}"].focus
20
+ #page["work_#{@next_field}"].select
22
21
  else
23
22
  @work.errors.each do |error|
24
23
  page["work_#{@work.id}_#{error[0]}"].addClassName('fieldWithErrors')
@@ -9,5 +9,5 @@ else
9
9
  end
10
10
  end
11
11
  if @day_total
12
- page.replace_html "hours_total", "#{'%d:%02d' % [@day_total.to_i, 60 * (@day_total % 1)]}"
12
+ page.replace_html "hours_total", "#{t @day_total}"
13
13
  end
@@ -1,102 +1,46 @@
1
- <% @page_title = l(:weekly_work_sheet) + (@period ? " for #{@period}" : '') + (user? ? " for #{user.login}" : '')%>
1
+ <% @page_title = l(:weekly_work_sheet) + " " + @week.to_s + (@user ? " for #{@user.name}" : '')%>
2
2
 
3
3
  <div id="spotlight">
4
- <% track_times = @rows.find {|r| r.find {|w| w && w.work_account.track_times?}} %>
5
- <% invoicing = @rows.find {|r| r.find {|w| w && w.work_account.enable_invoicing?}} %>
6
- <% columns = 2 + (track_times ? 1 : 0) + (invoicing ? 1 : 0) %>
7
-
8
- <h1>Work for week <%=@week%><%=user? ? " for #{user.login}" : ''%></h1>
4
+ <% columns = 2 %>
9
5
 
10
6
  <%=render :partial => 'weekly_work_sheet_buttons'%>
11
7
 
12
8
  <table border="1">
13
9
  <tr>
10
+ <th><%=l :work_account %></th>
14
11
  <% [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday].each_with_index do |day, i| %>
15
- <th colspan="<%=columns%>" align="center"><%=detour_to "#{l(day)} #{week_date(i+1)}", :action => :daily_work_sheet, :id => (@first_date + i).strftime('%Y-%m-%d') %></th>
12
+ <th align="center"><%=detour_to "#{l(day)} #{week_date(i+1)}", :action => :daily_work_sheet, :id => (@first_date + i).strftime('%Y-%m-%d') %></th>
16
13
  <% end %>
17
14
  <th align="center" nowrap="true"><%=l :week %> <%=@week%></th>
18
15
  </tr>
19
- <tr>
20
- <% (1..7).each do %>
21
- <th><%=l :work_account %></th>
22
- <% if invoicing %>
23
- <th><%=l :invoice_short %></th>
24
- <% end %>
25
- <% if track_times %>
26
- <th><%=l(:start) if track_times %> - <%=l(:stop) if track_times %></th>
27
- <% end %>
28
- <th><%=l :done %></th>
29
- <% end %>
30
- <th><%=l :totals %></th>
31
- </tr>
32
16
 
33
- <% week_total = 0 %>
17
+ <% week_totals = Hash.new(0) %>
34
18
  <% day_totals = Array.new(7, 0) %>
35
- <% for row in @rows %>
19
+ <% for work_account, totals in @work_accounts %>
36
20
  <tr>
37
- <% row.each_with_index do |@work, day| %>
38
- <% if @work %>
39
- <% day_totals[day] += @work.hours %>
40
- <% week_total += @work.hours %>
41
- <td valign="top">
42
- <%=image_detour_to('hammer.png', l(:edit), {:controller => 'works', :action => :edit, :id => @work.id}, {:style => "float: left"}) %>
43
- <% if @work.task_id %>
44
- <%=detour_to(h(@work.task.description), :controller => 'tasks', :action => :edit, :id => @work.task_id)%>
45
- <% else %>
46
- <% field_id = "work_#{@work.id}_description" %>
47
- <%=text_field(:work, :description, :id => field_id, :name => "work_#{@work.id}_description", :class => :task_description, :style => 'width: 8em', :disabled => @lock,
48
- :onchange => "new Ajax.Updater('#{field_id}', '#{url_for(:action => :update, :id => @work.id)}' + '?work[description]=' + this.value);") %>
49
- <% end %>
50
- <br/>
51
- <!--
52
- <%=detour_to(h(@work.task.period.name), :controller => 'periods', :action => :show, :id => @work.task.period) if @work.task && @work.task.period %>
53
- -->
54
- <%=detour_to(h(@work.work_account.name), :controller => 'work_accounts', :action => :show, :id => @work.work_account_id) %>
55
- </td>
56
- <% if invoicing %>
57
- <td id="invoice_<%=@work.id%>" valign="top">
58
- <% form_id = "work_#{@work.id}_invoice_form" %>
59
- <% field_id = "work_#{@work.id}_invoice" %>
60
- <%=check_box(:work, :invoice, :id => field_id, :name => field_id, :onchange => "new Ajax.Updater('invoice_#{@work}', '#{url_for(:action => :set_work_invoice, :id => @work)}' + '?value=' + this.checked);", :disabled => @lock) if @work.work_account.enable_invoicing? %>
61
- </td>
62
- <% end %>
63
- <% if track_times %>
64
- <td align="left" valign="top">
65
- <% form_id = "work_#{@work.id}_time_form" %>
66
- <% field_id = "work_#{@work.id}_start_time" %>
67
- <form id="<%=form_id%>">
68
- <%=text_field(:work, :start_time, :class => :task_time, :disabled => @lock,
69
- :onchange => "new Ajax.Updater('#{field_id}', '#{url_for(:action => :update, :id => @work.id)}' + '?work[start_time]=' + this.value);") %> -
70
- <%=text_field(:work, :completed_at_time, :class => :task_time, :disabled => @lock,
71
- :onchange => "new Ajax.Updater('work_#{@work.id}_completed_at_time', '#{url_for(:action => :update, :id => @work.id)}' + '?work[completed_at_time]=' + this.value);") %>
72
- </form>
73
- </td>
74
- <% end %>
75
- <td align="right" valign="top">
76
- <% form_id = "work_#{@work.id}_hours_form" %>
77
- <% field_id = "work_#{@work.id}_hours_time" %>
78
- <form id="<%=form_id%>">
79
- <%=text_field(:work, :hours_time, :value => '%d:%02d' % [@work.hours , 60 * @work.hours % 60], :class => 'task_hours', :disabled => @lock,
80
- :onchange => "new Ajax.Updater('#{field_id}', '#{url_for(:action => :update, :id => @work.id)}' + '?work[hours_time]=' + this.value);") %>
81
- </form>
82
- </td>
83
- <% else %>
84
- <td/>
85
- <% if invoicing %>
86
- <td/>
87
- <% end %>
88
- <% if track_times %>
89
- <td/>
90
- <% end %>
91
- <td/>
21
+ <td valign="top">
22
+ <%=detour_to(h(work_account.name), :controller => 'work_accounts', :action => :show, :id => work_account.id) %>
23
+ </td>
24
+ <% totals.each_with_index do |total, day| %>
25
+ <% if total %>
26
+ <% day_totals[day] += total %>
27
+ <% week_totals[work_account] += total %>
28
+ <td align="right" valign="top"><%=t(total)%></td>
29
+ <% else %>
30
+ <td/>
92
31
  <% end %>
93
32
  <% end %>
33
+ <td align="right"><%=t(week_totals[work_account])%></td>
94
34
  </tr>
95
35
  <% end %>
96
36
  <tr>
97
- <% (0..6).each do |day| %>
98
- <th class="hours" colspan="<%=columns%>"><%='%.2f' % day_totals[day] if day_totals[day] > 0%></th>
99
- <% end %>
100
- <th class="hours"><%= '%.2f' % week_total %></th>
37
+ <th><%=l :totals%></th>
38
+ <% (0..6).each do |day| %>
39
+ <% day_totals[day] += BigDecimal('8') if @absences[day] || @public_holidays[day] %>
40
+ <th class="hours"><%=t(day_totals[day]) if day_totals[day] > 0%></th>
41
+ <% end %>
42
+ <% week_total = day_totals.inject(BigDecimal('0')) {|total, day_total| total + day_total} %>
43
+ <th class="hours"><%=t(week_total)%></th>
101
44
  </tr>
102
45
  </table>
46
+ </div>
@@ -0,0 +1,102 @@
1
+ <% @page_title = l(:weekly_work_sheet) + (@period ? " for #{@period}" : '') + (user? ? " for #{user.login}" : '')%>
2
+
3
+ <div id="spotlight">
4
+ <% track_times = @rows.find {|r| r.find {|w| w && w.work_account.track_times?}} %>
5
+ <% invoicing = @rows.find {|r| r.find {|w| w && w.work_account.enable_invoicing?}} %>
6
+ <% columns = 2 + (track_times ? 1 : 0) + (invoicing ? 1 : 0) %>
7
+
8
+ <h1>Work for week <%=@week%><%=user? ? " for #{user.login}" : ''%></h1>
9
+
10
+ <%=render :partial => 'weekly_work_sheet_buttons'%>
11
+
12
+ <table border="1">
13
+ <tr>
14
+ <% [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday].each_with_index do |day, i| %>
15
+ <th colspan="<%=columns%>" align="center"><%=detour_to "#{l(day)} #{week_date(i+1)}", :action => :daily_work_sheet, :id => (@first_date + i).strftime('%Y-%m-%d') %></th>
16
+ <% end %>
17
+ <th align="center" nowrap="true"><%=l :week %> <%=@week%></th>
18
+ </tr>
19
+ <tr>
20
+ <% (1..7).each do %>
21
+ <th><%=l :work_account %></th>
22
+ <% if invoicing %>
23
+ <th><%=l :invoice_short %></th>
24
+ <% end %>
25
+ <% if track_times %>
26
+ <th><%=l(:start) if track_times %> - <%=l(:stop) if track_times %></th>
27
+ <% end %>
28
+ <th><%=l :done %></th>
29
+ <% end %>
30
+ <th><%=l :totals %></th>
31
+ </tr>
32
+
33
+ <% week_total = 0 %>
34
+ <% day_totals = Array.new(7, 0) %>
35
+ <% for row in @rows %>
36
+ <tr>
37
+ <% row.each_with_index do |@work, day| %>
38
+ <% if @work %>
39
+ <% day_totals[day] += @work.hours %>
40
+ <% week_total += @work.hours %>
41
+ <td valign="top">
42
+ <%=image_detour_to('hammer.png', l(:edit), {:controller => 'works', :action => :edit, :id => @work.id}, {:style => "float: left"}) %>
43
+ <% if @work.task_id %>
44
+ <%=detour_to(h(@work.task.description), :controller => 'tasks', :action => :edit, :id => @work.task_id)%>
45
+ <% else %>
46
+ <% field_id = "work_#{@work.id}_description" %>
47
+ <%=text_field(:work, :description, :id => field_id, :name => "work_#{@work.id}_description", :class => :task_description, :style => 'width: 8em', :disabled => @lock,
48
+ :onchange => "new Ajax.Updater('#{field_id}', '#{url_for(:action => :update, :id => @work.id)}' + '?work[description]=' + this.value);") %>
49
+ <% end %>
50
+ <br/>
51
+ <!--
52
+ <%=detour_to(h(@work.task.period.name), :controller => 'periods', :action => :show, :id => @work.task.period) if @work.task && @work.task.period %>
53
+ -->
54
+ <%=detour_to(h(@work.work_account.name), :controller => 'work_accounts', :action => :show, :id => @work.work_account_id) %>
55
+ </td>
56
+ <% if invoicing %>
57
+ <td id="invoice_<%=@work.id%>" valign="top">
58
+ <% form_id = "work_#{@work.id}_invoice_form" %>
59
+ <% field_id = "work_#{@work.id}_invoice" %>
60
+ <%=check_box(:work, :invoice, :id => field_id, :name => field_id, :onchange => "new Ajax.Updater('invoice_#{@work}', '#{url_for(:action => :set_work_invoice, :id => @work)}' + '?value=' + this.checked);", :disabled => @lock) if @work.work_account.enable_invoicing? %>
61
+ </td>
62
+ <% end %>
63
+ <% if track_times %>
64
+ <td align="left" valign="top">
65
+ <% form_id = "work_#{@work.id}_time_form" %>
66
+ <% field_id = "work_#{@work.id}_start_time" %>
67
+ <form id="<%=form_id%>">
68
+ <%=text_field(:work, :start_time, :class => :task_time, :disabled => @lock,
69
+ :onchange => "new Ajax.Updater('#{field_id}', '#{url_for(:action => :update, :id => @work.id)}' + '?work[start_time]=' + this.value);") %> -
70
+ <%=text_field(:work, :completed_at_time, :class => :task_time, :disabled => @lock,
71
+ :onchange => "new Ajax.Updater('work_#{@work.id}_completed_at_time', '#{url_for(:action => :update, :id => @work.id)}' + '?work[completed_at_time]=' + this.value);") %>
72
+ </form>
73
+ </td>
74
+ <% end %>
75
+ <td align="right" valign="top">
76
+ <% form_id = "work_#{@work.id}_hours_form" %>
77
+ <% field_id = "work_#{@work.id}_hours_time" %>
78
+ <form id="<%=form_id%>">
79
+ <%=text_field(:work, :hours_time, :value => '%d:%02d' % [@work.hours , 60 * @work.hours % 60], :class => 'task_hours', :disabled => @lock,
80
+ :onchange => "new Ajax.Updater('#{field_id}', '#{url_for(:action => :update, :id => @work.id)}' + '?work[hours_time]=' + this.value);") %>
81
+ </form>
82
+ </td>
83
+ <% else %>
84
+ <td/>
85
+ <% if invoicing %>
86
+ <td/>
87
+ <% end %>
88
+ <% if track_times %>
89
+ <td/>
90
+ <% end %>
91
+ <td/>
92
+ <% end %>
93
+ <% end %>
94
+ </tr>
95
+ <% end %>
96
+ <tr>
97
+ <% (0..6).each do |day| %>
98
+ <th class="hours" colspan="<%=columns%>"><%='%.2f' % day_totals[day] if day_totals[day] > 0%></th>
99
+ <% end %>
100
+ <th class="hours"><%= '%.2f' % week_total %></th>
101
+ </tr>
102
+ </table>
@@ -0,0 +1,9 @@
1
+ class ExpandWorkDonePrecision < ActiveRecord::Migration
2
+ def self.up
3
+ change_column :works, :hours, :decimal, :precision => 6, :scale => 3, :null => false, :default => 0
4
+ end
5
+
6
+ def self.down
7
+ change_column :works, :hours, :decimal, :precision => 6, :scale => 2, :null => false, :default => 0
8
+ end
9
+ end
@@ -2,7 +2,7 @@
2
2
  # migrations feature of ActiveRecord to incrementally modify your database, and
3
3
  # then regenerate this schema definition.
4
4
 
5
- ActiveRecord::Schema.define(:version => 30) do
5
+ ActiveRecord::Schema.define(:version => 31) do
6
6
 
7
7
  create_table "absences", :force => true do |t|
8
8
  t.column "user_id", :integer, :null => false
@@ -135,7 +135,7 @@ ActiveRecord::Schema.define(:version => 30) do
135
135
 
136
136
  create_table "works", :force => true do |t|
137
137
  t.column "task_id", :integer
138
- t.column "hours", :decimal, :precision => 6, :scale => 2, :default => 0.0, :null => false
138
+ t.column "hours", :decimal, :precision => 6, :scale => 3, :default => 0.0, :null => false
139
139
  t.column "completed_at", :datetime
140
140
  t.column "user_id", :integer
141
141
  t.column "invoice", :boolean
@@ -87,7 +87,7 @@ class WorksControllerTest < Test::Unit::TestCase
87
87
  assert_redirected_to :controller => 'periods', :action => 'show', :id => periods(:past).id, :task_id => 1
88
88
 
89
89
  assert_equal num_works + 1, Work.count
90
- assert_equal BigDecimal('0.33'), assigns(:work).hours
90
+ assert_equal BigDecimal('0.333'), assigns(:work).hours
91
91
  end
92
92
 
93
93
  def test_edit
@@ -21,7 +21,7 @@ class TimeOfDay
21
21
  def self.parse(string)
22
22
  string.strip!
23
23
  return nil if string.empty?
24
- raise "Illegal time format: '#{string}'" unless string =~ /(\d{1,2}):?(\d{2})?(?::(\d{1,2}))?/
24
+ raise "Illegal time format: '#{string}'" unless string =~ /^(\d{1,2}):?(\d{2})?(?::(\d{1,2}))?$/
25
25
  self.new($1.to_i, $2.to_i, $3.to_i)
26
26
  end
27
27
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backlog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.29.0
4
+ version: 0.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uwe Kubosch
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-07 00:00:00 +02:00
12
+ date: 2008-04-09 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -171,11 +171,12 @@ files:
171
171
  - app/views/works/_form.rhtml
172
172
  - app/views/works/weekly_work_sheet.rhtml
173
173
  - app/views/works/_weekly_work_sheet_buttons.rhtml
174
+ - app/views/works/weekly_work_sheet_details.rhtml
174
175
  - app/views/works/show.rhtml
175
- - app/views/works/weekly_work_sheet_by_work_account.rhtml
176
176
  - app/views/layouts
177
+ - app/views/layouts/_shortcuts_js.rhtml
177
178
  - app/views/layouts/wide.rhtml
178
- - app/views/layouts/absences.rhtml
179
+ - app/views/layouts/_shortcuts.rhtml
179
180
  - app/views/layouts/mwrt002.rhtml
180
181
  - app/views/layouts/_headers.rhtml
181
182
  - app/views/layouts/_left_top.rhtml
@@ -709,6 +710,7 @@ files:
709
710
  - db/migrate/018_create_groups.rb
710
711
  - db/migrate/003_add_estimation_points.rb
711
712
  - db/migrate/010_add_work_start.rb
713
+ - db/migrate/031_expand_work_done_precision.rb
712
714
  - db/migrate/007_add_task_created_at.rb
713
715
  - db/migrate/016_add_invoicable_flag.rb
714
716
  - db/migrate/009_add_subtasks.rb
@@ -1,17 +0,0 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
-
4
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
- <head>
6
- <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
7
- <title>Absences: <%= controller.action_name %></title>
8
- <%= stylesheet_link_tag 'scaffold' %>
9
- </head>
10
- <body>
11
-
12
- <p style="color: green"><%= flash[:notice] %></p>
13
-
14
- <%= yield %>
15
-
16
- </body>
17
- </html>
@@ -1,46 +0,0 @@
1
- <% @page_title = l(:weekly_work_sheet) + " " + @week.to_s + (@user ? " for #{@user.name}" : '')%>
2
-
3
- <div id="spotlight">
4
- <% columns = 2 %>
5
-
6
- <%=render :partial => 'weekly_work_sheet_buttons'%>
7
-
8
- <table border="1">
9
- <tr>
10
- <th><%=l :work_account %></th>
11
- <% [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday].each_with_index do |day, i| %>
12
- <th align="center"><%=detour_to "#{l(day)} #{week_date(i+1)}", :action => :daily_work_sheet, :id => (@first_date + i).strftime('%Y-%m-%d') %></th>
13
- <% end %>
14
- <th align="center" nowrap="true"><%=l :week %> <%=@week%></th>
15
- </tr>
16
-
17
- <% week_totals = Hash.new(0) %>
18
- <% day_totals = Array.new(7, 0) %>
19
- <% for work_account, totals in @work_accounts %>
20
- <tr>
21
- <td valign="top">
22
- <%=detour_to(h(work_account.name), :controller => 'work_accounts', :action => :show, :id => work_account.id) %>
23
- </td>
24
- <% totals.each_with_index do |total, day| %>
25
- <% if total %>
26
- <% day_totals[day] += total %>
27
- <% week_totals[work_account] += total %>
28
- <td align="right" valign="top"><%='%d:%02d' % [total, 60 * total % 60]%></td>
29
- <% else %>
30
- <td/>
31
- <% end %>
32
- <% end %>
33
- <td align="right"><%='%d:%02d' % [week_totals[work_account], 60 * week_totals[work_account] % 60]%></td>
34
- </tr>
35
- <% end %>
36
- <tr>
37
- <th><%=l :totals%></th>
38
- <% (0..6).each do |day| %>
39
- <% day_totals[day] += 8 if @absences[day] %>
40
- <th class="hours"><%='%d:%02d' % [day_totals[day], 60 * day_totals[day] % 60] if day_totals[day] > 0%></th>
41
- <% end %>
42
- <% week_total = day_totals.inject(0) {|total, day_total| total + day_total} %>
43
- <th class="hours"><%= '%d:%02d' % [week_total, 60 * week_total % 60] %></th>
44
- </tr>
45
- </table>
46
- </div>