Class ClinicalSession
In: app/models/clinical_session.rb
Parent: ActiveRecord::Base

Methods

Attributes

length  [W] 

Public Instance methods

Checks to see if this session is fully booked. Returns true if a Booking exists for this session. Returns false otherwise

Usage:

   make_booking unless clinical_session.booked?

[Source]

    # File app/models/clinical_session.rb, line 46
46:   def booked?
47:     self.bookings.length == self.max_bookings || false
48:   end

Returns the booking for this session that matches the supplied client_id.

[Source]

     # File app/models/clinical_session.rb, line 140
140:   def booking_for( client_id )
141:     self.bookings.detect {|b| b.client.id == client_id}
142:   end

[Source]

     # File app/models/clinical_session.rb, line 94
 94:   def client_booked_in?( client )
 95:     self.bookings.each do | booking |
 96:       return true if booking.client.id == client.id
 97:     end
 98:     
 99:     false
100:   end

Copy the details from another session

[Source]

     # File app/models/clinical_session.rb, line 207
207:   def copy_from(start, clinical_session)
208:     self.clinician    = clinical_session.clinician
209:     self.start        = start.to_time
210:     self.max_bookings = clinical_session.max_bookings
211:     self.session_type = clinical_session.session_type
212:     self.set_finish(clinical_session.length)
213:   end

Simple boolean to check whether session accepts multiple bookings.

[Source]

     # File app/models/clinical_session.rb, line 217
217:   def group_session?
218:     return (self.max_bookings > 1)
219:   end

Returns the length in minutes of the session.

Usage:

   rows = clinical_session.length / 60

[Source]

    # File app/models/clinical_session.rb, line 54
54:   def length
55:     return ((finish - start).round / 60)
56:   end

Returns array of future ClinicalSession objects that may have been created using recurrence. They will all have the same details (start, length, type, clinician).

This function needs to be updated to more accurately detect recurrence. This is problematic.

  • Sessions on subsequent days without a gap (except weekends)
  • Sessions on the same day in subsequent weeks
  • Sessions on the same date of the month

[Source]

     # File app/models/clinical_session.rb, line 153
153:   def recurrence
154:     similar_sessions = ClinicalSession.find(:all, 
155:       :conditions => "clinician_id = #{self.clinician.id} "<<
156:                      "AND start > '#{self.start.to_s(:db)}'"<<
157:                      "AND session_type_id = #{self.session_type.id}",
158:       :order      => "start ASC")
159:     
160:     similar_sessions = similar_sessions.delete_if {|cs| 
161:                          cs.start.hour != self.start.hour ||
162:                          cs.start.min != self.start.min ||
163:                          cs.length != self.length
164:                        }
165:     if similar_sessions
166:       recurring_sessions = []
167:       
168:       #Check for daily
169:       tomorrow, i = self.start.advance(:days => 1), 0
170:       while tomorrow.strftime("%w") == "0" ||
171:             tomorrow.strftime("%w") == "6"|| 
172:             (similar_sessions[i] && similar_sessions[i].start.to_date == tomorrow.to_date)
173:         
174:         unless tomorrow.strftime("%w") == "0" || tomorrow.strftime("%w") == "6"
175:           recurring_sessions << similar_sessions[i]
176:           i += 1
177:         end
178:         tomorrow = tomorrow.advance(:days => 1)
179:       end
180:       
181:       #Check for weekly
182:       next_week, i = self.start.advance(:days => 7), 0
183:       while similar_sessions[i] && similar_sessions[i].start <= next_week
184:         if similar_sessions[i].start.to_date == next_week.to_date
185:           recurring_sessions << similar_sessions[i]
186:           next_week = next_week.advance(:days => 7)
187:         end
188:         i += 1
189:       end
190:       
191:       #Check for monthly
192:       similar_sessions = similar_sessions.delete_if {|cs| cs.start.day != self.start.day }
193:       next_month, i = self.start.advance(:months => 1), 0
194:       while similar_sessions[i] && similar_sessions[i].start.day <= next_month.day
195:         if similar_sessions[i].start.to_date == next_month.to_date
196:           recurring_sessions << similar_sessions[i]
197:           next_month = next_month.advance(:months => 1)
198:         end
199:         i += 1
200:       end
201:     end
202:     
203:     return recurring_sessions.uniq
204:   end

Create multiple sessions with similar data to this one

[Source]

     # File app/models/clinical_session.rb, line 118
118:   def recurrence=(recurring)
119:     next_date = self.start
120:     until_time = recurring[:until].to_time
121:     case recurring[:frequency]
122:       when 'daily'
123:         while (next_date = next_date.advance(:days => 1)) <= until_time
124:           self.copy(next_date) unless next_date.strftime("%w") == "0" ||
125:                                       next_date.strftime("%w") == "6"
126:         end
127:       when 'weekly'
128:         while (next_date = next_date.advance(:days => 7)) <= until_time
129:           self.copy(next_date)
130:         end
131:       when 'monthly'
132:         while (next_date = next_date.advance(:months => 1)) <= until_time
133:           self.copy(next_date)
134:         end
135:     end
136:   end

Move this session for days number of days into the future. Will cancel any bookings for clients which clash with pre-existing bookings. Will delete any sessions that conflict with this new start/finish time!

Usage

  clinical_session.reschedule(7)

Would move the session to the next week (same time and length).

[Source]

    # File app/models/clinical_session.rb, line 76
76:   def reschedule( days )
77:     self.start = self.start.advance(:days=>days)
78:     self.finish = self.finish.advance(:days=>days)
79:     
80:     # Delete bookings for THIS session
81:     self.bookings.each do |booking|
82:       booking.destroy if booking.client.is_booked?(start, finish)
83:     end
84:     
85:     # Delete session(s) that conflict with this sessions'
86:     # new time
87:     if clashing_session = self.clinician.session_during(self.start, self.finish)
88:       clashing_session.destroy
89:     end
90:     
91:     self.update
92:   end

Set the finish time for this session based on the length.

[Source]

    # File app/models/clinical_session.rb, line 60
60:   def set_finish(mins)
61:     self.finish = self.start + (mins.to_f * 60)
62:   end

Returns true if this session starts between 2 times regardless of the date.

Usage

  start if starts_during_time(1.hour.since(now), now)

[Source]

     # File app/models/clinical_session.rb, line 108
108:   def starts_during_time?( start_time, finish_time )
109:     start_in_minutes         = start_time.hour  * 60 + start_time.min
110:     finish_in_minutes        = finish_time.hour * 60 + finish_time.min
111:     session_start_in_minutes = self.start.hour  * 60 + self.start.min
112:     
113:     return true if start_in_minutes <= session_start_in_minutes &&
114:            session_start_in_minutes < finish_in_minutes
115:   end

Convert ClinicalSession record into an Event class

[Source]

     # File app/models/clinical_session.rb, line 223
223:   def to_event
224:     e = Events::Event.new()
225:     e.start       = self.start
226:     e.finish      = self.finish
227:     e.title       = self.session_type.name
228:     e.description = "This is a session!"
229:     return e
230:   end

Protected Instance methods

Make a copy of this ClinicalSession at the supplied start time. The session will otherwise be the same.

[Source]

     # File app/models/clinical_session.rb, line 236
236:   def copy(start)
237:     set_session_length
238:     cs = ClinicalSession.new(:clinician    => self.clinician,
239:                              :start        => start.to_time,
240:                              :length       => self.length,
241:                              :session_type => self.session_type,
242:                              :max_bookings => self.max_bookings)
243:                                
244:     cs.save
245:   end

If no SessionType is provided, the first type found for the selected Discipline will be used.

[Source]

     # File app/models/clinical_session.rb, line 256
256:   def set_default_type
257:     if !self.session_type && self.clinician
258:       self.session_type = self.clinician.discipline.session_types.first
259:     end
260:   end

If a length has been provided (through the attr_writer), then set the finish time

[Source]

     # File app/models/clinical_session.rb, line 249
249:   def set_session_length
250:     self.set_finish(@length) if @length
251:     @length = nil
252:   end

Additional Validation

Check for session clashes

Checks for clashes with existing sessions. Also check that session is on 1 day.

There are three types of possible clashes:

  1. Internal Conflict - Where an existing session is entirely inside the new session
  2. Start Time Conflict - Where the start time is inside an existing session
  3. Finish Time Conflict - Where the finish time is inside an existing session

If both 2 and 3 occur, the new session occurs entirely within another session. This is an external conflict.

Special Case Exemption

A ClinicalSession may finish at the exact same time as another starts.

Check for session times

Check that the session start and finish times:

  1. Does not start before 7am
  2. Does not finish after 7pm
  3. Starts and finishes on the same day
  4. Finish is after start
  5. Session length is at least 15 minutes

Rule 5 was implemented because it is the smallest increment the calendar currently shows. It could be reduced or removed if the calendar was altered to smaller increments if neccasary.

[Source]

     # File app/models/clinical_session.rb, line 288
288:   def validate
289:     if start and finish
290:       # If there is no ID (ie this is a new session), use 0
291:       cs_id = id || 0
292:       
293:       # Test for (2)
294:       if cs = ClinicalSession.find(:first, :conditions => "start <= '#{start.to_s(:db)}' AND '#{start.to_s(:db)}' < finish AND id <> #{cs_id} AND clinician_id = #{clinician_id}")
295:         errors.add :start, "is invalid. Another session is already running at this start time"
296:       
297:       # Test for (3)  
298:       elsif ClinicalSession.find(:first, :conditions => "start < '#{finish.to_s(:db)}' AND '#{finish.to_s(:db)}' <= finish AND id <> #{cs_id} AND clinician_id = #{clinician_id}")
299:         errors.add :length, "is invalid. Another session starts before this session ends"
300:       
301:       # Test for (1)
302:       elsif ClinicalSession.find(:first, :conditions => "start > '#{start.to_s(:db)} ' AND '#{finish.to_s(:db)}' > finish AND id <> #{cs_id} AND clinician_id = #{clinician_id}")
303:         errors.add :length, "is invalid. A session already exists during this time"
304:         
305:       end
306:       
307:       # Test for too early start
308:       errors.add :start, "is too early. Sessions cannot start before 7am" if start.hour < 7
309:       
310:       # Test for too late finish
311:       errors.add :finish, "is too late. Sessions cannot finish after 7pm" if finish > "#{finish.strftime("%Y-%m-%d")} 19:00:00".to_time
312:       errors.add :finish, "is too late. Sessions must start and end on the same day" unless start.day == finish.day
313:       
314:       # Test for logical session start and finish values
315:       # This test is redundant as the length test will pick up the same errors
316:       # It has been left in incase the session length restriction is
317:       # removed in the future.
318:       errors.add :finish, "must be after start." unless finish > start
319:       
320:       # Test for session length at least 15 minutes
321:       errors.add :length, "must be at least 15 minutes." if self.length < 15
322:     end
323:   end

Update Validation

If this session is booked, it cannot be changed so that a client has duplicate bookings. Also test that the max_bookings is not reduced below the number of bookings.

[Source]

     # File app/models/clinical_session.rb, line 329
329:   def validate_on_update
330:     unless self.bookings.empty?
331:       errors.add :start, "is invalid. A client who is booked into this session already has a booking at this time." if self.bookings.detect {|b| b.client.is_booked?(start,finish,id)}
332:       errors.add :max_bookings, "is too small.  There are already #{self.bookings.length} clients booked into this session." if self.max_bookings < self.bookings.length
333:     end
334:   end

[Validate]