backlog 0.23.1 → 0.24.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.
Files changed (51) hide show
  1. data/History.txt +17 -4
  2. data/app/controllers/absences_controller.rb +64 -0
  3. data/app/controllers/periods_controller.rb +1 -1
  4. data/app/controllers/tasks_controller.rb +6 -4
  5. data/app/controllers/user_controller.rb +7 -0
  6. data/app/controllers/works_controller.rb +21 -16
  7. data/app/helpers/absences_helper.rb +2 -0
  8. data/app/models/absence.rb +14 -0
  9. data/app/models/task.rb +3 -2
  10. data/app/models/work.rb +33 -21
  11. data/app/views/absences/_form.rhtml +10 -0
  12. data/app/views/absences/edit.rhtml +9 -0
  13. data/app/views/absences/list.rhtml +27 -0
  14. data/app/views/absences/new.rhtml +8 -0
  15. data/app/views/absences/show.rhtml +8 -0
  16. data/app/views/layouts/absences.rhtml +17 -0
  17. data/app/views/tasks/_task.rhtml +1 -1
  18. data/app/views/tasks/edit.rhtml +1 -1
  19. data/app/views/user/edit.rhtml +36 -1
  20. data/app/views/works/_row.rhtml +19 -5
  21. data/app/views/works/_row_field.rhtml +14 -3
  22. data/app/views/works/daily_work_sheet.rhtml +81 -12
  23. data/app/views/works/list_excel.rhtml +1 -1
  24. data/app/views/works/timeliste.rhtml +3 -3
  25. data/app/views/works/update_row.rjs +3 -1
  26. data/app/views/works/update_time.rjs +2 -2
  27. data/app/views/works/weekly_work_sheet.rhtml +3 -3
  28. data/db/migrate/028_create_absences.rb +34 -0
  29. data/db/schema.rb +11 -2
  30. data/lang/en.yaml +4 -0
  31. data/lang/no.yaml +4 -0
  32. data/lib/change_column_null_migration_fix.rb +2 -0
  33. data/test/fixtures/absences.yml +11 -0
  34. data/test/fixtures/works.yml +10 -5
  35. data/test/functional/absences_controller_test.rb +93 -0
  36. data/test/functional/tasks_controller_test.rb +3 -2
  37. data/test/functional/works_controller_test.rb +33 -6
  38. data/test/test_helper.rb +3 -2
  39. data/test/unit/absence_test.rb +31 -0
  40. data/test/unit/work_test.rb +7 -0
  41. data/vendor/plugins/rails_time/MIT-LICENSE +20 -0
  42. data/vendor/plugins/rails_time/README +28 -0
  43. data/vendor/plugins/rails_time/init.rb +2 -0
  44. data/vendor/plugins/rails_time/lib/activerecord_time_extension.rb +28 -0
  45. data/vendor/plugins/rails_time/lib/time_of_day.rb +81 -0
  46. data/vendor/plugins/rails_time/test/database.yml +18 -0
  47. data/vendor/plugins/rails_time/test/rails_time_test.rb +122 -0
  48. data/vendor/plugins/rails_time/test/schema.rb +10 -0
  49. data/vendor/plugins/rails_time/test/test_helper.rb +17 -0
  50. metadata +28 -3
  51. data/app/views/works/daily_work_sheet_old.rhtml +0 -93
data/History.txt CHANGED
@@ -1,3 +1,16 @@
1
+ == 0.24.0 2008-02-28
2
+
3
+ === Features
4
+
5
+ * Allowed for marking dates as holiday or sick days in the daily work sheet.
6
+ * Show a summary of holidays and sick days in the user view.
7
+ * Added navigation with UP/DOWN keys in the daily work sheet.
8
+
9
+ === Fixes
10
+
11
+ * Made all work related views group and sort by start date and time.
12
+ * Do not try to register new work record in the daily work sheet unless work account name has been filled in.
13
+
1
14
  == 0.23.1 2008-02-26
2
15
 
3
16
  === Features
@@ -18,15 +31,15 @@
18
31
  * Changed sort order in daily work sheet from completion time to start time.
19
32
  * Changed to allow time format without separator: 0800 and 800 equal 08:00.
20
33
  * Minor cosmetic tweaks in the daily work sheet.
21
- * Fixed style sheet to remove the flickers in the sprint view.
34
+ * Fixed style sheet to remove the flicker in the sprint view.
22
35
 
23
36
  == 0.23.0 2008-02-25
24
37
 
25
38
  === Features
26
39
 
27
40
  * Improved input for daily_work_sheet
28
- * Allowed override for development and test database with new config file
29
- * Listed details for works without backlog in "Show Work Acoount" view.
41
+ * Allowed override for development and test database with new configuration file
42
+ * Listed details for works without backlog in "Show Work Account" view.
30
43
  * Added "List Works" view for Work Account.
31
44
  * Added filtering of tasks grabbed by other users in the sprint view.
32
45
 
@@ -43,7 +56,7 @@
43
56
  * Display blank field for hours in daily work sheet if it has not been filled in yet.
44
57
  * Always display row for new work record entry in daily work sheet.
45
58
  Earlier, a row for new input was only shown if the previous row was ended.
46
- * Removed message about missing database settings in main config file on startup since it is optional and deprecated.
59
+ * Removed message about missing database settings in main configuration file on startup since it is optional and deprecated.
47
60
  * Fixed error in class table inheritance library. Validations for the subclass were not called.
48
61
  * Added some Firewatir tests. EXPERIMENTAL!
49
62
  * Added explenation on how to set database connection parameters.
@@ -0,0 +1,64 @@
1
+ class AbsencesController < 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
+ @absence_pages, @absences = paginate :absences, :per_page => 10
13
+ end
14
+
15
+ def show
16
+ @absence = Absence.find(params[:id])
17
+ end
18
+
19
+ def new
20
+ @absence = Absence.new
21
+ end
22
+
23
+ def create
24
+ @absence = Absence.find(:first, :conditions => {:on => params[:absence][:on], :user_id => current_user.id})
25
+ if @absence
26
+ if params[:absence][:reason] == ''
27
+ @absence.destroy
28
+ flash[:notice] = 'Absence was successfully destroyed.'
29
+ else
30
+ @absence.update_attributes(params[:absence])
31
+ flash[:notice] = 'Absence was successfully updated.'
32
+ end
33
+ back_or_redirect_to :action => 'list'
34
+ else
35
+ @absence = Absence.new(params[:absence])
36
+ @absence.user_id = current_user.id
37
+ if @absence.save
38
+ flash[:notice] = 'Absence was successfully created.'
39
+ back_or_redirect_to :action => 'list'
40
+ else
41
+ render :action => 'new'
42
+ end
43
+ end
44
+ end
45
+
46
+ def edit
47
+ @absence = Absence.find(params[:id])
48
+ end
49
+
50
+ def update
51
+ @absence = Absence.find(params[:id])
52
+ if @absence.update_attributes(params[:absence])
53
+ flash[:notice] = 'Absence was successfully updated.'
54
+ redirect_to :action => 'show', :id => @absence
55
+ else
56
+ render :action => 'edit'
57
+ end
58
+ end
59
+
60
+ def destroy
61
+ Absence.find(params[:id]).destroy
62
+ redirect_to :action => 'list'
63
+ end
64
+ end
@@ -107,7 +107,7 @@ class PeriodsController < ApplicationController
107
107
 
108
108
  def list_work
109
109
  if @period = Period.find_by_id(params[:id])
110
- @works = @period.tasks.map {|t| t.works}.flatten.sort_by {|w| [w.completed_at || Time.now.midnight, w.started_at]}
110
+ @works = @period.tasks.map {|t| t.works}.flatten.sort_by {|w| [w.completed_at || Time.now.midnight, w.started_on, w.start_time]}
111
111
  def @works.page_count; size / 25; end
112
112
  render :template => '/works/list'
113
113
  else
@@ -228,9 +228,10 @@ class TasksController < ApplicationController
228
228
  :action => :edit,
229
229
  :id => started_work,
230
230
  'work[task_id]' => @task.id,
231
- 'work[started_at]' => started_work.started_at.iso8601,
232
- 'work[completed_at]' => [next_quarter, started_work.started_at].max.iso8601,
233
- 'work[hours]' => "%0.2f" % ((next_quarter - started_work.started_at).to_f / 3600),
231
+ 'work[started_on]' => started_work.started_on.to_s,
232
+ 'work[start_time]' => started_work.start_time.to_s,
233
+ 'work[completed_at]' => [next_quarter, started_work.started_on.at(started_work.start_time)].max.iso8601,
234
+ 'work[hours]' => "%0.2f" % ((next_quarter - started_work.started_on.at(started_work.start_time)).to_f / 3600),
234
235
  'estimate[todo]' => @task.todo
235
236
  })
236
237
  else
@@ -239,7 +240,8 @@ class TasksController < ApplicationController
239
240
  :controller => 'works',
240
241
  :action => :new,
241
242
  'work[task_id]' => @task.id,
242
- 'work[started_at]' => Time.previous_quarter.iso8601,
243
+ 'work[started_on]' => Time.previous_quarter.to_date.to_s,
244
+ 'work[start_time]' => Time.previous_quarter.time_of_day.to_s,
243
245
  'work[completed_at]' => next_quarter.iso8601,
244
246
  'work[hours]' => "0.25",
245
247
  'estimate[todo]' => @task.todo
@@ -217,6 +217,13 @@ class UserController < ApplicationController
217
217
  @groups = Group.find(:all, :order => 'name')
218
218
  @periods = @user.periods
219
219
  @associates = User.find(:all) - @user.work_lock_subscribers - [current_user]
220
+ absences = Absence.find(:all, :conditions => ['user_id = ? AND "on" BETWEEN ? AND ?', current_user.id, Date.new(Date.today.year, 1, 1), Date.new(Date.today.year, 12, 31)])
221
+ @holidays = absences.select {|a| a.reason == 'HOLIDAY'}
222
+ @holidays.map! {|a| a.on}
223
+ @sick_days = absences.select {|a| a.reason == 'SICK'}
224
+ @sick_days.map! {|a| a.on}
225
+ @sick_days_with_doctors_leave = absences.select {|a| a.reason == 'SICK_WITH_DOCTOR'}
226
+ @sick_days_with_doctors_leave.map! {|a| a.on}
220
227
  case request.method
221
228
  when :get
222
229
  render
@@ -1,11 +1,11 @@
1
1
  class WorksController < ApplicationController
2
2
  in_place_edit_for :work, :hours
3
3
  in_place_edit_for :work, :invoice
4
- in_place_edit_for :work, :started_at_time
4
+ in_place_edit_for :work, :start_time
5
5
  in_place_edit_for :work, :completed_at_time
6
6
  skip_before_filter :populate_layout, :except => [:create, :destroy, :edit, :index, :list, :new, :show, :update]
7
7
  auto_complete_for :work, :description
8
-
8
+
9
9
  def index
10
10
  list
11
11
  render :action => 'list'
@@ -57,12 +57,13 @@ class WorksController < ApplicationController
57
57
  end
58
58
  params[:work][:customer_id] = customer.id
59
59
  end
60
+ convert_hours_param
61
+ @work = Work.new(params[:work])
62
+ @work.started_on ||= Date.today
63
+ @work.completed_at = Time.now unless @work.start_time || @work.completed_at
64
+ @work.user_id = current_user.id
60
65
  end
61
- convert_hours_param
62
- @work = Work.new(params[:work])
63
- @work.completed_at = Time.now unless @work.started_at || @work.completed_at
64
- @work.user_id = current_user.id
65
- if @work.save
66
+ if @work && @work.save
66
67
  flash[:notice] = 'Work was successfully created.'
67
68
  else
68
69
  new
@@ -76,7 +77,7 @@ class WorksController < ApplicationController
76
77
  end
77
78
 
78
79
  def edit
79
- @work = Work.find(params[:id])
80
+ @work ||= Work.find(params[:id])
80
81
  @work.attributes = params[:work]
81
82
  @estimate = Estimate.new(params[:estimate])
82
83
  @work_accounts = WorkAccount.find(:all)
@@ -100,26 +101,29 @@ class WorksController < ApplicationController
100
101
  end
101
102
 
102
103
  def update_row
103
- update_work
104
- flash.discard
105
- @next_field = params[:next_field] || 'work_account_name'
104
+ if update_work
105
+ #flash.discard
106
+ @next_field = params[:next_field] || 'work_account_name'
107
+ works = Work.find_work_for_day((@work.started_at || @work.completed_at).to_date)
108
+ @day_total = works.inject(BigDecimal('0')){|total,work|total+=work.hours}
109
+ else
110
+ @next_field = params[:field] || 'work_account_name'
111
+ end
106
112
  @work_accounts = WorkAccount.find(:all, :order => :name)
107
113
  @customers = Customer.find(:all, :order => :name)
108
- works = Work.find_work_for_day((@work.started_at || @work.completed_at).to_date)
109
- @day_total = works.inject(BigDecimal('0')){|total,work|total+=work.hours}
110
114
  end
111
115
 
112
116
  def update_time
113
117
  update_work
114
118
  flash.discard
115
- @field = params[:field] || 'started_at_time'
119
+ @field = params[:field] || 'start_time'
116
120
  end
117
121
 
118
122
  def update_work
119
123
  @work = Work.find(params[:id])
120
124
  convert_hours_param
121
125
  if @work.update_attributes(params[:work])
122
- if params[:work] && (params[:work][:started_at_time] || params[:work][:completed_at_time])
126
+ if params[:work] && (params[:work][:start_time] || params[:work][:completed_at_time])
123
127
  @work.calculate_hours!
124
128
  @work.save!
125
129
  end
@@ -154,6 +158,7 @@ class WorksController < ApplicationController
154
158
  @started_works = Task.find_started
155
159
  @work = Work.new(:started_at => Time.now, :completed_at => Time.now)
156
160
  @work_accounts = WorkAccount.find(:all, :order => :name)
161
+ @absence = Absence.find(:first, :conditions => {:on => @date})
157
162
  render :layout => 'wide'
158
163
  end
159
164
 
@@ -198,7 +203,7 @@ class WorksController < ApplicationController
198
203
 
199
204
  def auto_complete_for_work_customer_name
200
205
  @customers = Customer.find(:all,
201
- :conditions => [ 'LOWER(name) LIKE ?',
206
+ :conditions => [ 'LOWER(name) LIKE ?',
202
207
  '%' + params[:work][:customer_name].downcase + '%' ],
203
208
  :order => 'name ASC',
204
209
  :limit => 16)
@@ -0,0 +1,2 @@
1
+ module AbsencesHelper
2
+ end
@@ -0,0 +1,14 @@
1
+ class Absence < ActiveRecord::Base
2
+ validates_presence_of :user_id
3
+ validates_presence_of :on
4
+ validates_presence_of :reason
5
+
6
+ validates_uniqueness_of :on
7
+
8
+ def validate
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
+ errors.add :on, "You have already registered work on this date."
11
+ end
12
+ end
13
+
14
+ end
data/app/models/task.rb CHANGED
@@ -350,13 +350,14 @@ class Task < ActiveRecord::Base
350
350
  # TODO (uwe): Only needed for rails 1.2.x branch. Remove when moving to 1.3 or 2.0.
351
351
  new_work.task = self
352
352
 
353
- new_work.started_at = Time.previous_quarter
353
+ new_work.started_on = Time.previous_quarter.to_date
354
+ new_work.start_time = Time.previous_quarter.time_of_day
354
355
  if current_user
355
356
  last_work = current_user.works.select {|w| w.completed_at}.last
356
357
  else
357
358
  last_work = Work.find(:first, :conditions => 'completed_at IS NOT NULL', :order => 'completed_at DESC')
358
359
  end
359
- if last_work && last_work.completed_at > new_work.started_at
360
+ if last_work && last_work.completed_at > new_work.started_on.at(new_work.start_time)
360
361
  new_work.started_at = last_work.completed_at
361
362
  end
362
363
 
data/app/models/work.rb CHANGED
@@ -9,12 +9,19 @@ class Work < ActiveRecord::Base
9
9
 
10
10
  validates_associated :task
11
11
  validates_presence_of :work_account
12
- validates_presence_of :started_at, :if => :track_times?
12
+ validates_presence_of :started_on
13
+ validates_presence_of :start_time, :if => :track_times?
13
14
 
14
15
  def validate
15
16
  errors.add(:work, "Work account is missing") unless work_account
17
+ if completed_at
18
+ absence_exists = Absence.exists? ['user_id = ? AND "on" BETWEEN ? AND ?', user_id, started_on, completed_at.to_date]
19
+ else
20
+ absence_exists = Absence.exists? :user_id => user_id, :on => started_on
21
+ end
22
+ errors.add :started_on, "You have already marked this date with an absence." if absence_exists
16
23
  end
17
-
24
+
18
25
  alias_method :old_work_account, :work_account
19
26
  def work_account
20
27
  self.old_work_account || (task && task.work_account)
@@ -33,11 +40,11 @@ class Work < ActiveRecord::Base
33
40
  # ]
34
41
  def self.works_for_week(year, week_no, user = current_user)
35
42
  first = Date.commercial(year, week_no, 1)
36
- last = first + 7
37
- works = find(:all, :conditions => "completed_at IS NOT NULL AND started_at BETWEEN '#{first.to_time.iso8601}' AND '#{last.to_time.iso8601}'", :order => 'started_at')
43
+ last = first + 6
44
+ works = find(:all, :conditions => "completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}'", :order => 'started_on, start_time')
38
45
  length = 0
39
46
  works_per_day = (0..6).map do |day|
40
- works_for_day = works.select {|work| work.started_at.to_date == (first + day) && (work.user_id.nil? || (user && work.user_id == user.id)) }
47
+ works_for_day = works.select {|work| work.started_on == (first + day) && (work.user_id.nil? || (user && work.user_id == user.id)) }
41
48
  length = [length, works_for_day.length].max
42
49
  works_for_day
43
50
  end
@@ -54,10 +61,10 @@ class Work < ActiveRecord::Base
54
61
  def self.works_for_week_by_work_account(year, week_no, user = current_user)
55
62
  first_date = Date.commercial(year, week_no, 1)
56
63
  last_date = first_date + 6
57
- works = find(:all, :conditions => "user_id #{user ? " = #{user.id}" : "IS NULL"} AND completed_at IS NOT NULL AND completed_at BETWEEN '#{first_date.to_time.iso8601}' AND '#{(last_date+1).to_time.iso8601}'", :order => 'completed_at, started_at')
64
+ works = find(:all, :conditions => "user_id #{user ? " = #{user.id}" : "IS NULL"} AND started_on BETWEEN '#{first_date}' AND '#{last_date}'", :order => 'completed_at, started_on, start_time')
58
65
  result = {}
59
66
  works.each do |work|
60
- day_of_week = work.completed_at.to_date.cwday - 1
67
+ day_of_week = work.started_on.cwday - 1
61
68
  result[work.work_account] ||= []
62
69
  result[work.work_account][day_of_week] ||= BigDecimal('0')
63
70
  result[work.work_account][day_of_week] += work.hours
@@ -74,13 +81,13 @@ class Work < ActiveRecord::Base
74
81
  # }
75
82
  def self.work_totals_for_week(year, week_no, user = current_user)
76
83
  first = Date.commercial(year, week_no, 1)
77
- last = first + 7
78
- works = find(:all, :conditions => "completed_at IS NOT NULL AND completed_at BETWEEN '#{first.to_time.iso8601}' AND '#{last.to_time.iso8601}'", :order => 'completed_at, started_at')
84
+ last = first + 6
85
+ works = find(:all, :conditions => "completed_at IS NOT NULL AND started_on BETWEEN '#{first}' AND '#{last}'", :order => 'started_on, start_time, completed_at')
79
86
  totals_per_work_account = {}
80
87
  works.map{|w| w.work_account}.uniq.each do |work_account|
81
88
  totals_per_work_account[work_account.id] = [[], []]
82
89
  (0..6).each do |day|
83
- works_for_day = works.select {|work| (work.work_account == work_account) && (work.completed_at.to_date == (first + day)) && ((user.nil? && work.user_id.nil?) || (user && work.user_id == user.id)) }
90
+ works_for_day = works.select {|work| (work.work_account == work_account) && (work.started_on == (first + day)) && ((user.nil? && work.user_id.nil?) || (user && work.user_id == user.id)) }
84
91
  invoice_works_for_day = works_for_day.select {|work| work.invoice? }
85
92
  internal_works_for_day = works_for_day.select {|work| !work.invoice? }
86
93
 
@@ -97,28 +104,33 @@ class Work < ActiveRecord::Base
97
104
  end
98
105
 
99
106
  def self.find_work_for_day date
100
- Work.find(:all, :conditions => "(completed_at IS NULL OR completed_at BETWEEN '#{date}' AND '#{date+1}') AND user_id = #{current_user.id}",
101
- :order => 'started_at, completed_at')
107
+ Work.find(:all, :conditions => "started_on = '#{date}' AND user_id = #{current_user.id}",
108
+ :order => 'start_time, completed_at')
102
109
  end
103
110
 
104
111
  def started?
105
112
  completed_at.nil?
106
113
  end
107
114
 
108
- def started_at_time
109
- started_at && started_at.strftime('%H:%M')
115
+ def started_at
116
+ started_on && start_time && started_on.at(start_time)
110
117
  end
111
118
 
112
- def started_at_time=(new_value)
113
- new_value = '0:00' if new_value == ''
114
- raise "invalid time format: #{new_value}" unless new_value =~ /(\d{0,2}):?(\d{2})/
115
- new_hour, new_minutes = $1.to_i, $2.to_i
116
- t = started_at || Time.now
117
- self.started_at = Time.local(t.year, t.month, t.day, new_hour, new_minutes)
119
+ def started_at=(new_value)
120
+ case new_value
121
+ when String:
122
+ t = Time.parse(new_value)
123
+ when Time:
124
+ t = new_value
125
+ else
126
+ raise "Illegal argument: #{new_value.inspect}"
127
+ end
128
+ self.started_on = Date.new(t.year, t.month, t.day)
129
+ self.start_time = TimeOfDay.new(t.hour, t.min)
118
130
  end
119
131
 
120
132
  def completed_at_time
121
- completed_at && completed_at.strftime('%H:%M')
133
+ completed_at && completed_at.time_of_day
122
134
  end
123
135
 
124
136
  def completed_at_time=(new_value)
@@ -0,0 +1,10 @@
1
+ <%= error_messages_for 'absence' %>
2
+
3
+ <!--[form:absence]-->
4
+ <p><label for="absence_on">On</label><br/>
5
+ <%= date_select 'absence', 'on' %></p>
6
+
7
+ <p><label for="absence_reason">Reason</label><br/>
8
+ <%= text_field 'absence', 'reason' %></p>
9
+ <!--[eoform:absence]-->
10
+
@@ -0,0 +1,9 @@
1
+ <h1>Editing absence</h1>
2
+
3
+ <% form_tag :action => 'update', :id => @absence do %>
4
+ <%= render :partial => 'form' %>
5
+ <%= submit_tag 'Edit' %>
6
+ <% end %>
7
+
8
+ <%= link_to 'Show', :action => 'show', :id => @absence %> |
9
+ <%= link_to 'Back', :action => 'list' %>
@@ -0,0 +1,27 @@
1
+ <h1>Listing absences</h1>
2
+
3
+ <table>
4
+ <tr>
5
+ <% for column in Absence.content_columns %>
6
+ <th><%= column.human_name %></th>
7
+ <% end %>
8
+ </tr>
9
+
10
+ <% for absence in @absences %>
11
+ <tr>
12
+ <% for column in Absence.content_columns %>
13
+ <td><%=h absence.send(column.name) %></td>
14
+ <% end %>
15
+ <td><%= link_to 'Show', :action => 'show', :id => absence %></td>
16
+ <td><%= link_to 'Edit', :action => 'edit', :id => absence %></td>
17
+ <td><%= link_to 'Destroy', { :action => 'destroy', :id => absence }, :confirm => 'Are you sure?', :method => :post %></td>
18
+ </tr>
19
+ <% end %>
20
+ </table>
21
+
22
+ <%= link_to 'Previous page', { :page => @absence_pages.current.previous } if @absence_pages.current.previous %>
23
+ <%= link_to 'Next page', { :page => @absence_pages.current.next } if @absence_pages.current.next %>
24
+
25
+ <br />
26
+
27
+ <%= link_to 'New absence', :action => 'new' %>