Quantcast
Channel: drupalpress » rules views integration
Viewing all articles
Browse latest Browse all 6

equipment booking system — managing reservation conflicts with field validation and EntityFieldQuery()

$
0
0

The first requirement of a registration system is to have something to reserve.

The second requirement of a registration system is to manage conflicting reservations.

Setting up validation of submitted reservations based on the existing reservation nodes was probably the most complex part of this project. A booking module like MERCI has this functionality baked in — but again, that was too heavy for us so we had to do it on our own. We started off on a fairly thankless path of Views/Rules integration. Basically we were building a view of existing reservations, contextually filtering that view by content id (the item that someone was trying to reserve) and then setting a rule that would delete the content and redirect the user to a “oops that’s not available” page. We ran into issues building the view contextually with rules (for some reason the rule wouldn’t pass the nid …) and even if we would have got that wired up, it would have been clunky.

Scrap that.

On to Field Validation.

The Field Validation module offers client-side form validation (not to be confused with Clientside Validation or Webform Validation based on any number of conditions at the field level. We were trying to validate the submitted reservation on length (no longer than 5 days) and availability (no reservations of the same item during any of the days requested).

The length turned out to be pretty straightforward — we set the “Date range2″ field validator on the reservation date field. The validator lets you choose “global” date format, which means you can input logic like “+ X days” so long as it can be converted by the strtotime() function.

field validation supports global date values, which means anything that the strtotime() PHP function can convert

 

Field Validation also gives you configurations to bypass the validation criteria by role — this was helpful in our case given that there are special circumstances when “approved” reservations can be made for longer than 5 days. And if something doesn’t validate, you can plug in a custom error message in the validator configuration.

bypass length restriction so staff can accommodate special cases for longer reservations, and field validation also lets you write a custom error message

 

With the condition set for the length of the reservation, we could tackle the real beast. Determining reservation conflicts required us to use the “powerfull [sic] but dangerous” PHP validator from Field Validation. Squirting custom code into our Drupal instance is something we try to avoid as much as possible — it’s difficult to maintain … and as you’ll see below it can be difficult to understand. To be honest, a big part of the impetus for writing this series of blog posts was to document the 60+ lines of code that we strung together to get our booking system to recognize conflicts.

The script starts by identifying information about the item that the patron is trying to reserve  (item = $arg1, checkout date = $arg2, return date = $arg3) and then builds an array of dates from the start to finish of the requested reservation. Then we use EntityFieldQuery() to find all of the reservations that have dates less than or equal to the end date request. That’s where we use the fieldCondition() with <= to the $arg3_for_fc value. What that gives us is all of the reservations on that item that could possibly conflict. Then we sort by descending and trim the top value out of the list to get the nearest reservation to the date requested. With that record in hand, we can build another array of start and end dates and use array_intersetct() to see if there is any overlap.

I bet that was fun to read.

I’ll leave you with the code and comments:

<?php
//find arguments from nid and dates for the requested reservation
$arg1 = $this->entity->field_equipmentt_item[und][0][target_id];

$arg2 = $this->entity->field_reservation_date[und][0][value];
$arg2 = new DateTime($arg2);

$arg3 = $this->entity->field_reservation_date[und][0][value2];
$arg3 = new DateTime($arg3);
$arg3_for_fc = $arg3->format("Ymd");


//build out array of argument dates for comparison with existing reservation
$beginning = $arg2;
$ending = $arg3;
$ending = $ending->modify( '+1 day' );

$argumentinterval = new DateInterval('P1D');
$argumentdaterange = new DatePeriod($beginning, $argumentinterval ,$ending);

$arraydates = array();
foreach($argumentdaterange as $argumentdates){
 $arraydates []= $argumentdates->format("Ymd");  
}

//execute entityfieldquery to find the most recent reservation that could conflict

$query = new EntityFieldQuery();

$fullquery = $query->entityCondition('entity_type', 'node')
  ->entityCondition('bundle', 'reservation')
  ->propertyCondition('status', NODE_PUBLISHED)
  ->fieldCondition('field_equipmentt_item', 'target_id', $arg1, '=')
  ->fieldCondition('field_reservation_date', 'value', $arg3_for_fc, '<=')
  ->fieldOrderBy('field_reservation_date', 'value', 'desc')
  ->range(0,1);

$fetchrecords = $fullquery->execute();

if (isset($fetchrecords['node'])) {
  $reservation_nids = array_keys($fetchrecords['node']);
  $reservations = entity_load('node', $reservation_nids);
}

//find std object for the nearest reservation from the top
$reservations_test = array_slice($reservations, 0, 1);

//parse and record values for dates
$startdate = $reservations_test[0]->field_reservation_date[und][0][value];
$enddate = $reservations_test[0]->field_reservation_date[und][0][value2];

//iterate through to create interval date array
$begin = new DateTime($startdate);
$end = new DateTime($enddate);
$end = $end->modify( '+1 day' );

$interval = new DateInterval('P1D');
$daterange = new DatePeriod($begin, $interval ,$end);

$arraydates2 = array();
foreach($daterange as $date){
 $arraydates2 []= $date->format("Ymd");  
}

$conflicts = array_intersect($arraydates, $arraydates2);

if($conflicts != NULL){
$this->set_error();
}
?>

Viewing all articles
Browse latest Browse all 6

Latest Images

Trending Articles





Latest Images