I am looking to build an appointment booking app with the following characteristics: - Users can be service providers or buyers - Service providers set their availabilities (but can only set their availabilities at a maximum of 6 months ahead) - Buyers can then book appointments based on those availabilities - each appointment, based on the type of service, takes a different amount of time - Based on the appointment that a buyer selects, a different set of availabilities is shown depending on how long the service takes
What I've built is the following:
- A TimeSlot
model where I create a number of generic 30 minute time slots based on a start_time
and end_time
attribute. In order to make these time slots extend 6 months into the future, I have a background job running each day that creates all the new time slots necessary
class TimeSlot < ActiveRecord::Base
has_many :user_time_slots
# ... more methods below
end
- A UserTimeSlots
model that basically represents a service provider's availability that they can set. So when they create a user_time_slot, they are essentially saying that they are available at that time.
class UserTimeSlot < ActiveRecord::Base
belongs_to :time_slot
belongs_to :service_provider, :class_name => "User"
belongs_to :appointment
end
- An Appointment
model that has many user_time_slots. It has many because an appointment belongs to a service which takes a certain amount of time (a time_required
attribute on services) and it might span a number consecutive user_time_slots.
class Appointment < ActiveRecord::Base
has_many :user_time_slots
belongs_to :buyer, :class_name => "User"
belongs_to :service_provider, :class_name => "User"
belongs_to :service
end
- A Service
model that has many appointments and belongs to a service provider who creates that service.
class Service < ActiveRecord::Base
has_many :appointments
belongs_to :service_provider, :class_name => "User"
end
This domain model works; however, I am wondering if there is a better way to do this because of the following:
It seems a little clunky to me to be creating TimeSlot records every day on my backend using a background job - the TimeSlots really have the sole purpose of having a start and end time and then being associated to.
Does anyone have a better way or solution to do this (particularly around the topic of finding consecutive time slots)?
Look like a fun project. :)
I would personally not model the "existing time", i.e I would not have a background job create "empty" data.
I would try a model like this:
Where the User table?
I would not use a shared User model for the two different User types. My gut feeling is that they need to be different, if not now, most surly over time. Also it makes the model more clear, in my opinion. If there is a login or any auth, I would add a User table for that specific data and rather have Service Provider and Consumer have some relation to this.
Service Provider manages availability
The Service Provider would only need an empty calendar (or similar), listing only her already entered Service Availability, per Service.
Consumer books appointment
The Consumer would search/browse/navigate Service Availability, per Service
Depending on business logic, i.e. will a Consumer always schedule the whole defined time slot? Or could the Consumer book a preferred time slot. As long as the booked slot is within the given Service's available time slot?
Scheduling
So we only put Service Availability records in when the Service Provider manages her availability for a specific Service. Empty is simply considered not available.
We only put Appointment records in when a Consumer books an Service, based on Service Availability.
If its possible for the Consumer to book only a part of a Service Availability time slot, I would recommend to create two (or one) new Service Availability records for the time left over. Checking if time is available could be done by comparing Service Availability with Appointment, but this would not be optimal. Better would be to just query Service Availability for a specific Service and get a availability schedule, where no record would mean no availability.
There are a lots of ifs here, depending on your specifc needs and requested business logic. But I hope this helps with some inspiration and ideas.
I would have a single model for each availability period. Each period has a start_time
and end_time
. They can be easily validated to not be more than 6 months in advance, and you can check if an appointment will fit. Your time slot does not need to belong to an appointment as such; the appointment would simply have a time and a service provider, and said service provider's availability would change to reflect the booked appointment.
class AvailabilityPeriod < ActiveRecord::Base
belongs_to :service_provider, :class_name => "User"
validates :end_time, :inclusion => { :in => Time.now..(Time.now + 6.months) }
end
For example, you could find all possible availabilities for a given service and provider like this:
duration = @service.duration
available_times = @provider.availability_periods.where (end_time - start_time > duration)
Booking an appointment is a bit more tricky. You need to split/shorten the availability period. For example, say a provider has the following availability:
May 30th 12:00 - 18:00
An appointment is booked for May 30th 14:00 - 16:00
The old availability needs to be removed and replaced by two new ones: May 30th 12:00 - 14:00 May 30th 16:00 - 18:00
But this is pretty easy to do in a model method.
I like jpriebe's solution, though I would propose a slight change to the validation: use (Date.today + 6.months).end_of_day
for the extreme end of the range.
The reason for my suggestion is to make the system slightly more user-friendly. If you used Time.now
, and a user wants to create an AvailabilityPeriod ending at 4pm, but it is still somewhat early in the morning (say, around 10:15am), 4pm would be out of the range. To create the AvailabilityPeriod, they'd have to remember to come back later - and they might forget / are taking the lasagna out of the oven / insert distraction here.
Admittedly, there is really only one possible time in which this scenario could occur, and that is if a user is trying to create an AvailabilityPeriod exactly 6 months ahead (e.g., May 27 => Nov 27). Most of the time, I'm certain most users would not be setting up an AvailabilityPeriod that far in advance. Still, using .end_of_day
would extend the acceptable time-range to the end of the appropriate day.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With