backlog 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,27 @@
1
+ == 0.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