backlog 0.26.0 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,27 @@
1
+ == 0.28.0 2008-04-01
2
+
3
+ === Features
4
+
5
+ * Added public holiday.
6
+ * Added "Sick day with doctor's note".
7
+ * Added search for work and work accounts.
8
+
9
+ === Fixes
10
+
11
+ * Fixed infinite loop sending emails when starting the application after 10 o'clock on the last day of the month.
12
+ * Fixed scope error for absences. Only one person could be absent on any date.
13
+ * Added "notes" field to new wrok records.
14
+
15
+ == 0.27.0 2008-03-31
16
+
17
+ === Features
18
+
19
+ * Added a "Notes" field to work record.
20
+
21
+ === Fixes
22
+
23
+ * Fixed infinite loop sending emails when starting the application after 10 o'clock on the last day of the month.
24
+
1
25
  == 0.26.0 2008-03-28
2
26
 
3
27
  === Features
@@ -32,6 +32,10 @@ class AbsencesController < ApplicationController
32
32
  end
33
33
  back_or_redirect_to :action => 'list'
34
34
  else
35
+ if params[:absence][:reason] == ''
36
+ back_or_redirect_to :action => 'list'
37
+ return
38
+ end
35
39
  @absence = Absence.new(params[:absence])
36
40
  @absence.user_id = current_user.id
37
41
  if @absence.save
@@ -101,18 +101,20 @@ class ApplicationController < ActionController::Base
101
101
  end
102
102
  end
103
103
 
104
- def back_or_redirect_to(options)
105
- if session[:detours]
106
- detour = pop_detour
107
- post = detour.delete(:request_method) == :post
108
- if post
109
- redirect_to_post(detour)
110
- else
111
- redirect_to detour
112
- end
104
+ def back
105
+ return false if session[:detours].nil?
106
+ detour = pop_detour
107
+ post = detour.delete(:request_method) == :post
108
+ if post
109
+ redirect_to_post(detour)
113
110
  else
114
- redirect_to options
111
+ redirect_to detour
115
112
  end
113
+ return true
114
+ end
115
+
116
+ def back_or_redirect_to(options)
117
+ back or redirect_to options
116
118
  end
117
119
 
118
120
  def pop_detour
@@ -0,0 +1,70 @@
1
+ class PublicHolidaysController < ApplicationController
2
+ def index
3
+ list
4
+ render :action => 'list'
5
+ end
6
+
7
+ # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
8
+ verify :method => :post, :only => [ :destroy, :create, :update ],
9
+ :redirect_to => { :action => :list }
10
+
11
+ def list
12
+ @public_holiday_pages, @public_holidays = paginate :public_holidays, :per_page => 10
13
+ end
14
+
15
+ def show
16
+ @public_holiday = PublicHoliday.find(params[:id])
17
+ end
18
+
19
+ def new
20
+ @public_holiday = PublicHoliday.new
21
+ end
22
+
23
+ def create
24
+ @public_holiday = PublicHoliday.new(params[:public_holiday])
25
+ if @public_holiday.save
26
+ flash[:notice] = 'PublicHoliday was successfully created.'
27
+ redirect_to :action => 'list'
28
+ else
29
+ render :action => 'new'
30
+ end
31
+ end
32
+
33
+ def edit
34
+ @public_holiday = PublicHoliday.find(params[:id])
35
+ end
36
+
37
+ def update
38
+ @public_holiday = PublicHoliday.find(params[:id])
39
+ if @public_holiday.update_attributes(params[:public_holiday])
40
+ flash[:notice] = 'PublicHoliday was successfully updated.'
41
+ redirect_to :action => 'show', :id => @public_holiday
42
+ else
43
+ render :action => 'edit'
44
+ end
45
+ end
46
+
47
+ def destroy
48
+ PublicHoliday.find(params[:id]).destroy
49
+ redirect_to :action => 'list'
50
+ end
51
+
52
+ def mark
53
+ if params[:public_holiday]
54
+ on = params[:public_holiday][:on]
55
+ marked = params[:public_holiday][:marked] == '1'
56
+ @public_holiday = PublicHoliday.find(:first, :conditions => {:on => on})
57
+ if marked
58
+ if @public_holiday.nil?
59
+ PublicHoliday.create!(:on => on)
60
+ end
61
+ else
62
+ if @public_holiday
63
+ @public_holiday.destroy
64
+ end
65
+ end
66
+ end
67
+ back_or_redirect_to :controller => 'works', :action => :daily_work_sheet
68
+ end
69
+
70
+ end
@@ -5,9 +5,13 @@ class SearchController < ApplicationController
5
5
  @search = params[:q]
6
6
  @backlogs = Backlog.find(:all, :conditions => ["lower(name) LIKE ?", "%#{@search.downcase}%"])
7
7
  @tasks = Task.find(:all, :conditions => ["lower(description) LIKE ? OR lower(notes) LIKE ?", "%#{@search.downcase}%", "%#{@search.downcase}%"])
8
+ @work_accounts = WorkAccount.find(:all, :conditions => ["lower(name) LIKE ?", "%#{@search.downcase}%"])
9
+ @works = Work.find(:all, :conditions => ["lower(description) LIKE ? OR lower(notes) LIKE ?", "%#{@search.downcase}%", "%#{@search.downcase}%"])
8
10
  else
9
11
  @backlogs = []
10
12
  @tasks = []
13
+ @work_accounts = []
14
+ @works = []
11
15
  end
12
16
  end
13
17
  end
@@ -57,11 +57,16 @@ class WorksController < ApplicationController
57
57
  @work.calculate_hours! unless @work.hours && @work.hours.to_f > 0
58
58
  @work.user_id = current_user.id
59
59
  end
60
- if @work && @work.save
60
+ if @work && (@work.description || @work.notes || @work.start_time || @work.completed_at) && @work.save
61
61
  flash[:notice] = 'Work was successfully created.'
62
+ @work.task.grab if @work.task
62
63
  else
63
- new
64
- render :action => 'new'
64
+ if params[:detour]
65
+ back
66
+ else
67
+ new
68
+ render :action => 'new'
69
+ end
65
70
  return
66
71
  end
67
72
 
@@ -120,7 +125,7 @@ class WorksController < ApplicationController
120
125
  if update_work
121
126
  #flash.discard
122
127
  @next_field = params[:next_field] || 'description'
123
- works = Work.find_work_for_day((@work.started_at || @work.completed_at).to_date)
128
+ works = Work.find_work_for_day((@work.started_on || @work.completed_at).to_date)
124
129
  @day_total = works.inject(BigDecimal('0')){|total,work|total+=work.hours}
125
130
  else
126
131
  @next_field = params[:field] || 'description'
@@ -181,6 +186,7 @@ class WorksController < ApplicationController
181
186
  @work = Work.new(:started_at => Time.now, :completed_at => Time.now)
182
187
  @work_accounts = WorkAccount.find(:all, :order => :name)
183
188
  @absence = Absence.find(:first, :conditions => {:on => @date, :user_id => current_user.id})
189
+ @public_holiday = PublicHoliday.find(:first, :conditions => {:on => @date})
184
190
  render :layout => 'wide'
185
191
  end
186
192
 
@@ -202,6 +208,10 @@ class WorksController < ApplicationController
202
208
  @first_date = Date.commercial(@year, @week, 1)
203
209
  @last_date = @first_date + 6
204
210
  @lock = WorkLock.find(:first, :conditions => ['user_id = ? AND start_on <= ? AND end_on >= ?', current_user.id, @first_date, @last_date])
211
+ @absences = Absence.find(:all, :conditions => ['"on" BETWEEN ? AND ?', @first_date, @last_date], :order => '"on"')
212
+ (0..6).each do |day|
213
+ @absences.insert(day, nil) if @absences[day] && @absences[day].on > @first_date + day
214
+ end
205
215
  render :layout => 'wide'
206
216
  end
207
217
 
@@ -0,0 +1,2 @@
1
+ module PublicHolidaysHelper
2
+ end
@@ -3,12 +3,15 @@ class Absence < ActiveRecord::Base
3
3
  validates_presence_of :on
4
4
  validates_presence_of :reason
5
5
 
6
- validates_uniqueness_of :on
6
+ validates_uniqueness_of :on, :scope => :user_id
7
7
 
8
8
  def validate
9
9
  if Work.exists? ['user_id = ? AND (started_on = ? OR (started_on <= ? AND completed_at IS NOT NULL AND completed_at >= ?))', user_id, on, on, on]
10
10
  errors.add :on, "You have already registered work on this date."
11
11
  end
12
+ if PublicHoliday.exists? :on => on
13
+ errors.add :on, "You cannot mark absence on a public holiday."
14
+ end
12
15
  end
13
16
 
14
17
  end
@@ -0,0 +1,2 @@
1
+ class PublicHoliday < ActiveRecord::Base
2
+ end
@@ -134,7 +134,8 @@ class Work < ActiveRecord::Base
134
134
  end
135
135
 
136
136
  def completed_at_time=(new_value)
137
- if new_value == ''
137
+ new_value.strip!
138
+ if new_value.empty?
138
139
  self.completed_at = nil
139
140
  return
140
141
  end
@@ -20,8 +20,10 @@ class WorkLockNagger
20
20
  url = url_for(:host => host, :port => port, :controller => 'works', :action => :weekly_work_sheet_by_work_account)
21
21
  end
22
22
  rescue Exception => e
23
+ puts "Work Lock Nagger Exception"
23
24
  puts e.message
24
25
  puts e.backtrace
26
+ exit 1
25
27
  end
26
28
 
27
29
  loop do
@@ -39,14 +41,18 @@ class WorkLockNagger
39
41
  WorkLockNotify.deliver_nag(u, week, url + "/#{year}/#{week}")
40
42
  end
41
43
  now = Time.now
42
- next_nag_time = Time.local(now.year, now.month, now.hour < 10 ? now.day : now.day + 1, 10, 0, 0)
44
+ next_nag_time = Time.local(now.year, now.month, now.day, 10, 0, 0)
45
+ next_nag_time += 1.day if next_nag_time <= now
43
46
  sleep_duration = next_nag_time - now
44
47
  puts "Sleeping #{sleep_duration.to_i / 3600} hours #{(sleep_duration.to_i % 3600) / 60} minutes and #{sleep_duration.to_i % 60} seconds."
48
+ puts "From #{now} to #{next_nag_time}."
45
49
  sleep sleep_duration
46
50
  rescue Exception => e
47
51
  p e
52
+ puts e.backtrace
53
+ sleep 5.minutes
48
54
  end
49
- puts "Nag ends"
55
+ puts "Nag ends"; STDOUT.flush
50
56
  end
51
57
  end
52
58
 
@@ -0,0 +1,7 @@
1
+ <%= error_messages_for 'public_holiday' %>
2
+
3
+ <!--[form:public_holiday]-->
4
+ <p><label for="public_holiday_on">On</label><br/>
5
+ <%= date_select 'public_holiday', 'on' %></p>
6
+ <!--[eoform:public_holiday]-->
7
+
@@ -0,0 +1,9 @@
1
+ <h1>Editing public_holiday</h1>
2
+
3
+ <% form_tag :action => 'update', :id => @public_holiday do %>
4
+ <%= render :partial => 'form' %>
5
+ <%= submit_tag 'Edit' %>
6
+ <% end %>
7
+
8
+ <%= link_to 'Show', :action => 'show', :id => @public_holiday %> |
9
+ <%= link_to 'Back', :action => 'list' %>
@@ -0,0 +1,27 @@
1
+ <h1>Listing public_holidays</h1>
2
+
3
+ <table>
4
+ <tr>
5
+ <% for column in PublicHoliday.content_columns %>
6
+ <th><%= column.human_name %></th>
7
+ <% end %>
8
+ </tr>
9
+
10
+ <% for public_holiday in @public_holidays %>
11
+ <tr>
12
+ <% for column in PublicHoliday.content_columns %>
13
+ <td><%=h public_holiday.send(column.name) %></td>
14
+ <% end %>
15
+ <td><%= link_to 'Show', :action => 'show', :id => public_holiday %></td>
16
+ <td><%= link_to 'Edit', :action => 'edit', :id => public_holiday %></td>
17
+ <td><%= link_to 'Destroy', { :action => 'destroy', :id => public_holiday }, :confirm => 'Are you sure?', :method => :post %></td>
18
+ </tr>
19
+ <% end %>
20
+ </table>
21
+
22
+ <%= link_to 'Previous page', { :page => @public_holiday_pages.current.previous } if @public_holiday_pages.current.previous %>
23
+ <%= link_to 'Next page', { :page => @public_holiday_pages.current.next } if @public_holiday_pages.current.next %>
24
+
25
+ <br />
26
+
27
+ <%= link_to 'New public_holiday', :action => 'new' %>
@@ -0,0 +1,8 @@
1
+ <h1>New public_holiday</h1>
2
+
3
+ <% form_tag :action => 'create' do %>
4
+ <%= render :partial => 'form' %>
5
+ <%= submit_tag "Create" %>
6
+ <% end %>
7
+
8
+ <%= link_to 'Back', :action => 'list' %>
@@ -0,0 +1,8 @@
1
+ <% for column in PublicHoliday.content_columns %>
2
+ <p>
3
+ <b><%= column.human_name %>:</b> <%=h @public_holiday.send(column.name) %>
4
+ </p>
5
+ <% end %>
6
+
7
+ <%= link_to 'Edit', :action => 'edit', :id => @public_holiday %> |
8
+ <%= link_to 'Back', :action => 'list' %>
@@ -1,5 +1,27 @@
1
1
  <% @page_title = l(:search_results_for, @search) %>
2
2
 
3
+ <div id="lfeature">
4
+ <div class="btitle">
5
+ <h4><%=l(:backlogs) %></h4>
6
+ </div>
7
+ <ul>
8
+ <% @backlogs.each do |backlog| %>
9
+ <li><%=link_to backlog.name, :controller => 'backlogs', :action => :show, :id => backlog%></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+
14
+ <div id="rfeature">
15
+ <div class="btitle">
16
+ <h4><%=l(:work_accounts) %></h4>
17
+ </div>
18
+ <ul>
19
+ <% @work_accounts.each do |work_account| %>
20
+ <li><%=link_to task.description, :controller => 'work_accounts', :action => :edit, :id => work_account%></li>
21
+ <% end %>
22
+ </ul>
23
+ </div>
24
+
3
25
  <div id="lfeature">
4
26
  <div class="btitle">
5
27
  <h4><%=l(:tasks) %></h4>
@@ -13,11 +35,17 @@
13
35
 
14
36
  <div id="rfeature">
15
37
  <div class="btitle">
16
- <h4><%=l(:backlogs) %></h4>
38
+ <h4><%=l(:work) %></h4>
17
39
  </div>
18
40
  <ul>
19
- <% @backlogs.each do |backlog| %>
20
- <li><%=link_to backlog.name, :controller => 'backlogs', :action => :show, :id => backlog%></li>
41
+ <% @works.each do |work| %>
42
+ <li><%=link_to "#{work.description && !work.description.empty? ? work.description : l(:empty_work_record)}", :controller => 'works', :action => :edit, :id => work%></li>
21
43
  <% end %>
22
44
  </ul>
23
- </div>
45
+ </div>
46
+
47
+ <script type="text/javascript">
48
+ f = $('q');
49
+ f.focus();
50
+ f.select();
51
+ </script>
@@ -0,0 +1,141 @@
1
+ <% form_tag with_detour(:controller => 'works', :action => 'create'), :id => "work_form" do %>
2
+ <%=submit_tag('checkmark', :value => l(:save), :style => 'display: none')%>
3
+ <%=hidden_field :work, :started_on, :value => @date %>
4
+
5
+ <tr>
6
+ <td id="account_cell" valign="bottom">
7
+ <%=select :work, :work_account_id, [['', '']] + @work_accounts.map {|wa| [wa.name, wa.id]}, :value => '' %>
8
+ </td>
9
+ <td valign="bottom">
10
+ <%=text_field_with_auto_complete :work, :description, :class => :task_description,
11
+ :onkeypress => "
12
+ if(event.ctrlKey && event.keyCode == 40) { // CTRL-Down-arrow
13
+ $('work_notes_div').style.visibility = 'visible';
14
+ e = $('work_notes');
15
+ e.focus();
16
+ // e.select();
17
+ } else if(event.keyCode == 38) {
18
+ e = $('work#{"_#{last_work.id}" if last_work}_description');
19
+ e.focus();
20
+ e.select();
21
+ } else if(event.keyCode == 13) {
22
+ e = $('work_work_account_id');
23
+ if(e.value == '') {
24
+ e.focus();
25
+ e.select();
26
+ return false;
27
+ }
28
+ }"
29
+ %>
30
+ <br clear="all"/>
31
+ <% field = 'notes' %>
32
+ <% next_field = 'description' %>
33
+ <div id="work_notes_div" style="visibility: hidden; position: absolute; opacity: .8">
34
+ <%=text_area :work, :notes, :id => "work_#{field}",
35
+ #:onchange => "new Ajax.Request('/works/update_new_row?field=#{field}&next_field=#{next_field}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)});",
36
+ :onkeypress => "
37
+ if(event.ctrlKey && event.keyCode == 38) { // CTRL-UP-arrow
38
+ e = $('work_description');
39
+ e.focus();
40
+ //e.select();
41
+ } else if(event.keyCode == 38) { // Up-arrow
42
+ notes_field = $('work_#{field}');
43
+ if (notes_field.selectionStart == 0 || notes_field.value.indexOf('\\n') == -1 || notes_field.value.indexOf('\\n') > notes_field.selectionStart) {
44
+ description_field = $('work_description');
45
+ description_field.focus();
46
+ //description_field.select();
47
+ return false;
48
+ }
49
+ }",
50
+ :onBlur => "
51
+ $('work_notes_div').style.visibility = 'hidden';
52
+ ",
53
+ :cols => 80
54
+ %>
55
+ </div>
56
+ </td>
57
+ <td id="work_start_time_cell" align="right" valign="bottom">
58
+ <% field = 'start_time'; next_field = 'completed_at_time' %>
59
+ <%=text_field :work, :start_time, :value => last_work && last_work.completed_at && last_work.completed_at.strftime('%H:%M'), :class => 'task_time',
60
+ :onchange => remote_function(:url => {:action => :update_new_row, :field => field,
61
+ :next_field => next_field},
62
+ :with => "
63
+ 'work[started_on]=' + work_started_on.value
64
+ + '&work[start_time]=' + work_start_time.value
65
+ + '&work[completed_at_time]=' + work_completed_at_time.value
66
+ + '&work[hours_time]=' + work_hours_time.value
67
+ "
68
+ ),
69
+ :onkeypress => "
70
+ if(event.keyCode == 38) {
71
+ e = $('work#{"_#{last_work.id}" if last_work}_start_time');
72
+ e.focus();
73
+ e.select();
74
+ } else if(event.keyCode == 13) {
75
+ e = $('work_work_account_id');
76
+ if(e.value == '') {
77
+ e.focus();
78
+ e.select();
79
+ return false;
80
+ }
81
+ }"
82
+ %>
83
+ </td>
84
+ <td align="left" valign="bottom">
85
+ <% field = 'completed_at_time'; next_field = 'hours_time' %>
86
+ <%=text_field :work, :completed_at_time, :class => 'task_time',
87
+ :onchange => remote_function(:url => {:action => :update_new_row, :field => field,
88
+ :next_field => next_field},
89
+ :with => "
90
+ 'work[started_on]=' + work_started_on.value
91
+ + '&work[start_time]=' + work_start_time.value
92
+ + '&work[completed_at_time]=' + work_completed_at_time.value
93
+ + '&work[hours_time]=' + work_hours_time.value
94
+ "
95
+ ),
96
+ :onkeypress => "
97
+ if(event.keyCode == 38) {
98
+ e = $('work#{"_#{last_work.id}" if last_work}_completed_at_time');
99
+ e.focus();
100
+ e.select();
101
+ } else if(event.keyCode == 13) {
102
+ e = $('work_work_account_id');
103
+ if(e.value == '') {
104
+ e.focus();
105
+ e.select();
106
+ return false;
107
+ }
108
+ }",
109
+ :value => @work && @work.completed_at_time.strftime('%H:%M')
110
+ %>
111
+ </td>
112
+ <td align="right" valign="bottom">
113
+ <%=text_field :work, :hours_time, :value => '', :class => 'task_hours',
114
+ :onkeypress => "
115
+ if(event.keyCode == 38) {
116
+ e = $('work#{"_#{last_work.id}" if last_work}_hours_time');
117
+ e.focus();
118
+ e.select();
119
+ } else if(event.keyCode == 13) {
120
+ e = $('work_work_account_id');
121
+ if(e.value == '') {
122
+ e.focus();
123
+ e.select();
124
+ return false;
125
+ }
126
+ }"
127
+ %>
128
+ </td>
129
+ <td valign="bottom">
130
+ <%=text_field_with_auto_complete :work, :task_id, {:value => '', :size => 16, :class => :task_id} %>
131
+ </td>
132
+ <td id="work_invoice_cell" align="left" valign="bottom">
133
+ <%=check_box :work, :invoice %>
134
+ </td>
135
+ <td valign="bottom">
136
+ <%=text_field_with_auto_complete :work, :customer_name, {:value => '', :size => 16, :class => :task_description} %>
137
+ </td>
138
+ <td id="work_icons_cell" valign="bottom">
139
+ </td>
140
+ </tr>
141
+ <% end %>
@@ -5,8 +5,58 @@
5
5
  <% end %>
6
6
  </td>
7
7
  <td>
8
- <%=render :partial => 'row_field', :locals => {:field => 'description',
9
- :next_field => 'start_time', :next_row_id => next_row_id, :previous_row_id => previous_row_id} %>
8
+ <% field = 'description' %>
9
+ <% next_field = 'start_time' %>
10
+ <% previous_row_id = previous_row_id %>
11
+ <% remote_form_for :work, :url => {:action => :update_row, :id => @work.id, :field=>field, :next_field => next_field} do |f|%>
12
+ <%=text_field :work, field, :id => "work_#{@work.id}_#{field}",
13
+ :class => ('task_time' if field=~/_time$/),
14
+ :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?field=#{field}&next_field=#{next_field}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)});",
15
+ :onkeypress => "
16
+ if(event.ctrlKey && event.keyCode == 40) { // CTRL-Down-arrow
17
+ $('work#{"_#{@work.id}"}_notes_div').style.visibility = 'visible';
18
+ e = $('work_#{@work.id}_notes');
19
+ e.focus();
20
+ // e.select();
21
+ } else if(event.keyCode == 40) { // Down-arrow
22
+ e = $('work#{"_#{next_row_id}" if next_row_id}_#{field}');
23
+ e.focus();
24
+ e.select();
25
+ } else if(event.keyCode == 38) { // Up-arrow
26
+ e = $('work#{"_#{previous_row_id}" if previous_row_id}_#{field}');
27
+ e.focus();
28
+ e.select();
29
+ }",
30
+ :value => (field=~/_time$/ ? @work.send(field) && @work.send(field).strftime('%H:%M') : @work.send(field) )
31
+ %>
32
+ <% field = 'notes' %>
33
+ <% next_field = 'description' %>
34
+ <div id="work_<%=@work.id%>_notes_div" style="visibility: hidden; position: absolute; opacity: .8">
35
+ <%=text_area :work, :notes, :id => "work_#{@work.id}_#{field}",
36
+ :onchange => "new Ajax.Request('/works/update_row/#{@work.id}?field=#{field}&next_field=#{next_field}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(form)});",
37
+ :onkeypress => "
38
+ if(event.ctrlKey && event.keyCode == 38) { // CTRL-UP-arrow
39
+ e = $('work_#{@work.id}_description');
40
+ e.focus();
41
+ //e.select();
42
+ } else if(event.keyCode == 38) { // Up-arrow
43
+ notes_field = $('work_#{@work.id}_#{field}');
44
+ if (notes_field.selectionStart == 0 || notes_field.value.indexOf('\\n') == -1 || notes_field.value.indexOf('\\n') > notes_field.selectionStart) {
45
+ description_field = $('work_#{@work.id}_description');
46
+ description_field.focus();
47
+ //description_field.select();
48
+ return false;
49
+ }
50
+ }",
51
+ :onBlur => "
52
+ $('work#{"_#{@work.id}"}_notes_div').style.visibility = 'hidden';
53
+ ",
54
+ :value => @work.send(field),
55
+ :cols => 80
56
+ %>
57
+ </div>
58
+ <% end %>
59
+
10
60
  </span>
11
61
  <td align="right">
12
62
  <%=render :partial => 'row_field', :locals => {:field => 'start_time',
@@ -10,14 +10,31 @@
10
10
 
11
11
  <br clear="all" />
12
12
 
13
- <div id="absence">
14
- <% form_for :absence, :html => {:id => :absence_form}, :url => with_detour({:controller => 'absences', :action => :create}) do |f| %>
15
- <%=f.hidden_field :on, :value => @date %>
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
- <%=f.radio_button :reason, 'HOLIDAY', :disabled => !@works.empty?, :onchange => 'form.submit()' %><label for="absence_reason_holiday">Holiday</label>
18
- <%=f.radio_button :reason, 'SICK', :disabled => !@works.empty?, :onchange => 'form.submit()' %><label for="absence_reason_sick">Sick day</label>
19
- <% end %>
20
- </div>
13
+ <table>
14
+ <tr>
15
+ <td>
16
+ <div id="absence">
17
+ <% work_disabled = !@works.empty? && (@absence.nil? || @absence.reason.nil? || @absence.reason.empty?) %>
18
+ <% absence_disabled = !@works.empty? || @public_holiday %>
19
+ <% form_for :absence, :html => {:id => :absence_form}, :url => with_detour({:controller => 'absences', :action => :create}) do |f| %>
20
+ <%=f.hidden_field :on, :value => @date %>
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
+ <%=f.radio_button :reason, 'HOLIDAY', :disabled => absence_disabled, :onchange => 'form.submit()' %><label for="absence_reason_holiday"><%=l :holiday%></label>
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>
25
+ <% end %>
26
+ </div>
27
+ </td>
28
+ <td>
29
+ <div id="public_holiday">
30
+ <% form_for :public_holiday, :html => {:id => :public_holiday_form}, :url => with_detour({:controller => 'public_holidays', :action => :mark}) do |f| %>
31
+ <%=f.hidden_field :on, :value => @date %>
32
+ <%=f.check_box :marked, :checked => !@public_holiday.nil?, :onchange => 'form.submit()' %><label for="public_holiday_marked"><%=l :public_holiday%></label>
33
+ <% end %>
34
+ </div>
35
+ </td>
36
+ </tr>
37
+ </table>
21
38
 
22
39
  <br clear="all" />
23
40
 
@@ -48,119 +65,10 @@
48
65
 
49
66
  <% unless @absence %>
50
67
  <% @work = nil %>
51
-
52
- <% form_tag with_detour(:controller => 'works', :action => 'create'), :id => "work_form" do %>
53
- <%=submit_tag('checkmark', :value => l(:save), :style => 'display: none')%>
54
- <%=hidden_field :work, :started_on, :value => @date %>
55
-
56
- <tr>
57
- <td id="account_cell" valign="bottom">
58
- <%#=text_field_with_auto_complete :work, :work_account_name, {:value => '', :size => 16, :class => :task_description}, {:delay => "0.01"} %>
59
- <%=select :work, :work_account_id, @work_accounts.map {|wa| [wa.name, wa.id]}, :value => '' %>
60
- </td>
61
- <td valign="bottom">
62
- <%=text_field_with_auto_complete :work, :description, :class => :task_description,
63
- :onkeypress => "
64
- if(event.keyCode == 38) {
65
- e = $('work#{"_#{last_work.id}" if last_work}_description');
66
- e.focus();
67
- e.select();
68
- } else if(event.keyCode == 13) {
69
- e = $('work_work_account_name');
70
- if(e.value == '') {
71
- e.focus();
72
- e.select();
73
- return false;
74
- }
75
- }"
76
- %>
77
- </td>
78
- <td id="work_start_time_cell" align="right" valign="bottom">
79
- <% field = 'start_time'; next_field = 'completed_at_time' %>
80
- <%=text_field :work, :start_time, :value => last_work && last_work.completed_at && last_work.completed_at.strftime('%H:%M'), :class => 'task_time',
81
- :onchange => remote_function(:url => {:action => :update_new_row, :field => field,
82
- :next_field => next_field},
83
- :with => "
84
- 'work[started_on]=' + work_started_on.value
85
- + '&work[start_time]=' + work_start_time.value
86
- + '&work[completed_at_time]=' + work_completed_at_time.value
87
- + '&work[hours_time]=' + work_hours_time.value
88
- "
89
- ),
90
- :onkeypress => "
91
- if(event.keyCode == 38) {
92
- e = $('work#{"_#{last_work.id}" if last_work}_start_time');
93
- e.focus();
94
- e.select();
95
- } else if(event.keyCode == 13) {
96
- e = $('work_work_account_name');
97
- if(e.value == '') {
98
- e.focus();
99
- e.select();
100
- return false;
101
- }
102
- }"
103
- %>
104
- </td>
105
- <td align="left" valign="bottom">
106
- <% field = 'completed_at_time'; next_field = 'hours_time' %>
107
- <%=text_field :work, :completed_at_time, :class => 'task_time',
108
- :onchange => remote_function(:url => {:action => :update_new_row, :field => field,
109
- :next_field => next_field},
110
- :with => "
111
- 'work[started_on]=' + work_started_on.value
112
- + '&work[start_time]=' + work_start_time.value
113
- + '&work[completed_at_time]=' + work_completed_at_time.value
114
- + '&work[hours_time]=' + work_hours_time.value
115
- "
116
- ),
117
- :onkeypress => "
118
- if(event.keyCode == 38) {
119
- e = $('work#{"_#{last_work.id}" if last_work}_completed_at_time');
120
- e.focus();
121
- e.select();
122
- } else if(event.keyCode == 13) {
123
- e = $('work_work_account_name');
124
- if(e.value == '') {
125
- e.focus();
126
- e.select();
127
- return false;
128
- }
129
- }",
130
- :value => @work && @work.completed_at_time.strftime('%H:%M')
131
- %>
132
- </td>
133
- <td align="right" valign="bottom">
134
- <%=text_field :work, :hours_time, :value => '', :class => 'task_hours',
135
- :onkeypress => "
136
- if(event.keyCode == 38) {
137
- e = $('work#{"_#{last_work.id}" if last_work}_hours_time');
138
- e.focus();
139
- e.select();
140
- } else if(event.keyCode == 13) {
141
- e = $('work_work_account_name');
142
- if(e.value == '') {
143
- e.focus();
144
- e.select();
145
- return false;
146
- }
147
- }"
148
- %>
149
- </td>
150
- <td valign="bottom">
151
- <%=text_field_with_auto_complete :work, :task_id, {:value => '', :size => 16, :class => :task_id} %>
152
- </td>
153
- <td id="work_invoice_cell" align="left" valign="bottom">
154
- <%=check_box :work, :invoice %>
155
- </td>
156
- <td valign="bottom">
157
- <%=text_field_with_auto_complete :work, :customer_name, {:value => '', :size => 16, :class => :task_description} %>
158
- </td>
159
- <td id="work_icons_cell" valign="bottom">
160
- </td>
161
- </tr>
68
+ <%=render :partial => 'new_row', :locals => {:last_work => last_work} %>
162
69
  <% end %>
163
70
 
71
+ <% unless @works.empty? %>
164
72
  <tr>
165
73
  <td/>
166
74
  <th><%=l :totals %></th>
@@ -184,23 +92,17 @@
184
92
  <%=render :partial => '/periods/burn_down_chart' %>
185
93
  <% end %>
186
94
 
95
+ <% unless @absence %>
187
96
  <script type="text/JavaScript">
188
97
  //<!--
189
- <% if last_work %>
190
- // $('work_work_account_name').style.width = $('work_<%=last_work.id%>_work_account_id').clientWidth - 6 + 'px';
191
- // $('work_description').style.width = $('work_<%=last_work.id%>_description').clientWidth - 2 + 'px';
192
- // $('work_start_time_cell').style.width = $('start_time_header').clientWidth - 2 + 'px';
193
- // $('work_invoice_cell').style.width = $('invoice_header').clientWidth - 2 + 'px';
194
- // $('work_customer_name').style.width = $('work_<%=last_work.id%>_customer_id').clientWidth + 10 + 'px';
195
- // $('work_icons_cell').style.width = $('icons_header').clientWidth - 2 + 'px';
196
- <% end %>
197
98
  var start_field;
198
99
  <% if last_work.completed_at %>
199
- start_field = $('work_work_account_id');
100
+ start_field = $('work_work_account_id');
200
101
  <% else %>
201
- start_field = $('work_<%=last_work.id%>_completed_at_time');
102
+ start_field = $('work_<%=last_work.id%>_completed_at_time');
202
103
  <% end %>
203
104
  start_field.focus();
204
105
  start_field.select();
205
106
  //-->
206
107
  </script>
108
+ <% end %>
@@ -4,15 +4,17 @@ if @work && @work.errors.empty?
4
4
  if field_name.to_s =~ /(.*)_time$/
5
5
  attribute_name = $1
6
6
  value = @work[attribute_name] || @work[field_name]
7
- if value.is_a? BigDecimal
7
+ if value.nil?
8
+ value = ''
9
+ elsif value.is_a? BigDecimal
8
10
  value = t(value)
9
11
  else
10
12
  value = value.strftime('%H:%M')
11
13
  end
12
- page["work_" + field_name.to_s].value = value
13
14
  else
14
- page["work_" + field_name.to_s].value = @work.send(field_name).to_s
15
+ value = @work.send(field_name).to_s
15
16
  end
17
+ page["work_" + field_name.to_s].value = value
16
18
  end
17
19
  #page.replace "work_#{@work.id}", :partial => 'row', :object => @work
18
20
  #page["work_#{@work.id}_#{@next_field}"].select
@@ -36,9 +36,10 @@
36
36
  <tr>
37
37
  <th><%=l :totals%></th>
38
38
  <% (0..6).each do |day| %>
39
+ <% day_totals[day] += 8 if @absences[day] %>
39
40
  <th class="hours"><%='%d:%02d' % [day_totals[day], 60 * day_totals[day] % 60] if day_totals[day] > 0%></th>
40
41
  <% end %>
41
- <% week_total = week_totals.values.inject(0) {|total, work_account_total| total + work_account_total} %>
42
+ <% week_total = day_totals.inject(0) {|total, day_total| total + day_total} %>
42
43
  <th class="hours"><%= '%d:%02d' % [week_total, 60 * week_total % 60] %></th>
43
44
  </tr>
44
45
  </table>
@@ -0,0 +1,9 @@
1
+ class AddWorkComments < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :works, :notes, :text
4
+ end
5
+
6
+ def self.down
7
+ remove_column :works, :notes
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class CreatePublicHolidays < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :public_holidays do |t|
4
+ t.column :on, :date, :null => false
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_table :public_holidays
10
+ end
11
+ 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 => 28) do
5
+ ActiveRecord::Schema.define(:version => 30) do
6
6
 
7
7
  create_table "absences", :force => true do |t|
8
8
  t.column "user_id", :integer, :null => false
@@ -60,6 +60,10 @@ ActiveRecord::Schema.define(:version => 28) do
60
60
  t.column "party_id", :integer
61
61
  end
62
62
 
63
+ create_table "public_holidays", :force => true do |t|
64
+ t.column "on", :date, :null => false
65
+ end
66
+
63
67
  create_table "task_files", :force => true do |t|
64
68
  t.column "task_id", :integer, :null => false
65
69
  t.column "name", :string, :limit => 64, :null => false
@@ -140,6 +144,7 @@ ActiveRecord::Schema.define(:version => 28) do
140
144
  t.column "description", :string
141
145
  t.column "started_on", :date, :null => false
142
146
  t.column "start_time", :time
147
+ t.column "notes", :text
143
148
  end
144
149
 
145
150
  add_index "works", ["task_id", "completed_at"], :name => "index_works_on_task_id_and_completed_at"
@@ -26,6 +26,7 @@ done: Done
26
26
  down: Down
27
27
  edit: Edit
28
28
  editing: Editing
29
+ empty_work_record: Empty work record
29
30
  enable_customer: Enable customer
30
31
  enable_invoicing: Enable invoicing
31
32
  enable_periods: Enable Sprints
@@ -41,6 +42,7 @@ grab_task: Grab task
41
42
  grabbed_by_you: Grabbed by you
42
43
  group: Group
43
44
  groups: Groups
45
+ holiday: Holiday
44
46
  holidays_used: Holidays used
45
47
  home: Home
46
48
  hours: Hours
@@ -85,6 +87,7 @@ postponed: Postponed
85
87
  previous: Previous
86
88
  previous_abr: Prev.
87
89
  projection: Projection
90
+ public_holiday: Public Holiday
88
91
  release_task: Release task
89
92
  reopen: Reopen task
90
93
  resolution: Resolution
@@ -26,6 +26,7 @@ done: Utført
26
26
  down: Ned
27
27
  edit: Rediger
28
28
  editing: Redigerer
29
+ empty_work_record: Tom arbeidslinje
29
30
  enable_customer: Tillat kunder
30
31
  enable_invoicing: Tillat fakturering
31
32
  enable_periods: Tillat sprinter
@@ -41,6 +42,7 @@ grab_task: Grip oppgave
41
42
  grabbed_by_you: Tatt av deg
42
43
  group: Gruppe
43
44
  groups: Grupper
45
+ holiday: Ferie
44
46
  holidays_used: Brukte feriedager
45
47
  home: Hjem
46
48
  hours: Timer
@@ -85,6 +87,7 @@ postponed: Utsatt
85
87
  previous: Forrige
86
88
  previous_abr: Forrige
87
89
  projection: Projeksjon
90
+ public_holiday: Offentlig fridag
88
91
  release_task: Slipp oppgave
89
92
  reopen: Gjenåpne
90
93
  resolution: Avslutningsmåte
@@ -0,0 +1,7 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+ one:
3
+ id: 1
4
+ "on": 2008-04-01
5
+ two:
6
+ id: 2
7
+ "on": 2008-11-25
@@ -0,0 +1,93 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'public_holidays_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class PublicHolidaysController; def rescue_action(e) raise e end; end
6
+
7
+ class PublicHolidaysControllerTest < Test::Unit::TestCase
8
+ fixtures :public_holidays
9
+
10
+ def setup
11
+ @controller = PublicHolidaysController.new
12
+ @request = ActionController::TestRequest.new
13
+ @response = ActionController::TestResponse.new
14
+ @request.session[:user_id] = 1000001
15
+
16
+ @first_id = public_holidays(:one).id
17
+ end
18
+
19
+ def test_index
20
+ get :index
21
+ assert_response :success
22
+ assert_template 'list'
23
+ end
24
+
25
+ def test_list
26
+ get :list
27
+
28
+ assert_response :success
29
+ assert_template 'list'
30
+
31
+ assert_not_nil assigns(:public_holidays)
32
+ end
33
+
34
+ def test_show
35
+ get :show, :id => @first_id
36
+
37
+ assert_response :success
38
+ assert_template 'show'
39
+
40
+ assert_not_nil assigns(:public_holiday)
41
+ assert assigns(:public_holiday).valid?
42
+ end
43
+
44
+ def test_new
45
+ get :new
46
+
47
+ assert_response :success
48
+ assert_template 'new'
49
+
50
+ assert_not_nil assigns(:public_holiday)
51
+ end
52
+
53
+ def test_create
54
+ num_public_holidays = PublicHoliday.count
55
+
56
+ post :create, :public_holiday => {:on => '2008-05-27'}
57
+
58
+ assert_response :redirect
59
+ assert_redirected_to :action => 'list'
60
+
61
+ assert_equal num_public_holidays + 1, PublicHoliday.count
62
+ end
63
+
64
+ def test_edit
65
+ get :edit, :id => @first_id
66
+
67
+ assert_response :success
68
+ assert_template 'edit'
69
+
70
+ assert_not_nil assigns(:public_holiday)
71
+ assert assigns(:public_holiday).valid?
72
+ end
73
+
74
+ def test_update
75
+ post :update, :id => @first_id
76
+ assert_response :redirect
77
+ assert_redirected_to :action => 'show', :id => @first_id
78
+ end
79
+
80
+ def test_destroy
81
+ assert_nothing_raised {
82
+ PublicHoliday.find(@first_id)
83
+ }
84
+
85
+ post :destroy, :id => @first_id
86
+ assert_response :redirect
87
+ assert_redirected_to :action => 'list'
88
+
89
+ assert_raise(ActiveRecord::RecordNotFound) {
90
+ PublicHoliday.find(@first_id)
91
+ }
92
+ end
93
+ end
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class PublicHolidayTest < Test::Unit::TestCase
4
+ fixtures :public_holidays
5
+
6
+ # Replace this with your real tests.
7
+ def test_truth
8
+ assert true
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class TimeOfDayTest < Test::Unit::TestCase
4
+ def setup
5
+ @twelve_o_clock = TimeOfDay.parse '12:00:00'
6
+ end
7
+
8
+ def teardown
9
+ end
10
+
11
+ def test_parse_full
12
+ assert_equal @twelve_o_clock, TimeOfDay.parse('12:00:00')
13
+ end
14
+
15
+ def test_parse_hour_and_minute
16
+ assert_equal @twelve_o_clock, TimeOfDay.parse('12:00')
17
+ end
18
+
19
+ def test_parse_hour_only
20
+ assert_equal @twelve_o_clock, TimeOfDay.parse('12')
21
+ end
22
+
23
+ end
@@ -19,7 +19,9 @@ class TimeOfDay
19
19
  end
20
20
 
21
21
  def self.parse(string)
22
- raise "Illegal time format: '#{string}'" unless string =~ /(\d{1,2}):?(\d{2})(?::(\d{1,2}))?/
22
+ string.strip!
23
+ return nil if string.empty?
24
+ raise "Illegal time format: '#{string}'" unless string =~ /(\d{1,2}):?(\d{2})?(?::(\d{1,2}))?/
23
25
  self.new($1.to_i, $2.to_i, $3.to_i)
24
26
  end
25
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.26.0
4
+ version: 0.28.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-03-28 00:00:00 +01:00
12
+ date: 2008-04-01 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -133,6 +133,12 @@ files:
133
133
  - app/views/work_accounts/_name_list.rhtml
134
134
  - app/views/work_accounts/show.rhtml
135
135
  - app/views/estimates
136
+ - app/views/public_holidays
137
+ - app/views/public_holidays/new.rhtml
138
+ - app/views/public_holidays/list.rhtml
139
+ - app/views/public_holidays/edit.rhtml
140
+ - app/views/public_holidays/_form.rhtml
141
+ - app/views/public_holidays/show.rhtml
136
142
  - app/views/parties
137
143
  - app/views/groups
138
144
  - app/views/groups/new.rhtml
@@ -163,6 +169,7 @@ files:
163
169
  - app/views/works/list.rhtml
164
170
  - app/views/works/_row.rhtml
165
171
  - app/views/works/list_excel.rhtml
172
+ - app/views/works/_new_row.rhtml
166
173
  - app/views/works/_buttons.rhtml
167
174
  - app/views/works/_task_id_list.rhtml
168
175
  - app/views/works/timeliste.rhtml
@@ -233,6 +240,7 @@ files:
233
240
  - app/controllers/periods_controller.rb
234
241
  - app/controllers/search_controller.rb
235
242
  - app/controllers/work_accounts_controller.rb
243
+ - app/controllers/public_holidays_controller.rb
236
244
  - app/controllers/task_files_controller.rb
237
245
  - app/controllers/tasks_controller.rb
238
246
  - app/controllers/absences_controller.rb
@@ -252,8 +260,10 @@ files:
252
260
  - app/helpers/parties_helper.rb
253
261
  - app/helpers/periods_helper.rb
254
262
  - app/helpers/search_helper.rb
263
+ - app/helpers/public_holidays_helper.rb
255
264
  - app/helpers/absences_helper.rb
256
265
  - app/models
266
+ - app/models/public_holiday.rb
257
267
  - app/models/work_lock_notify.rb
258
268
  - app/models/work.rb
259
269
  - app/models/work_lock_nagger.rb
@@ -307,6 +317,8 @@ files:
307
317
  - test/unit/work_account_test.rb
308
318
  - test/unit/localization_test.rb
309
319
  - test/unit/work_lock_subscription_test.rb
320
+ - test/unit/time_of_day_test.rb
321
+ - test/unit/public_holiday_test.rb
310
322
  - test/unit/period_test.rb
311
323
  - test/unit/estimate_test.rb
312
324
  - test/unit/party_test.rb
@@ -321,6 +333,7 @@ files:
321
333
  - test/functional/tasks_controller_test.rb
322
334
  - test/functional/welcome_controller_test.rb
323
335
  - test/functional/parties_controller_test.rb
336
+ - test/functional/public_holidays_controller_test.rb
324
337
  - test/functional/absences_controller_test.rb
325
338
  - test/functional/user_controller_test.rb
326
339
  - test/functional/backlogs_controller_test.rb
@@ -354,6 +367,7 @@ files:
354
367
  - test/fixtures/periods.yml
355
368
  - test/fixtures/parties.yml
356
369
  - test/fixtures/works.yml
370
+ - test/fixtures/public_holidays.yml
357
371
  - test/fixtures/users.yml
358
372
  - test/fixtures/customers.yml
359
373
  - test/fixtures/tasks.yml
@@ -683,10 +697,12 @@ files:
683
697
  - db/migrate/015_add_user_option.rb
684
698
  - db/migrate/028_create_absences.rb
685
699
  - db/migrate/019_remove_unique_index_for_period_position.rb
700
+ - db/migrate/030_create_public_holidays.rb
686
701
  - db/migrate/021_create_work_accounts.rb
687
702
  - db/migrate/014_add_customer_option.rb
688
703
  - db/migrate/022_remove_track_done_flag.rb
689
704
  - db/migrate/012_add_resolution.rb
705
+ - db/migrate/029_add_work_comments.rb
690
706
  - db/migrate/023_add_indices_for_burn_down_chart.rb
691
707
  - db/migrate/017_insert_datek_projects.rb
692
708
  - db/migrate/006_works_data_fix.rb