tandusrl / acts_as_bookable

A reservation plugin for Rails applications that allows resources to be bookable.
MIT License
140 stars 103 forks source link

Get availability for a bookable #22

Open chrisedington opened 7 years ago

chrisedington commented 7 years ago

Hi,

I was wondering if there is a way I can get the available times for a specific bookable.

My bookable at the moment is created like this:

session = Session.new
session.capacity = 5
session.schedule = IceCube::Schedule.new(iteration.date, duration: 90.minutes)
session.schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(9,10, 11, 12, 14, 15, 16, 17)
session.save!

So ideally I would like to display a list of "available times" which can be reserved, based on current availability. I don't want to show a full list of the slots mentioned in the snippet above, rather only the available ones.

I would imagine doingsession.availability(which doesn't exist) and getting back a list.

Any advice on how I can achieve this?

Chosko commented 7 years ago

The availability of a bookable is calculated at runtime, given an hash of options. This hash of options must provide all the condition requested by a booking instance, included the time (or interval) at which you want to book the resource (if :time_type != :none).

As you can see in the source code, there are complex calculations to understand if the bookable is really available, and it's not possible to have a list of availabilities without making those calculations for each possible time. A way to get a real list of availabilities would require to iterate over all the possible 'slots' (WARNING: you can have an infinite number of slots!) and call check_availability! with a temporary hash of options for each slot....

I imagine a feasible similar feature. It would be a function "bookable.get_availabilities(from: start_time, to: end_time)" that iterates over the slots of the schedule, but limiting the calculation between the given interval.

PRs or suggestions are appreciated

colemerrick commented 7 years ago

@Chosko I'm interested in this as well and can help. Would it depend on the schedule you assign to the bookable? For example, if a bookable is available daily for a day at a time, could you loop through the bookings, create an array of the days the bookable is booked, and subtract it from an array that reflects the days of the month. Obviously a simple use case, but maybe it can be applied to some more complicated ones - with the idea being to limit the infinite number of slots such that one can define how far in advance you want to print out the available ones.

micksabox commented 7 years ago

I'm interested in a solution and am also available to help. One requirement I have is that not only do I need the availability for 1 specific Bookable instance, I need ALL available time slots among N number of Bookables of same class, within a maximum day time frame.

I also need the algorithm to be efficient, because I'm planning on having 15 min schedule intervals per Bookable. So worst case, that is 96 intervals per day multiplied by 8 = 768, where N is equal to 8.

I'm not sure how schedule is serialized as :text, but maybe there needs to be a separate DB construct to model availability more efficiently?

zernie commented 3 years ago

I know that this is an old issue, but ...has anyone been able to implement this ?

My dumb implementation is not working correctly:)

  # only works for time_type=range
  def availability(time_start, time_end, amount: 1, duration: nil)
        overlapped = ActsAsBookable::Booking.overlapped(self, time_start: time_start, time_end: time_end).to_a

        self.schedule.occurrences_between(time_start, time_end).select do |occurrence|
          time_end = if duration
                       occurrence.start_time + duration
                     else
                       occurrence.end_time
                     end
          self.check_availability(time_start: occurrence.start_time, time_end: time_end, scope: overlapped, amount: amount)
        end
      end

##
# Real capacity check (calculated with overlapped bookings)
#
overlapped = if opts[:scope]
                       opts[:scope]
                     else
                       ActsAsBookable::Booking.overlapped(self, opts)
                     end