Commit f3466e70 authored by Josh Fabean's avatar Josh Fabean
Browse files

Issue #3303327: Error when add booking

parent 9f72c3bb
Loading
Loading
Loading
Loading
+67 −52
Original line number Diff line number Diff line
# Bookable Calendar

This aim to be a very easy to use Bookable Calendar module. Whether you're giving lessons and want
your students to be able to book a lesson or a business trying to stagger traffic into your building,
this module aims to get you up and running as fast as possible.
This aim to be a very easy to use Bookable Calendar module. Whether you're
giving lessons and want your students to be able to book a lesson or a business
trying to stagger traffic into your building, this module aims to get you
up and running as fast as possible.

## Steps to create your first Calendar and take a Booking

@@ -14,54 +15,65 @@ this module aims to get you up and running as fast as possible.

### As an user (currently though admin interface UI coming soon)

1. Create a new Booking Contact filling out what Instance you want and party size.
2. On save it will tell you whether or not that will work based on max slots available and other things.
1. Create a new Booking Contact filling out what Instance you want and party
size.
2. On save it will tell you whether or not that will work based on max
slots available and other things.

## Entities and what they do

### Bookable Calendar

This is the main Calendar that will be user facing. The thought would be if you're an individual giving
lessons it could be called "Piano Lessons" or if you're a company selling spots in line for Santa it
would say "Santa at the Mall" or whatever. It's fieldable so you can add any extra fields needed for
your situation by default it has the following fields:
This is the main Calendar that will be user facing. The thought would be
if you're an individual giving lessons it could be called "Piano Lessons" or
if you're a company selling spots in line for Santa it would say
"Santa at the Mall" or whatever. It's fieldable so you can add any
extra fields needed for your situation by default it has the following fields:

- **Title:** The name of your Calendar
- **Active:** Whether or not you are currently allowing more bookings, this is so you can still show
the calendar but freeze Bookings.
- **Active:** Whether or not you are currently allowing more bookings, this is
so you can still show the calendar but freeze Bookings.
- **Status:** Standard Drupal status, will hide this calendar from non admins.
- **Description:** This is a long text field that can be filled out to show any sort of body text
you want to show to users about this calendar.
- **Max Party Size:** Limit the max amount of bookings an individual contact can make to a single Opening.
- **Slots Per Opening:** How many bookings you want to allow per opening, if you're giving individual
lessons this might be 1 but if you're booking our an event space this might be 100 or whatever you need.
- **Booking Future Time:** This will limit user's ability to book openings more than X days/weeks/months in the future.
- **Booking Lead Time:** This is the minimum amount time in the future a Booking can take place. This can
be used to limit users booking same day/week/month booking
- **Calendar Openings:** This is not user created but is an entity reference to each opening for this Calendar.
- **Description:** This is a long text field that can be filled out to show any
sort of body text you want to show to users about this calendar.
- **Max Party Size:** Limit the max amount of bookings an individual contact
can make to a single Opening.
- **Slots Per Opening:** How many bookings you want to allow per opening,
if you're giving individual lessons this might be 1 but if you're booking
our an event space this might be 100 or whatever you need.
- **Booking Future Time:** This will limit user's ability to book openings
more than X days/weeks/months in the future.
- **Booking Lead Time:** This is the minimum amount time in the future a
Booking can take place. This can be used to limit users booking same
day/week/month booking
- **Calendar Openings:** This is not user created but is an entity reference
to each opening for this Calendar.

### Bookable Calendar Opening

The individual openings for your Bookable Calendar. You can create as many of these as you'd like for
your calendar. This is where you define what dates and times are available to book. Due to it being
a multiple date value with repeat it's possible you could create only one of these per Bookable Calendar
but the option is there to create as many as needed. The advantage af splitting them out is then it's
easier to turn of a certain Opening whether that be a single day or opening as there is the status
field to quickly turn it on or off vs editing your repeating date field to remove a day.

- **Title:** Only shown to admins to quickly let you know what opening you're looking at.
- **Status:** Easy turn it on/off for booking, you can turn the whole calendar off in the Bookable Calendar
or here to turn off a single Opening.
The individual openings for your Bookable Calendar. You can create as many of
these as you'd like for your calendar. This is where you define what dates and
times are available to book. Due to it being a multiple date value with repeat
it's possible you could create only one of these per Bookable Calendar but the
option is there to create as many as needed. The advantage af splitting them
out is then it's easier to turn of a certain Opening whether that be a single
day or opening as there is the status field to quickly turn it on or off vs
editing your repeating date field to remove a day.

- **Title:** Only shown to admins to quickly let you know what opening you're
looking at.
- **Status:** Easy turn it on/off for booking, you can turn the whole calendar
off in the Bookable Calendar or here to turn off a single Opening.
- **Bookable Calendar:** Reference to the calendar this opening is for.
- **Booking Instance:** Not user created but links openings to instances.
- **Date:** The date field that is repeatable for when this Opening occurs.

### Bookable Calendar Opening Instance

This is an entity that you don't create directly, but per time slot that is open based on the
date field on Bookable Calendar Opening an instance is created. Then users will register
for indivdual instances. The indivdual instances are also what shows up on the
listing of all bookings you book.
This is an entity that you don't create directly, but per time slot that is
open based on the date field on Bookable Calendar Opening an instance is
created. Then users will register for indivdual instances. The indivdual
instances are also what shows up on the listing of all bookings you book.

- **Booking:** Points to all the Bookings that have Booked this Instance.
- **Booking Opening:** Points to the parent Opening.
@@ -71,30 +83,33 @@ listing of all bookings you book.

This is the entity that a front end user will be creating when they register.

- **Email:** The email for this booking, this is used emails to confirm the booking and a
link to edit the booking.
- **Party Size:** The amount of people this booking is for, this lets us know how many slots
are being taken by this booking.
- **Email:** The email for this booking, this is used emails to confirm the
booking and a link to edit the booking.
- **Party Size:** The amount of people this booking is for, this lets us
know how many slots are being taken by this booking.
- **Booking Instance:** The instance this booking points to.
- **Booking:** Not user created, the bookings this contact is linked to.

### Booking

This is another entity no one creates directly, but when a new Contact is created a new
Booking is created per person in the party size. This is how we know how many people
have booked each instance easily.
This is another entity no one creates directly, but when a new Contact
is created a new Booking is created per person in the party size.
This is how we know how many people have booked each instance easily.

- **Created:** Time Booking was created.
- **Booking Instance:** The Booking Instance this Booking is for.
- **Booking Calendar:** The parent calendar this Booking is for. (this seems redundant but Instances might get cleaned up over time)
- **Booking Calendar:** The parent calendar this Booking is for. (this seems
redundant but Instances might get cleaned up over time)
- **Contact:** The conctact that owns this Booking.
- **Booking Date:** The date this Booking is for. This is because I see a situation where
you need to clean up old Instances as you will most likely have thousands over the
course of a year, but you might still want historic records of Bookings and what timeslot they booked.
- **Booking Date:** The date this Booking is for. This is because I see a
situation where you need to clean up old Instances as you will most likely
have thousands over the course of a year, but you might still want
historic records of Bookings and what timeslot they booked.

## API

There are some API endpoints exposed so you can create new Bookings however you'd like.
There are some API endpoints exposed so you can create new Bookings
however you'd like.

### Book an Instance

@@ -130,8 +145,8 @@ fetch("/bookable-calendar/{$instance_id}/book", requestOptions)
## Todo

- Cleanup no longer matching instances maybe on cron.
- Allow for cleanup on Cron based on settings of old Instances, Bookings and Contacts
if you don't care about things older than X months.
- Allow for cleanup on Cron based on settings of old Instances,
Bookings and Contacts if you don't care about things older than X months.

## Quickly Delete Everything

@@ -146,13 +161,13 @@ $openings = $opening_storage->loadMultiple();
foreach ($openings as $opening) {
  $opening->delete();
}
$instance_storage = \Drupal::entityTypeManager()->getStorage('bookable_calendar_opening_inst');
$instances = $instance_storage->loadMultiple();
$instanceStorage = \Drupal::entityTypeManager()->getStorage('bookable_calendar_opening_inst');
$instances = $instanceStorage->loadMultiple();
foreach ($instances as $instance) {
  $instance->delete();
}
$booking_contact_storage = \Drupal::entityTypeManager()->getStorage('booking_contact');
$booking_contacts = $booking_contact_storage->loadMultiple();
$booking_contactStorage = \Drupal::entityTypeManager()->getStorage('booking_contact');
$booking_contacts = $booking_contactStorage->loadMultiple();
foreach ($booking_contacts as $contact) {
  $contact->delete();
}
+22 −22
Original line number Diff line number Diff line
@@ -91,11 +91,10 @@ function bookable_calendar_views_data_alter(array &$data) {
}

/**
 * Replace all titles on pages to make them easier to understand as there are no real titles.
 * Replace all titles on pages to make them easier to understand.
 *
 * @param array $variables
 *
 * @return void
 *   Variables associated with hook_page_title.
 */
function template_preprocess_page_title(array &$variables) {
  $route_name = \Drupal::routeMatch()->getRouteName();
@@ -161,13 +160,13 @@ function template_preprocess_page_title(array &$variables) {
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the bookable calendar information and any
 *     fields attached to the entity.
 *   - elements: An associative array containing the bookable calendar
 *     information and any fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_bookable_calendar(array &$variables) {
  $entity_type_manager = \Drupal::service('entity_type.manager');
  $instance_storage = $entity_type_manager->getStorage('bookable_calendar_opening_inst');
  $entityTypeManager = \Drupal::service('entity_type.manager');
  $instanceStorage = $entityTypeManager->getStorage('bookable_calendar_opening_inst');
  $this_calendar = $variables['content']['#bookable_calendar'];
  $query = \Drupal::database()->select('bookable_calendar__calendar_openings', 'cal');
  $query->fields('cal', [
@@ -183,7 +182,7 @@ function template_preprocess_bookable_calendar(array &$variables) {
  $opening_instances = $query->execute()->fetchAllAssoc('id');
  if ($opening_instances) {
    $instances = array_keys($opening_instances);
    $loaded_instances = $instance_storage->loadMultiple($instances);
    $loaded_instances = $instanceStorage->loadMultiple($instances);
    $variables['content']['instances'] = [];
    foreach ($loaded_instances as $key => $instance) {
      $render_instance = Drupal::entityTypeManager()->getViewBuilder('bookable_calendar_opening_inst')->view($instance);
@@ -199,18 +198,18 @@ function template_preprocess_bookable_calendar(array &$variables) {
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the booking calendar opening information and any
 *     fields attached to the entity.
 *   - elements: An associative array containing the booking calendar opening
 *     information and any fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_bookable_calendar_opening(array &$variables) {
  $entity_type_manager = \Drupal::service('entity_type.manager');
  $instance_storage = $entity_type_manager->getStorage('bookable_calendar_opening_inst');
  $entityTypeManager = \Drupal::service('entity_type.manager');
  $instanceStorage = $entityTypeManager->getStorage('bookable_calendar_opening_inst');
  $this_opening = $variables['content']['#bookable_calendar_opening'];
  $instances = $this_opening->get('booking_instance')->getValue();
  $variables['instances'] = [];
  foreach ($instances as $key => $instance_id) {
    $instance = $instance_storage->load($instance_id['target_id']);
    $instance = $instanceStorage->load($instance_id['target_id']);
    if ($instance) {
      $render_instance = Drupal::entityTypeManager()->getViewBuilder('bookable_calendar_opening_inst')->view($instance);
      $variables['instances'][$key] = $render_instance;
@@ -226,8 +225,8 @@ function template_preprocess_bookable_calendar_opening(array &$variables) {
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the booking contact information and any
 *     fields attached to the entity.
 *   - elements: An associative array containing the booking contact
 *     information and any fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_booking_contact(array &$variables) {
@@ -241,8 +240,8 @@ function template_preprocess_booking_contact(array &$variables) {
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the booking information and any
 *     fields attached to the entity.
 *   - elements: An associative array containing the booking information
 *     and any fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_booking(array &$variables) {
@@ -256,8 +255,8 @@ function template_preprocess_booking(array &$variables) {
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the bookable calendar opening instance information and any
 *     fields attached to the entity.
 *   - elements: An associative array containing the bookable calendar
 *     opening instance information and any fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_bookable_calendar_opening_inst(array &$variables) {
@@ -468,7 +467,7 @@ function bookable_calendar_tokens($type, $tokens, array $data, array $options, B
}

/**
 *
 * Form alters.
 */
function bookable_calendar_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ($form_id === 'booking_contact_add_form') {
@@ -517,7 +516,7 @@ function bookable_calendar_form_alter(&$form, FormStateInterface $form_state, $f
}

/**
 *
 * Redirect to front page when you delete your own booking.
 */
function bookable_calendar_delete_set_redirect($form, FormStateInterface $form_state) {
  $form_state->setRedirect('<front>');
@@ -531,7 +530,8 @@ function bookable_calendar_views_post_execute(ViewExecutable $view) {
  if ($view->getBaseTables()['bookable_calendar_opening_inst'] ?? NULL) {
    // Handle available slots filter.
    if ($slots_filter = $view->storage->get('bookable_calendar_available_slots_filter')) {
      // Get filter values & operator and apply logic based on the instance's available slots.
      // Get filter values & operator and apply logic
      // based on the instance's available slots.
      $value = $slots_filter['value']['value'] ?? NULL;
      $min = $slots_filter['value']['min'] ?? NULL;
      $max = $slots_filter['value']['max'] ?? NULL;
+1 −1
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ use Drupal\Core\Field\FieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;

/**
 * Item list for a computed field that displays the available slots for a bookable calendar opening instance.
 * Item list for a computed field that displays the available slots.
 */
class AvailableSlotsItemList extends FieldItemList {

+12 −3
Original line number Diff line number Diff line
@@ -22,10 +22,16 @@ class BookableCalendarAccessControlHandler extends EntityAccessControlHandler {
        return AccessResult::allowedIfHasPermission($account, 'view bookable calendar');

      case 'update':
        return AccessResult::allowedIfHasPermissions($account, ['edit bookable calendar', 'administer bookable calendar'], 'OR');
        return AccessResult::allowedIfHasPermissions($account, [
          'edit bookable calendar',
          'administer bookable calendar',
        ], 'OR');

      case 'delete':
        return AccessResult::allowedIfHasPermissions($account, ['delete bookable calendar', 'administer bookable calendar'], 'OR');
        return AccessResult::allowedIfHasPermissions($account, [
          'delete bookable calendar',
          'administer bookable calendar',
        ], 'OR');

      default:
        // No opinion.
@@ -38,7 +44,10 @@ class BookableCalendarAccessControlHandler extends EntityAccessControlHandler {
   * {@inheritdoc}
   */
  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
    return AccessResult::allowedIfHasPermissions($account, ['create bookable calendar', 'administer bookable calendar'], 'OR');
    return AccessResult::allowedIfHasPermissions($account, [
      'create bookable calendar',
      'administer bookable calendar',
    ], 'OR');
  }

}
+13 −4
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Defines the access control handler for the bookable calendar opening entity type.
 * Defines the access control handler for the bookable calendar opening.
 */
class BookableCalendarOpeningAccessControlHandler extends EntityAccessControlHandler {

@@ -22,10 +22,16 @@ class BookableCalendarOpeningAccessControlHandler extends EntityAccessControlHan
        return AccessResult::allowedIfHasPermission($account, 'view bookable calendar opening');

      case 'update':
        return AccessResult::allowedIfHasPermissions($account, ['edit bookable calendar opening', 'administer bookable calendar opening'], 'OR');
        return AccessResult::allowedIfHasPermissions($account, [
          'edit bookable calendar opening',
          'administer bookable calendar opening',
        ], 'OR');

      case 'delete':
        return AccessResult::allowedIfHasPermissions($account, ['delete bookable calendar opening', 'administer bookable calendar opening'], 'OR');
        return AccessResult::allowedIfHasPermissions($account, [
          'delete bookable calendar opening',
          'administer bookable calendar opening',
        ], 'OR');

      default:
        // No opinion.
@@ -38,7 +44,10 @@ class BookableCalendarOpeningAccessControlHandler extends EntityAccessControlHan
   * {@inheritdoc}
   */
  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
    return AccessResult::allowedIfHasPermissions($account, ['create bookable calendar opening', 'administer bookable calendar opening'], 'OR');
    return AccessResult::allowedIfHasPermissions($account, [
      'create bookable calendar opening',
      'administer bookable calendar opening',
    ], 'OR');
  }

}
Loading