backlog 0.31.1 → 0.32.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.
@@ -1,3 +1,27 @@
1
+ == 0.32.0 2008-05-06
2
+
3
+ == Features
4
+
5
+ * Added filtering on work account to work search view.
6
+ * Added "Sick child" as absence reason in the daily work sheet.
7
+ * Added navigating to work records for a user from the subscription list in the user view.
8
+
9
+ === Fixes
10
+
11
+ * Fixed wrong parsing of one-digit minutes for work duration.
12
+ * Reformatted the Excel export of work records for a work account.
13
+ Also added sorting by user, task, start and stop times.
14
+ Should work with MS Excel now.
15
+ * Set default search dates for work to current month.
16
+ * Keep user_id when following links from the weekly work sheet to the daily work sheet.
17
+ This enables subscribers of time sheets to get details about the time sheets they are watching.
18
+ * Added a "Back" link from weekly work sheet.
19
+ * Indent subtasks in the sprint view.
20
+ * Fixed bug in work lock subscription invitation.
21
+ * Fixed so that partially filled in work records are retained after failed creation.
22
+ Also error messages for failed work record creations are displayed.
23
+ * Fixed design of the "New Work Account" view, and set focus on the "Name" field when it is first loaded.
24
+
1
25
  == 0.31.1 2008-04-24
2
26
 
3
27
  === Fixes
data/Rakefile CHANGED
@@ -14,6 +14,8 @@ require 'tasks/rails'
14
14
  require 'hoe'
15
15
  require 'version_from_history'
16
16
 
17
+ ENV['SKIP_AR_JDBC_RAKE_REDEFINES'] = '1'
18
+
17
19
  Hoe.new("backlog", APP::VERSION) do |p|
18
20
  p.rubyforge_name = "backlog"
19
21
  p.summary = "Application to aid collecting, processing, organizing, reviewing and doing tasks."
@@ -217,6 +217,7 @@ class UserController < ApplicationController
217
217
  @groups = Group.find(:all, :order => 'name')
218
218
  @periods = @user.periods
219
219
  @associates = User.find(:all) - @user.work_lock_subscribers - [current_user]
220
+ @potential_subscribees = User.find(:all) - @user.work_lock_subscriptions - [current_user]
220
221
  absences = Absence.find(:all, :conditions => ['user_id = ? AND "on" BETWEEN ? AND ?', current_user.id, Date.new(Date.today.year, 1, 1), Date.new(Date.today.year, 12, 31)])
221
222
  @holidays = absences.select {|a| a.reason == 'HOLIDAY'}
222
223
  @holidays.map! {|a| a.on}
@@ -55,19 +55,4 @@ class WorkAccountsController < ApplicationController
55
55
  redirect_to :action => 'list'
56
56
  end
57
57
 
58
- def works
59
- work_account = WorkAccount.find(params[:id])
60
- @report_filter = WorksReportFilter.new(params[:report_filter])
61
- @report_filter.title = "#{l :hours} for #{work_account.name} #{@report_filter.start_on && @report_filter.start_on.strftime('%Y-%m-%d - ')}#{@report_filter.end_on && @report_filter.end_on.strftime('%Y-%m-%d')}"
62
- @works = Work.paginate :conditions => ["completed_at BETWEEN ? AND ? AND work_account_id = ? #{@report_filter.invoice.nil? ? '' : 'AND invoice = ?'} #{@report_filter.user_id.nil? ? '' : 'AND user_id = ?'}", @report_filter.start_on, @report_filter.end_on + 1, work_account.id, @report_filter.invoice, @report_filter.user_id].compact,
63
- :page => params[:page], :per_page => @report_filter.page_size,
64
- :order => 'started_on, start_time, completed_at'
65
- @users = User.find(:all)
66
- if params[:export] == 'excel'
67
- render :template => '/works/list_excel', :layout => false
68
- else
69
- render :template => '/works/list'
70
- end
71
- end
72
-
73
58
  end
@@ -8,7 +8,6 @@ class WorksController < ApplicationController
8
8
 
9
9
  def index
10
10
  list
11
- render :action => 'list'
12
11
  end
13
12
 
14
13
  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
@@ -16,8 +15,21 @@ class WorksController < ApplicationController
16
15
  :redirect_to => { :action => :list }
17
16
 
18
17
  def list
18
+ work_account_name = (params[:work_account_id] && WorkAccount.find(params[:work_account_id]).name) || 'all accoutns'
19
+ @report_filter = WorksReportFilter.new(params[:report_filter])
20
+ @report_filter.title = "#{l :hours} for #{work_account_name} #{@report_filter.start_on && @report_filter.start_on.strftime('%Y-%m-%d - ')}#{@report_filter.end_on && @report_filter.end_on.strftime('%Y-%m-%d')}"
19
21
  @period = params[:id] && Period.find(params[:id])
20
- @works = Work.paginate :order => 'completed_at', :page => params[:page]
22
+ @works = Work.paginate :conditions => ["completed_at BETWEEN ? AND ? #{'AND work_account_id = ?' if @report_filter.work_account_id} #{@report_filter.invoice.nil? ? '' : 'AND invoice = ?'} #{@report_filter.user_id.nil? ? '' : 'AND user_id = ?'}", @report_filter.start_on, @report_filter.end_on + 1, @report_filter.work_account_id, @report_filter.invoice, @report_filter.user_id].compact,
23
+ :page => params[:page], :per_page => @report_filter.page_size,
24
+ :order => 'started_on, start_time, completed_at'
25
+ @work_accounts = WorkAccount.find(:all, :order => :name)
26
+ @users = User.find(:all, :order => 'first_name, last_name')
27
+ if params[:export] == 'excel'
28
+ @works = @works.sort_by {|w| [w.user_id || 0, w.task_id || 999999, w.started_on]}
29
+ render :template => '/works/list_excel', :layout => false
30
+ else
31
+ render :template => '/works/list'
32
+ end
21
33
  end
22
34
 
23
35
  def show
@@ -63,6 +75,7 @@ class WorksController < ApplicationController
63
75
  else
64
76
  if params[:detour]
65
77
  flash[:notice] = 'Error creating new work record.'
78
+ flash[:work] = @work
66
79
  back
67
80
  else
68
81
  new
@@ -180,13 +193,14 @@ class WorksController < ApplicationController
180
193
  @date = (params[:id] && Date.parse(params[:id])) || Date.today
181
194
  @year = @date.year
182
195
  @week = @date.cweek
196
+ @user = (params[:user_id] && User.find(params[:user_id])) || current_user
183
197
  @periods = []
184
- @works = Work.find_work_for_day @date
198
+ @works = Work.find_work_for_day(@date, @user)
185
199
  @customers = Customer.find(:all, :order => :name)
186
200
  @started_works = Task.find_started
187
- @work = Work.new(:started_at => Time.now, :completed_at => Time.now)
201
+ @new_work = flash[:work] || Work.new(:started_at => Time.now)
188
202
  @work_accounts = WorkAccount.find(:all, :order => :name)
189
- @absence = Absence.find(:first, :conditions => {:on => @date, :user_id => current_user.id})
203
+ @absence = Absence.find(:first, :conditions => {:on => @date, :user_id => @user.id})
190
204
  @public_holiday = PublicHoliday.find(:first, :conditions => {:on => @date})
191
205
  render :layout => 'wide'
192
206
  end
@@ -209,7 +223,7 @@ class WorksController < ApplicationController
209
223
  @first_date = Date.commercial(@year, @week, 1)
210
224
  @last_date = @first_date + 6
211
225
  @lock = WorkLock.find_by_week(@year, @week)
212
- @absences = Absence.find_by_week(@year, @week)
226
+ @absences = Absence.find_by_week(@year, @week, @user)
213
227
  @public_holidays = PublicHoliday.find_by_week(@year, @week)
214
228
  render :layout => 'wide'
215
229
  end
@@ -302,8 +316,8 @@ class WorksController < ApplicationController
302
316
  end
303
317
  if params[:work][:hours_time] =~ /^(\d*):(\d{2})?$/
304
318
  new_hours = $1
305
- new_minutes = $2.to_i
306
- new_value_str = "#{new_hours}.#{(new_minutes / 0.6).round}"
319
+ new_minutes = BigDecimal($2)
320
+ new_value_str = '%02d.%03d' % [new_hours, (new_minutes / BigDecimal('0.06')).round]
307
321
  new_value = BigDecimal(new_value_str)
308
322
  params[:work][:hours] = new_value
309
323
  elsif params[:work][:hours_time] =~ /^(\d*)[.,]?(\d*)$/
@@ -6,10 +6,10 @@ module ApplicationHelper
6
6
  include UserSystem
7
7
  include UrlForFix
8
8
 
9
- def image_button_to(image_source, title, options)
10
- image_submit_tag image_source, :class => 'image-submit', :alt => title, :title => title,
11
- :id => "#{title}_#{options[:id]}", :name => title,
12
- :onclick => "form.action='#{url_for(options)}'"
9
+ def image_button_to(image_source, title, options, html_options = {})
10
+ image_submit_tag image_source, {:class => 'image-submit', :alt => title, :title => title,
11
+ :id => "#{title}_#{options[:id]}", :name => title,
12
+ :onclick => "form.action='#{url_for(options)}'"}.update(html_options)
13
13
  end
14
14
 
15
15
  def detour_to(title, options, html_options = nil)
@@ -17,10 +17,10 @@ class Absence < ActiveRecord::Base
17
17
  end
18
18
 
19
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)
20
+ def self.find_by_week(year, week, user = current_user)
21
21
  first_date = Date.commercial(year, week, 1)
22
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"')
23
+ results = self.find(:all, :conditions => ['"on" BETWEEN ? AND ? AND user_id = ?', first_date, last_date, user && user.id], :order => '"on"')
24
24
  (0..6).each do |day|
25
25
  results.insert(day, nil) if results[day] && results[day].on > first_date + day
26
26
  end
@@ -5,8 +5,9 @@ class ReportFilter
5
5
  attr_reader :page_size
6
6
 
7
7
  def initialize(attributes)
8
- @start_on = Date.civil(2007, 01, 01)
9
- @end_on = Date.today
8
+ date = Date.today - 5
9
+ @start_on = Date.civil(date.year, date.month, 01)
10
+ @end_on = Date.civil(date.year, date.month, -1)
10
11
  @page_size = 1000
11
12
 
12
13
  if attributes
@@ -9,6 +9,7 @@ class User < Party
9
9
  # even if they follow the defaults since ClassTableInheritanceInRails breaks it.
10
10
  has_and_belongs_to_many :groups, :join_table => "groups_users", :foreign_key => "user_id", :association_foreign_key => 'group_id'
11
11
  has_and_belongs_to_many :work_lock_subscribers, :class_name => 'User', :join_table => "user_work_lock_subscriptions", :foreign_key => "user_id", :association_foreign_key => 'subscriber_user_id'
12
+ has_and_belongs_to_many :work_lock_subscriptions, :class_name => 'User', :join_table => "user_work_lock_subscriptions", :association_foreign_key => "user_id", :foreign_key => 'subscriber_user_id'
12
13
  has_many :works, :foreign_key => :user_id, :order => :completed_at
13
14
  has_many :work_locks, :foreign_key => :user_id, :order => :end_on
14
15
 
@@ -103,8 +103,8 @@ class Work < ActiveRecord::Base
103
103
  totals_per_work_account
104
104
  end
105
105
 
106
- def self.find_work_for_day date
107
- Work.find(:all, :conditions => "started_on = '#{date}' AND user_id = #{current_user.id}",
106
+ def self.find_work_for_day(date, user = current_user)
107
+ Work.find(:all, :conditions => "started_on = '#{date}' AND user_id = #{user.id}",
108
108
  :order => 'start_time, completed_at')
109
109
  end
110
110
 
@@ -1,14 +1,19 @@
1
1
  class WorksReportFilter < ReportFilter
2
+ attr_reader :work_account_id
2
3
  attr_reader :invoice
3
4
  attr_reader :user_id
4
5
 
5
6
  def initialize(attributes)
7
+ @work_account_id = nil
6
8
  @invoice = nil
7
9
  @user_id = nil
8
10
 
9
11
  if attributes
10
12
  attributes = attributes.clone
11
13
 
14
+ work_account_id_param = attributes.delete(:work_account_id)
15
+ @work_account_id = work_account_id_param.to_i if work_account_id_param && work_account_id_param.size > 0
16
+
12
17
  invoice_param = attributes.delete(:invoice)
13
18
  @invoice = invoice_param == 'true' if invoice_param && invoice_param.size > 0
14
19
 
@@ -35,9 +35,13 @@ function handleEvent(field, event, id) {
35
35
  <div id="active_tasks"<%=' style="display: none;"' unless @tasks and not @tasks.empty?%>>
36
36
  <%=render :partial => '/tasks/fields_header', :locals => { :backlog => nil, :active => true, :track_todo => @tasks.find {|t|t.period && t.period.active?}, :track_times => @tasks.find {|t|t.period && t.period.active?}, :track_done => @tasks.find {|t|t.period && t.period.active? }, :work_done => @tasks.find {|t| t.total_done > 0} } %>
37
37
  <ul id="active_tasks_<%=@period.id%>" class="task_list">
38
+ <% max_depth = @tasks.map{|t|t.depth}.max %>
38
39
  <% for task in @tasks -%>
39
40
  <% next if @show_only_grabbed_tasks && !(task.users.empty? || task.users.include?(current_user))%>
40
- <%=render :partial => '/tasks/task', :locals => { :task => task, :i => i, :active => true, :highlight_task => task == @selected_task, :update => :spotlight, :show_backlog => true, :hidden => false } %>
41
+ <%=render :partial => '/tasks/task', :locals => { :task => task, :i => i,
42
+ :active => true, :highlight_task => (task == @selected_task),
43
+ :update => :spotlight, :show_backlog => true, :hidden => false,
44
+ :max_depth => max_depth} %>
41
45
  <% i += 1 %>
42
46
  <% end -%>
43
47
  </ul>
@@ -3,14 +3,13 @@
3
3
  <div style="float: left" style="border: 0px">
4
4
  <% if @task.enable_subtasks? && active && (@task.period.nil? || @task.period.active_or_future?) %>
5
5
  <% form_tag({:controller => 'tasks', :action => :specify, :id => @task}) do %>
6
- <%= image_button_to('add.png', l(:specify), :controller => 'tasks', :action => :specify, :id => @task.id)%>
6
+ <%=image_button_to('add.png', l(:specify), {:controller => 'tasks', :action => :specify, :id => @task.id},
7
+ :style => "margin-left: #{@task.depth * 2}em") %>
7
8
  <% end %>
8
9
  <% end %>
9
10
  </div>
10
11
  <div style="float: left" style="border: 3px solid red">
11
- <%=("&nbsp;" * @task.depth * 4) if @task.depth > 0 %>
12
12
  <%=resolution_image(@task.resolution) if @task.finished_at %>
13
- <%="-" if @task.children.size > 0 %>
14
13
  </div>
15
14
  <div id="task_<%=@task.id%>_id" class="task_id">
16
15
  <%=detour_to "##{@task.id}", :controller => 'tasks', :action => :edit, :id => @task.id, :style => 'border: 0px; margin: 0px; padding: 0px' if @task.position || @task.depth == 0 %>
@@ -53,6 +53,7 @@
53
53
  <div id="lfeature">
54
54
  <div class="btitle">
55
55
  <h4><%=l :work_lock_subscriptions %></h4>
56
+ <h3>Users watching <%=@user.name%></h4>
56
57
  </div>
57
58
 
58
59
  <% if @user == current_user %>
@@ -67,10 +68,11 @@
67
68
  <% end %>
68
69
  <% unless @associates.empty? %>
69
70
  <tr>
70
- <% remote_form_for :user, :url => {:action => :invite_work_lock_subscriber} do |f| %>
71
- <td align="right"><%=select :id, nil, @associates.map{|u| [u.name, u.id]}, {}, :name => 'id'%></td>
72
- <td align="left"><%=submit_tag l(:invite)%></td>
73
- <% end %>
71
+ <td align="right">
72
+ <% remote_form_for :user, :html => {:id => 'invitation_form'}, :url => {:action => :invite_work_lock_subscriber} do |f| %>
73
+ <%=select :id, nil, @associates.map{|u| [u.name, u.id]}, {}, :name => 'id'%></td>
74
+ <% end %>
75
+ <td align="left"><%=button_to_function l(:invite), "new Ajax.Request('/user/invite_work_lock_subscriber', {asynchronous:true, evalScripts:true, parameters:Form.serialize($('invitation_form'))});" %></td>
74
76
  </tr>
75
77
  <% end %>
76
78
  </table>
@@ -80,6 +82,36 @@
80
82
  <% end %>
81
83
  </div>
82
84
 
85
+ <div id="rfeature">
86
+ <div class="btitle">
87
+ <h4><%=l :work_lock_subscriptions %></h4>
88
+ <h3><%=@user.name%> is monitoring</h4>
89
+ </div>
90
+
91
+ <% if @user == current_user %>
92
+ <table align="right">
93
+ <% unless @user.work_lock_subscriptions.empty? %>
94
+ <% for subscribee in @user.work_lock_subscriptions %>
95
+ <tr>
96
+ <td align="right"><%=detour_to subscribee.name, :action => :edit, :id => subscribee.id %></td>
97
+ <td align="left"><%=image_link_to 'work_account.png', "#{l :work}", {:controller => 'works', :action => :weekly_work_sheet, :user_id => subscribee.id} %></td>
98
+ <td align="left"><%=image_link_to_remote 'email.png', "#{l :stop} #{l :monitoring}", {:action => :toggle_work_lock_monitoring, :subscriber_id => current_user.id, :id => subscribee.id}, :id => "work_lock_monitor_icon_#{subscribee.id}"%></td>
99
+ </tr>
100
+ <% end %>
101
+ <% end %>
102
+ <% unless @potential_subscribees.empty? %>
103
+ <tr>
104
+ <td align="right">
105
+ <% remote_form_for :user, :html => {:id => 'monitoring_form'}, :url => {:action => :invite_work_lock_subscriber} do |f| %>
106
+ <%=select :id, nil, @potential_subscribees.map{|u| [u.name, u.id]}, {}, :name => 'id'%></td>
107
+ <% end %>
108
+ <td align="left"><%=button_to_function l(:monitor), "new Ajax.Request('/user/toggle_work_lock_monitoring', {asynchronous:true, evalScripts:true, parameters:Form.serialize($('monitoring_form'))});" %></td>
109
+ </tr>
110
+ <% end %>
111
+ </table>
112
+ <% end %>
113
+ </div>
114
+
83
115
  <div id="lfeature">
84
116
  <div class="btitle">
85
117
  <h4><%=l :holidays_used %></h4>
@@ -1,6 +1,5 @@
1
1
  <%= error_messages_for 'work_account' %>
2
2
 
3
- <!--[form:work_account]-->
4
3
  <p><label for="work_account_name">Name</label><br/>
5
4
  <%= text_field 'work_account', 'name' %></p>
6
5
 
@@ -11,5 +10,7 @@
11
10
 
12
11
  <p><label for="work_account_invoice_code">Invoice code</label><br/>
13
12
  <%= text_field 'work_account', 'invoice_code' %></p>
14
- <!--[eoform:work_account]-->
15
13
 
14
+ <%=javascript_tag update_page { |page|
15
+ page[:work_account_name].focus
16
+ } %>
@@ -6,7 +6,7 @@
6
6
  <% form_tag :action => 'update', :id => @work_account do %>
7
7
  <%=render :partial => 'form' %>
8
8
  <%=submit_tag l(:save) %>
9
- <%=back_or_link_to l(:back), :action => 'show', :id => @backlog %>
9
+ <%=back_or_link_to l(:back), :action => :list %>
10
10
  <% end %>
11
11
 
12
12
  </div>
@@ -15,7 +15,7 @@
15
15
 
16
16
  <% for work_account in @work_accounts %>
17
17
  <tr>
18
- <td><%=link_to h(work_account.name), :action => 'show', :id => work_account %></td>
18
+ <td><%=link_to h(work_account.name), :controller => 'works', :action => :list, :report_filter => {:work_account_id => work_account.id} %></td>
19
19
  <td align="right"><%=work_account.track_times? %></td>
20
20
  <td><%=h(work_account.invoice_code) %></td>
21
21
  <td><%= link_to 'Edit', :action => 'edit', :id => work_account %></td>
@@ -1,8 +1,10 @@
1
- <h1>New work_account</h1>
1
+ <% @page_title = "#{l :new_work_account}" %>
2
2
 
3
+ <div id="spotlight">
3
4
  <% form_tag :action => 'create' do %>
4
5
  <%= render :partial => 'form' %>
5
6
  <%= submit_tag "Create" %>
6
7
  <% end %>
7
8
 
8
9
  <%= link_to 'Back', :action => 'list' %>
10
+ </div>
@@ -106,7 +106,7 @@
106
106
  return false;
107
107
  }
108
108
  }",
109
- :value => @work && @work.completed_at_time.strftime('%H:%M')
109
+ :value => @work && @work.completed_at && @work.completed_at_time.strftime('%H:%M')
110
110
  %>
111
111
  </td>
112
112
  <td align="right" valign="bottom">
@@ -1,4 +1,4 @@
1
- <% @page_title = "#{l :daily_work_sheet} on #{@date}" + (" for #{user.login}" if user?) %>
1
+ <% @page_title = "#{l :daily_work_sheet} on #{@date}" + (" for #{@user.name}" if user?) %>
2
2
 
3
3
  <div id="spotlight">
4
4
 
@@ -21,6 +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_CHILD', :disabled => absence_disabled, :onchange => 'form.submit()' %><label for="absence_reason_sick_child">Sick child</label>
24
25
  <%=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
26
  <% end %>
26
27
  </div>
@@ -64,7 +65,7 @@
64
65
  <% last_work = @work %>
65
66
 
66
67
  <% unless @absence %>
67
- <% @work = nil %>
68
+ <% @work = @new_work %>
68
69
  <%=render :partial => 'new_row', :locals => {:last_work => last_work} %>
69
70
  <% end %>
70
71
 
@@ -97,7 +98,7 @@
97
98
  <script type="text/JavaScript">
98
99
  //<!--
99
100
  var start_field;
100
- <% if last_work.completed_at %>
101
+ <% if last_work.nil? || last_work.completed_at %>
101
102
  start_field = $('work_work_account_id');
102
103
  <% else %>
103
104
  start_field = $('work_<%=last_work.id%>_completed_at_time');
@@ -7,6 +7,14 @@
7
7
  <% if @report_filter %>
8
8
  <% form_for :report_filter, :html => {:method => :get} do |f| %>
9
9
  <table>
10
+ <tr>
11
+ <td coslpan="3">
12
+ <label for="report_filter_work_account_id"><%=l :work_account%></label>
13
+ </td>
14
+ <td>
15
+ <%=f.select :work_account_id, [[l(:all), nil]] + @work_accounts.map{|wa|[wa.name, wa.id]}, {}, {:onchange => 'form.submit()'} %>
16
+ </td>
17
+ </tr>
10
18
  <tr>
11
19
  <td>
12
20
  <label for="report_filter_start_on"><%=l :start_on%></label>
@@ -91,9 +99,9 @@
91
99
 
92
100
  <table>
93
101
  <tr>
94
- <th><%=l :task %></th>
95
102
  <th><%=l :user %></th>
96
103
  <th><%=l :done %></th>
104
+ <th><%=l :description %></th>
97
105
  <th><%=l :invoice %></th>
98
106
  <% if @period && @period.track_times? %>
99
107
  <th><%=l :started_at %></th>
@@ -103,23 +111,26 @@
103
111
 
104
112
  <% for work in @works %>
105
113
  <tr>
106
- <td><%=work.task.description if work.task %></td>
107
- <td><%=work.user && work.user.login %></td>
108
- <td><%=work.hours %></td>
114
+ <td valign="top"><%=work.user && work.user.login %></td>
115
+ <td valign="top"><%=work.hours %></td>
116
+ <%if work.task
117
+ link = "Task: #{work.task.description} <br/>"
118
+ else
119
+ link = ""
120
+ end
121
+ link += h(work.description)
122
+ %>
123
+ <td><%= link_to link, :controller => 'works', :action => 'daily_work_sheet', :id => work.started_on.strftime("%Y-%m-%d") %></td>
109
124
  <td><%=work.invoice ? l(:yes) : '' %></td>
110
125
  <% if @period && @period.track_times? %>
111
126
  <td><%=work.started_at && work.started_at.strftime('%Y-%m-%d %H:%M:%S') %></td>
112
127
  <% end %>
113
- <td><%=work.completed_at && work.completed_at.strftime('%Y-%m-%d %H:%M:%S') %></td>
114
- <td><%= link_to 'Show', :controller => 'works', :action => 'show', :id => work %></td>
115
- <td><%= link_to 'Edit', :controller => 'works', :action => 'edit', :id => work %></td>
116
- <td><%= link_to 'Destroy', { :controller => 'works', :action => 'destroy', :id => work }, :confirm => 'Are you sure?', :method => :post %></td>
117
- </tr>
128
+ <td valign="top"><%=work.completed_at && work.completed_at.strftime('%Y-%m-%d %H:%M:%S') %></td>
129
+ <tr>
118
130
  <% end %>
119
131
  <tr>
120
132
  <th><%=l :total%>:</th>
121
- <td/>
122
- <th><%=@works.inject(BigDecimal('0')){|total, work| total += work.hours}%> <%=l(:hours).downcase%></td>
133
+ <th colspan="2"><%=@works.inject(BigDecimal('0')){|total, work| total += work.hours}%> <%=l(:hours).downcase%></td>
123
134
  </tr>
124
135
  </table>
125
136
 
@@ -136,4 +147,4 @@
136
147
 
137
148
  <% if @period %>
138
149
  <%=render :partial => '/periods/burn_down_chart' %>
139
- <% end %>
150
+ <% end %>
@@ -29,11 +29,6 @@
29
29
  <Styles>
30
30
  <Style ss:ID="Default" ss:Name="Normal">
31
31
  <Alignment ss:Vertical="Top"/>
32
- <Borders/>
33
- <Font/>
34
- <Interior/>
35
- <NumberFormat/>
36
- <Protection/>
37
32
  </Style>
38
33
  <Style ss:ID="title">
39
34
  <Font x:Family="Swiss" ss:Size="24" ss:Bold="1"/>
@@ -41,37 +36,49 @@
41
36
  <Style ss:ID="header">
42
37
  <Font x:Family="Swiss" ss:Size="14" ss:Bold="1"/>
43
38
  </Style>
39
+ <Style ss:ID="DateTime">
40
+ <NumberFormat ss:Format="Number" />
41
+ </Style>
44
42
  </Styles>
45
43
  <Worksheet ss:Name="<%=l(:done)%>">
46
- <Table ss:ExpandedColumnCount="256" ss:ExpandedRowCount="35" x:FullColumns="1"
44
+ <Table ss:ExpandedColumnCount="256" x:FullColumns="1"
47
45
  x:FullRows="1">
48
- <Column ss:AutoFitWidth="1" ss:Width="10cm"/>
49
- <Column ss:AutoFitWidth="2" ss:Width="4cm"/>
50
- <Column ss:AutoFitWidth="4" ss:Width="5cm"/>
51
- <Column ss:AutoFitWidth="5" ss:Width="5cm"/>
52
- <Column ss:AutoFitWidth="3" ss:Width="1.5cm"/>
46
+ <Column ss:AutoFitWidth="1" ss:Width="100"/>
47
+ <Column ss:AutoFitWidth="1" ss:Width="90"/>
48
+ <Column ss:AutoFitWidth="1" ss:Width="120"/>
49
+ <Column ss:AutoFitWidth="1" ss:Width="200"/>
50
+ <Column ss:AutoFitWidth="1" ss:Width="60"/>
51
+ <Column ss:AutoFitWidth="1" ss:Width="45"/>
52
+ <Column ss:AutoFitWidth="1" ss:Width="90"/>
53
+ <Column ss:AutoFitWidth="1" ss:Width="90"/>
53
54
  <Row>
54
- <Cell ss:StyleID="title" ss:MergeAcross="4"><Data ss:Type="String"><%=@report_filter.title%></Data></Cell>
55
+ <Cell ss:StyleID="title" ss:MergeAcross="6"><Data ss:Type="String"><%=@report_filter.title%></Data></Cell>
55
56
  </Row>
56
57
  <Row ss:AutoFitHeight="0" ss:Height="6.5625">
57
- <Cell ss:MergeAcross="4"><Data ss:Type="String"></Data></Cell>
58
+ <Cell ss:MergeAcross="6"><Data ss:Type="String"></Data></Cell>
58
59
  </Row>
59
60
 
60
61
  <Row ss:AutoFitHeight="0" ss:Height="19.875">
61
- <Cell ss:StyleID="header"><Data ss:Type="String">Aktivitet</Data></Cell>
62
- <Cell ss:StyleID="header"><Data ss:Type="String">Person</Data></Cell>
63
- <Cell ss:StyleID="header"><Data ss:Type="String">Start</Data></Cell>
64
- <Cell ss:StyleID="header"><Data ss:Type="String">Stopp</Data></Cell>
65
- <Cell ss:StyleID="header"><Data ss:Type="String">Timer</Data></Cell>
62
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :work_account%></Data></Cell>
63
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :user%></Data></Cell>
64
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :task%></Data></Cell>
65
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :description%></Data></Cell>
66
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :done%></Data></Cell>
67
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :invoice_short%></Data></Cell>
68
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :start%></Data></Cell>
69
+ <Cell ss:StyleID="header"><Data ss:Type="String"><%=l :stop%></Data></Cell>
66
70
  </Row>
67
71
 
68
72
  <% for work in @works %>
69
73
  <Row>
70
- <Cell><Data ss:Type="String"><%=[work.task && work.task.description, work.description].compact.join('&#10;') %></Data></Cell>
74
+ <Cell><Data ss:Type="String"><%=work.work_account.name %></Data></Cell>
71
75
  <Cell><Data ss:Type="String"><%=work.user && work.user.name %></Data></Cell>
72
- <Cell><Data ss:Type="Date"><%=work.started_on.strftime('%Y-%m-%d') %><%=work.start_time && work.start_time.strftime(' %H:%M:%S') %></Data></Cell>
73
- <Cell><Data ss:Type="Date"><%=work.completed_at && work.completed_at.strftime('%Y-%m-%d %H:%M:%S') %></Data></Cell>
74
- <Cell><Data ss:Type="Number"><%=work.hours %> %></Data></Cell>
76
+ <Cell><Data ss:Type="String"><%=work.task && work.task.description %></Data></Cell>
77
+ <Cell><Data ss:Type="String"><%=work.description %></Data></Cell>
78
+ <Cell><Data ss:Type="Number"><%=work.hours %></Data></Cell>
79
+ <Cell><Data ss:Type="String"><%=work.invoice? ? 'Ja' : 'Nei' %></Data></Cell>
80
+ <Cell ss:StyleID="DateTime"><Data ss:Type="Number"><%=BigDecimal((work.started_on - Date.new(1899, 12, 31)).to_s) + (work.start_time ? (BigDecimal(work.start_time.hour.to_s) + BigDecimal(work.start_time.minute.to_s)/60) / 24 : 0) %></Data></Cell>
81
+ <Cell ss:StyleID="DateTime"><Data ss:Type="Number"><%=work.completed_at && (BigDecimal((work.completed_at.to_datetime - DateTime.parse('1899-12-31T00:00:00+02:00')).to_f.to_s))%></Data></Cell>
75
82
  </Row>
76
83
  <% end %>
77
84
 
@@ -9,7 +9,7 @@
9
9
  <tr>
10
10
  <th><%=l :work_account %></th>
11
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>
12
+ <th align="center"><%=detour_to "#{l(day)} #{week_date(i+1)}", :action => :daily_work_sheet, :id => (@first_date + i).strftime('%Y-%m-%d'), :user_id => (@user != current_user ? @user : nil) %></th>
13
13
  <% end %>
14
14
  <th align="center" nowrap="true"><%=l :week %> <%=@week%></th>
15
15
  </tr>
@@ -43,4 +43,7 @@
43
43
  <th class="hours"><%=t(week_total)%></th>
44
44
  </tr>
45
45
  </table>
46
+
47
+ <%=back_or_link_to l(:back), '' %>
48
+
46
49
  </div>
@@ -20,7 +20,7 @@ test:
20
20
  driver: org.postgresql.Driver
21
21
  url: jdbc:postgresql://localhost:5432/backlog_test
22
22
  database: backlog_test
23
- username: root
23
+ username: cruisecontrol
24
24
  host: localhost
25
25
  #<% else %>
26
26
  adapter: postgresql
@@ -110,10 +110,10 @@ require 'version_from_history'
110
110
  require 'user_system'
111
111
  require 'url_for_fix'
112
112
 
113
- if RUBY_PLATFORM !~ /i386-mswin32/
113
+ if RUBY_PLATFORM !~ /(i386-mswin32|java)/
114
114
  gem 'slave'
115
115
  require 'slave'
116
116
  work_lock_nagger_thread = Slave.object(:async=>true) {WorkLockNagger.new.nag}
117
117
  else
118
- puts 'Not spawning worklognagger on windows'
118
+ puts "Not spawning worklog nagger on #$1 platform."
119
119
  end
@@ -37,3 +37,11 @@ without_task:
37
37
  completed_at: 2007-06-13T14:35:00
38
38
  hours: 8.0
39
39
  user_id: 1000001
40
+ short:
41
+ id: 7
42
+ started_on: 2007-06-18
43
+ start_time: !time 10:00:00
44
+ completed_at: 2007-06-18T10:05:00
45
+ hours: 0.083
46
+ work_account_id: 1
47
+ user_id: 1000001
@@ -91,24 +91,4 @@ class WorkAccountsControllerTest < Test::Unit::TestCase
91
91
  }
92
92
  end
93
93
 
94
- def test_works
95
- get :works, :id => 1, :report_filter => {:start_on => '2007-04-01', :end_on => '2007-06-13'}
96
-
97
- assert_response :success
98
- assert_template 'list'
99
-
100
- assert_not_nil assigns(:works)
101
- assert_equal 4, assigns(:works).size
102
- end
103
-
104
- def test_works_excel
105
- get :works, :id => 1, :report_filter => {:start_on => '2007-04-01', :end_on => '2007-06-13'}, :export => 'excel'
106
-
107
- assert_response :success
108
- assert_template 'list_excel'
109
-
110
- assert_not_nil assigns(:works)
111
- assert_equal 4, assigns(:works).size
112
- end
113
-
114
94
  end
@@ -36,6 +36,26 @@ class WorksControllerTest < Test::Unit::TestCase
36
36
  assert_not_nil assigns(:works)
37
37
  end
38
38
 
39
+ def test_list_with_filter
40
+ get :list, :id => 1, :report_filter => {:start_on => '2007-04-01', :end_on => '2007-06-13'}
41
+
42
+ assert_response :success
43
+ assert_template 'list'
44
+
45
+ assert_not_nil assigns(:works)
46
+ assert_equal 4, assigns(:works).size
47
+ end
48
+
49
+ def test_list_excel
50
+ get :list, :id => 1, :report_filter => {:start_on => '2007-04-01', :end_on => '2007-06-13'}, :export => 'excel'
51
+
52
+ assert_response :success
53
+ assert_template 'list_excel'
54
+
55
+ assert_not_nil assigns(:works)
56
+ assert_equal 4, assigns(:works).size
57
+ end
58
+
39
59
  def test_show
40
60
  get :show, :id => 1
41
61
 
@@ -118,6 +138,15 @@ class WorksControllerTest < Test::Unit::TestCase
118
138
  assert_equal expected_time, assigns(:work).start_time
119
139
  end
120
140
 
141
+ def test_update_time_with_five_minutes
142
+ before = works(:first)
143
+
144
+ post :update_time, :id => 1, :work => {"hours_time"=>"0:05"}
145
+
146
+ assert_response :success
147
+ assert_equal BigDecimal('0.083'), assigns(:work).hours
148
+ end
149
+
121
150
  def test_update_row_with_empty_start_time
122
151
  before = works(:first)
123
152
  old_start_date = before.started_on
@@ -68,6 +68,10 @@ class WorkTest < Test::Unit::TestCase
68
68
  end
69
69
  end
70
70
 
71
+ def test_calculate_hours
72
+ assert_equal 0.083, works(:short).calculate_hours
73
+ end
74
+
71
75
  private
72
76
 
73
77
  # TODO (uwe): This method should be removed
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.31.1
4
+ version: 0.32.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-24 00:00:00 +02:00
12
+ date: 2008-05-08 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency