backlog 0.31.1 → 0.32.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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