backlog 0.24.0 → 0.25.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.
@@ -44,23 +44,13 @@ class WorksController < ApplicationController
44
44
  task = backlog.tasks_with_children.find{|t| t.description == task_description}
45
45
  params[:work][:task_id] = task.id if task
46
46
  end
47
- account_name = params[:work].delete(:work_account_name)
48
- if account_name && account_name.size > 0
49
- account = WorkAccount.find_by_name(account_name)
50
- params[:work][:work_account_id] = account.id
51
- end
52
- customer_name = params[:work].delete(:customer_name)
53
- if customer_name && customer_name.size > 0
54
- customer = Customer.find_by_name(customer_name)
55
- if (customer.nil? )
56
- customer = Customer.create!(:name => customer_name)
57
- end
58
- params[:work][:customer_id] = customer.id
59
- end
47
+ convert_work_account_param
48
+ convert_customer_param
60
49
  convert_hours_param
61
50
  @work = Work.new(params[:work])
62
51
  @work.started_on ||= Date.today
63
52
  @work.completed_at = Time.now unless @work.start_time || @work.completed_at
53
+ @work.calculate_hours! unless @work.hours && @work.hours.to_f > 0
64
54
  @work.user_id = current_user.id
65
55
  end
66
56
  if @work && @work.save
@@ -76,6 +66,28 @@ class WorksController < ApplicationController
76
66
  back_or_redirect_to :controller => 'periods', :action => 'show', :id => @work.task && @work.task.period, :task_id => @work.task && @work.task.id
77
67
  end
78
68
 
69
+
70
+ def convert_work_account_param
71
+ account_name = params[:work].delete :work_account_name
72
+ if account_name && account_name.size > 0
73
+ account = WorkAccount.find_by_name account_name
74
+ params[:work][:work_account_id] = account.id
75
+ end
76
+ end
77
+ private :convert_work_account_param
78
+
79
+ def convert_customer_param
80
+ customer_name = params[:work].delete :customer_name
81
+ if customer_name && customer_name.size > 0
82
+ customer = Customer.find_by_name customer_name
83
+ if (customer.nil?)
84
+ customer = Customer.create! :name => customer_name
85
+ end
86
+ params[:work][:customer_id] = customer.id
87
+ end
88
+ end
89
+ private :convert_customer_param
90
+
79
91
  def edit
80
92
  @work ||= Work.find(params[:id])
81
93
  @work.attributes = params[:work]
@@ -103,11 +115,11 @@ class WorksController < ApplicationController
103
115
  def update_row
104
116
  if update_work
105
117
  #flash.discard
106
- @next_field = params[:next_field] || 'work_account_name'
118
+ @next_field = params[:next_field] || 'description'
107
119
  works = Work.find_work_for_day((@work.started_at || @work.completed_at).to_date)
108
120
  @day_total = works.inject(BigDecimal('0')){|total,work|total+=work.hours}
109
121
  else
110
- @next_field = params[:field] || 'work_account_name'
122
+ @next_field = params[:field] || 'description'
111
123
  end
112
124
  @work_accounts = WorkAccount.find(:all, :order => :name)
113
125
  @customers = Customer.find(:all, :order => :name)
@@ -121,7 +133,11 @@ class WorksController < ApplicationController
121
133
 
122
134
  def update_work
123
135
  @work = Work.find(params[:id])
124
- convert_hours_param
136
+ unless convert_hours_param
137
+ flash[:notice] = "Illegal time format"
138
+ @work.errors.add :hours_time, "Illegal time format."
139
+ return false
140
+ end
125
141
  if @work.update_attributes(params[:work])
126
142
  if params[:work] && (params[:work][:start_time] || params[:work][:completed_at_time])
127
143
  @work.calculate_hours!
@@ -219,19 +235,24 @@ class WorksController < ApplicationController
219
235
  render :partial => '/backlogs/name_list'
220
236
  end
221
237
 
222
- def auto_complete_for_work_task_description
223
- if backlog_name = params[:backlog_name]
224
- if @backlog = Backlog.find_by_name(backlog_name)
225
- @tasks = @backlog.tasks_with_children.select {|t| t.description.downcase =~ /#{params[:work][:task_description].downcase}/}
226
- end
238
+ def auto_complete_for_work_task_id
239
+ @tasks = []
240
+ if (search_id = params[:work][:task_id].to_i) > 0
241
+ @tasks += Task.find(:all,
242
+ :conditions => [ "id = ? OR id BETWEEN ? AND ?",
243
+ search_id, search_id * 10, ((search_id+1) * 10 -1) ],
244
+ :order => 'id',
245
+ :limit => 10)
227
246
  end
228
- @tasks ||= Task.find(:all,
229
- :conditions => [ "finished_at IS NULL AND LOWER(description) LIKE ?",
230
- '%' + params[:work][:task_description].downcase + '%' ],
247
+
248
+
249
+ @tasks += Task.find(:all,
250
+ :conditions => [ "finished_at IS NULL AND LOWER(description) LIKE ?",
251
+ '%' + params[:work][:task_id].downcase + '%' ],
231
252
  :order => 'description ASC',
232
- :limit => 16)
253
+ :limit => 10)
233
254
 
234
- render :partial => 'description_list'
255
+ render :partial => 'task_id_list'
235
256
  end
236
257
 
237
258
  def calculate_hours
@@ -240,24 +261,44 @@ class WorksController < ApplicationController
240
261
  render :update do |page| page['work_hours_time'].value = new_value end
241
262
  end
242
263
 
264
+ def update_new_row
265
+ @field = params[:field]
266
+ @next_field = params[:next_field]
267
+ convert_work_account_param
268
+ convert_customer_param
269
+ raise "Unknown time format: #{params[:hours_time]}" unless convert_hours_param
270
+ @work = Work.new(params[:work])
271
+ @updated_fields = [@field]
272
+ if @work.hours == 0 && @work.start_time && @work.completed_at
273
+ @work.calculate_hours!
274
+ @updated_fields << :hours_time
275
+ end
276
+ end
277
+
243
278
  private
244
279
 
245
280
  def convert_hours_param
246
281
  if params[:work] && params[:work][:hours_time]
247
282
  params[:work][:hours_time].strip!
248
- if params[:work][:hours_time] =~ /^(\d*):(\d{2})$/
283
+ if params[:work][:hours_time].empty?
284
+ params[:work].delete(:hours_time)
285
+ return true
286
+ end
287
+ if params[:work][:hours_time] =~ /^(\d*):(\d{2})?$/
249
288
  new_hours = $1
250
289
  new_minutes = $2.to_i
251
290
  new_value_str = "#{new_hours}.#{(new_minutes / 0.6).ceil}"
252
291
  new_value = BigDecimal(new_value_str)
253
292
  params[:work][:hours] = new_value
254
- elsif params[:work][:hours_time] =~ /^(\d*(?:.\d)?)$/
255
- params[:work][:hours] = BigDecimal($1)
293
+ elsif params[:work][:hours_time] =~ /^(\d*)[.,]?(\d*)$/
294
+ params[:work][:hours] = BigDecimal("#{$1.to_i}.#{$2.to_i}")
256
295
  else
257
- raise "Unknown time format: '#{params[:work][:hours_time]}'"
296
+ #raise "Unknown time format: '#{params[:work][:hours_time]}'"
297
+ return false
258
298
  end
259
299
  params[:work].delete :hours_time
260
300
  end
301
+ return true
261
302
  end
262
303
 
263
304
  end
@@ -59,7 +59,7 @@ module ApplicationHelper
59
59
  end
60
60
 
61
61
  def t(time_as_float)
62
- "#{time_as_float.to_i}:#{'%02d' % (time_as_float.frac * 60).to_i}"
62
+ "#{time_as_float.to_i}:#{'%02d' % (time_as_float.frac * 60).ceil}"
63
63
  end
64
64
 
65
65
  def h(object)
@@ -90,6 +90,7 @@ module ApplicationHelper
90
90
  if flash[:notice]
91
91
  page.replace_html :notice, flash[:notice]
92
92
  page.visual_effect(:appear, :notice)
93
+ page.visual_effect(:highlight, :notice)
93
94
  flash.discard
94
95
  else
95
96
  page.visual_effect(:fade, :notice)
data/app/models/work.rb CHANGED
@@ -138,7 +138,7 @@ class Work < ActiveRecord::Base
138
138
  self.completed_at = nil
139
139
  return
140
140
  end
141
- raise "invalid time format: #{new_value}" unless new_value =~ /(\d{0,2}):?(\d{2})/
141
+ raise "invalid time format: #{new_value}" unless new_value =~ /^(\d{0,2}):?(\d{2})?$/
142
142
  new_hour, new_minutes = $1, $2
143
143
  t = completed_at || Time.now
144
144
  self.completed_at = Time.local(t.year, t.month, t.day, new_hour, new_minutes)
@@ -80,7 +80,7 @@
80
80
  <%= if @task.users.empty?
81
81
  l(:not_grabbed)
82
82
  elsif @task.users.include? current_user
83
- l(:grabbed)
83
+ l(:grabbed_by_you)
84
84
  end
85
85
  %>
86
86
  <%=link_to_remote l(:invite), :action => :invite, :id => @task.id %>
@@ -66,12 +66,12 @@
66
66
  <% end %>
67
67
  <% end %>
68
68
  <% unless @associates.empty? %>
69
- <tr><td>
69
+ <tr>
70
70
  <% remote_form_for :user, :url => {:action => :invite_work_lock_subscriber} do |f| %>
71
- <%=select :id, nil, @associates.map{|u| [u.name, u.id]}, {}, :name => 'id'%>
72
- <td align="left"><%=submit_tag l(:invite)%></td>
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
73
  <% end %>
74
- </td></tr>
74
+ </tr>
75
75
  <% end %>
76
76
  </table>
77
77
  <% else %>
@@ -91,7 +91,7 @@
91
91
  </tr>
92
92
  <% for date in @holidays %>
93
93
  <tr>
94
- <td align="center"><%=date%></td>
94
+ <td align="center"><%=link_to date, :controller => 'works', :action => :daily_work_sheet, :id => date %></td>
95
95
  </tr>
96
96
  <% end %>
97
97
  </table>
@@ -108,7 +108,7 @@
108
108
  </tr>
109
109
  <% for date in @sick_days %>
110
110
  <tr>
111
- <td align="center"><%=date%></td>
111
+ <td align="center"><%=link_to date, :controller => 'works', :action => :daily_work_sheet, :id => date %></td>
112
112
  </tr>
113
113
  <% end %>
114
114
  </table>
@@ -1,12 +1,7 @@
1
1
  <tr id="work_<%=@work.id%>" style="border: 1px solid black">
2
2
  <td>
3
- <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :next_field => :customer_name}, :html => {:id => "work_#{@work.id}_form"} do |f|%>
4
- <%=f.select :work_account_id, @work_accounts.map {|wa| [wa.name, wa.id]}, {}, :onchange => "form.submit()"%>
5
- <% end %>
6
- </td>
7
- <td>
8
- <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :next_field => :description} do |f|%>
9
- <%=f.select :customer_id, [['', '']] + @customers.map {|cu| [cu.name, cu.id]}, {}, :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=description', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"%>
3
+ <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :next_field => :description}, :html => {:id => "work_#{@work.id}_form"} do |f|%>
4
+ <%=f.select :work_account_id, @work_accounts.map {|wa| [wa.name, wa.id]}, {}, :id => "work_#{@work.id}_work_account_id", :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=customer_id', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"%>
10
5
  <% end %>
11
6
  </td>
12
7
  <td>
@@ -37,15 +32,27 @@
37
32
  :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=invoice', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"%>
38
33
  <% end %>
39
34
  </td>
35
+ <td align="right">
36
+ <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :next_field => :invoice} do |f|%>
37
+ <%=text_field_with_auto_complete "work_#{@work.id}", :task_id,
38
+ {:name => "work[task_id]", :value => @work.task_id, :class => :task_id, :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=invoice', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"},
39
+ {:url => url_for(:action => :auto_complete_for_work_task_id), :with => "'work[task_id]=' + $('work_#{@work.id}_task_id').value"} %>
40
+ <% if @work.task_id %>
41
+ <%=image_detour_to('task.png', "#{l :task}: #{@work.task.description}", {:controller => 'tasks', :action => :edit, :id => @work.task.id})%>
42
+ <% end %>
43
+ <% end %>
44
+ </td>
40
45
  <td style="text-align: left">
41
46
  <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id} do |f|%>
42
47
  <%=f.check_box :invoice, :id => "work_#{@work.id}_invoice", :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=invoice', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"%>
43
48
  <% end %>
44
49
  </td>
50
+ <td>
51
+ <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :next_field => :description} do |f|%>
52
+ <%=f.select :customer_id, [['', '']] + @customers.map {|cu| [cu.name, cu.id]}, {}, :id => "work_#{@work.id}_customer_id", :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?next_field=description', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)})"%>
53
+ <% end %>
54
+ </td>
45
55
  <td>
46
56
  <%=image_detour_to('delete.png', l(:delete), {:controller => 'works', :action => :destroy, :id => @work, :confirm => true}, nil, true)%>
47
- <% if @work.task %>
48
- <%=image_detour_to('task.png', "#{l :task}: #{@work.task.description}", {:controller => 'tasks', :action => :edit, :id => @work.task.id})%>
49
- <% end %>
50
57
  </td>
51
58
  </tr>
@@ -3,11 +3,11 @@
3
3
  :class => ('task_time' if field=~/_time$/),
4
4
  :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?field=#{field}&next_field=#{next_field}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)});",
5
5
  :onkeypress => "
6
- if(event.keyCode == 40) {
6
+ if(event.keyCode == 40) { // Down-arrow
7
7
  e = $('work#{"_#{next_row_id}" if next_row_id}_#{field}');
8
8
  e.focus();
9
9
  e.select();
10
- } else if(event.keyCode == 38) {
10
+ } else if(event.keyCode == 38) { // Up-arrow
11
11
  e = $('work#{"_#{previous_row_id}" if previous_row_id}_#{field}');
12
12
  e.focus();
13
13
  e.select();
@@ -0,0 +1,5 @@
1
+ <ul class="tasks" style="width: 30em; background: white">
2
+ <% for task in @tasks do -%>
3
+ <li><div style="width: 40em"><%=task.id%><span class="informal"> <%="#{h task.root_task.backlog.name}: " unless @backlog%><%=h task.description %></span></div></li>
4
+ <% end -%>
5
+ </ul>
@@ -13,7 +13,7 @@
13
13
  <div id="absence">
14
14
  <% form_for :absence, :html => {:id => :absence_form}, :url => with_detour({:controller => 'absences', :action => :create}) do |f| %>
15
15
  <%=f.hidden_field :on, :value => @date %>
16
- <%=f.radio_button :reason, '', :disabled => !@works.empty?, :onchange => 'form.submit()' %><label for="absence_reason_">Work day</label>
16
+ <%=f.radio_button :reason, '', :disabled => !@works.empty? && (@absence.nil? || @absence.reason.nil? || @absence.reason.empty?), :onchange => 'form.submit()' %><label for="absence_reason_">Work day</label>
17
17
  <%=f.radio_button :reason, 'HOLIDAY', :disabled => !@works.empty?, :onchange => 'form.submit()' %><label for="absence_reason_holiday">Holiday</label>
18
18
  <%=f.radio_button :reason, 'SICK', :disabled => !@works.empty?, :onchange => 'form.submit()' %><label for="absence_reason_sick">Sick day</label>
19
19
  <% end %>
@@ -21,43 +21,44 @@
21
21
 
22
22
  <br clear="all" />
23
23
 
24
- <table sstyle="width: 100%" border="0">
24
+ <table>
25
25
 
26
26
  <% if @absence.nil? || @works.size > 0 %>
27
27
  <tr>
28
28
  <th><%=l :account %></th>
29
- <th><%=l :customer %></th>
30
29
  <th><%=l :description %></th>
31
- <th><%=l :started_at %></th>
30
+ <th id="start_time_header"><%=l :started_at %></th>
32
31
  <th><%=l :completed_at %></th>
33
32
  <th><%=l :done %></th>
34
- <th><%=l :invoice %></th>
35
- <th/>
33
+ <th><%=l :task %></th>
34
+ <th id="invoice_header"><%=l :invoice %></th>
35
+ <th><%=l :customer %></th>
36
+ <th id="icons_header" />
36
37
  </tr>
37
38
  <% end %>
38
39
 
39
- <% day_total = 0 %>
40
+ <% day_total = BigDecimal('0.0') %>
40
41
  <% @works.each_with_index do |@work, i| %>
41
42
  <% day_total += @work.hours if @work %>
42
43
  <%=render :partial => 'row', :locals => {:next_row_id => @works[i+1] && @works[i+1].id,
43
44
  :previous_row_id => @works[i-1] && @works[i-1].id} %>
44
45
  <% end %>
46
+ </table>
47
+
45
48
  <% last_work = @work %>
46
49
 
47
50
  <% unless @absence %>
48
51
  <% @work = nil %>
49
52
 
50
- <% form_tag with_detour(:controller => 'works', :action => 'create') do %>
53
+ <% form_tag with_detour(:controller => 'works', :action => 'create'), :id => "work_form" do %>
51
54
  <%=submit_tag('checkmark', :value => l(:save), :style => 'display: none')%>
52
- <%=hidden_field :work, :completed_at, :value => @date %>
55
+ <%=hidden_field :work, :started_on, :value => @date %>
53
56
 
57
+ <table>
54
58
  <tr>
55
- <td valign="bottom">
59
+ <td id="account_cell" valign="bottom">
56
60
  <%=text_field_with_auto_complete :work, :work_account_name, {:value => '', :size => 16, :class => :task_description}, {:delay => "0.01"} %>
57
61
  </td>
58
- <td valign="bottom">
59
- <%=text_field_with_auto_complete :work, :customer_name, {:value => '', :size => 16, :class => :task_description} %>
60
- </td>
61
62
  <td valign="bottom">
62
63
  <%=text_field_with_auto_complete :work, :description, :class => :task_description,
63
64
  :onkeypress => "
@@ -75,7 +76,7 @@
75
76
  }"
76
77
  %>
77
78
  </td>
78
- <td align="right" valign="bottom">
79
+ <td id="work_start_time_cell" align="right" valign="bottom">
79
80
  <%=text_field :work, :start_time, :value => last_work && last_work.completed_at && last_work.completed_at.strftime('%H:%M'), :class => 'task_time',
80
81
  :onkeypress => "
81
82
  if(event.keyCode == 38) {
@@ -93,7 +94,10 @@
93
94
  %>
94
95
  </td>
95
96
  <td align="left" valign="bottom">
97
+ <% field = 'completed_at_time'; next_field = 'hours_time' %>
96
98
  <%=text_field :work, :completed_at_time, :class => 'task_time',
99
+ :onchange => remote_function(:url => {:action => :update_new_row, :field => field,
100
+ :next_field => next_field}, :with => "Form.serialize(form)"),
97
101
  :onkeypress => "
98
102
  if(event.keyCode == 38) {
99
103
  e = $('work#{"_#{last_work.id}" if last_work}_completed_at_time');
@@ -107,8 +111,7 @@
107
111
  return false;
108
112
  }
109
113
  }",
110
- :value => @work && @work.completed_at.strftime('%H:%M'),
111
- :onchange => "update_hours(this.form);"
114
+ :value => @work && @work.completed_at_time.strftime('%H:%M')
112
115
  %>
113
116
  </td>
114
117
  <td align="right" valign="bottom">
@@ -128,10 +131,17 @@
128
131
  }"
129
132
  %>
130
133
  </td>
131
- <td align="left" valign="bottom">
134
+ <td valign="bottom">
135
+ <%=text_field_with_auto_complete :work, :task_id, {:value => '', :size => 16, :class => :task_id} %>
136
+ <%=image_tag 'task.png'%>
137
+ </td>
138
+ <td id="work_invoice_cell" align="left" valign="bottom">
132
139
  <%=check_box :work, :invoice %>
133
140
  </td>
134
- <td valign="bottom">
141
+ <td valign="bottom">
142
+ <%=text_field_with_auto_complete :work, :customer_name, {:value => '', :size => 16, :class => :task_description} %>
143
+ </td>
144
+ <td id="work_icons_cell" valign="bottom">
135
145
  </td>
136
146
  </tr>
137
147
  <% end %>
@@ -141,8 +151,8 @@
141
151
  <th><%=l :totals %></th>
142
152
  <th/>
143
153
  <th/>
154
+ <th id="hours_total" class="hours"><%=t(day_total) %></th>
144
155
  <th/>
145
- <th id="hours_total" class="hours"><%='%d:%02d' % [day_total.to_i, 60 * (day_total % 1)] %></th>
146
156
  <th/>
147
157
  </tr>
148
158
  </table>
@@ -162,39 +172,18 @@
162
172
 
163
173
  <script type="text/JavaScript">
164
174
  //<!--
165
- function update_hours(form, id) {
166
- var id_str;
167
- if(!id) {
168
- id_str = '';
169
- } else {
170
- id_str = '_' + id;
171
- }
172
-
173
- var started_at = $('work' + id_str + '_start_time');
174
- var completed_at = $('work' + id_str + '_completed_at_time');
175
- var hours_field = $('work' + id_str + '_hours_time');
176
-
177
- if (started_at.value.length >= 4 && completed_at.value.length >= 4){
178
- var start_hours = started_at.value.substr(0,2);
179
- var start_minutes = started_at.value.substr(3,5);
180
- var end_hours = completed_at.value.substr(0,2);
181
- var end_minutes = completed_at.value.substr(3,5);
182
-
183
- var hours = end_hours - start_hours;
184
- var minutes = end_minutes - start_minutes;
185
- if ( minutes < 0){
186
- hours--;
187
- minutes += 60;
188
- }
189
-
190
- hours_field.value = '' + hours + ':' + (minutes < 10 ? '0' : '') + minutes;
191
- }
192
- }
193
-
175
+ <% if last_work %>
176
+ $('work_work_account_name').style.width = $('work_<%=last_work.id%>_work_account_id').clientWidth - 6 + 'px';
177
+ $('work_description').style.width = $('work_<%=last_work.id%>_description').clientWidth - 2 + 'px';
178
+ $('work_start_time_cell').style.width = $('start_time_header').clientWidth - 2 + 'px';
179
+ $('work_invoice_cell').style.width = $('invoice_header').clientWidth - 2 + 'px';
180
+ $('work_customer_name').style.width = $('work_<%=last_work.id%>_customer_id').clientWidth + 10 + 'px';
181
+ $('work_icons_cell').style.width = $('icons_header').clientWidth - 2 + 'px';
182
+ <% end %>
194
183
  <% if last_work.completed_at %>
195
- start_field = $('work_work_account_name')
184
+ start_field = $('work_work_account_name');
196
185
  <% else %>
197
- start_field = $('work_<%=last_work.id%>_completed_at_time')
186
+ start_field = $('work_<%=last_work.id%>_completed_at_time');
198
187
  <% end %>
199
188
  start_field.focus();
200
189
  start_field.select();