The Advanced Booking Rule is no longer used, use Workflows instead.

 

Advanced Booking Rules provide a means for the implementation of almost anything required. They allow the configurer to write a Booking Rule using Java that will be run using BeanShell. This means it requires a programmer to create an Advanced Booking Rule. This documentation includes some example Booking Rules, so that a programmer may be able to produce the effect they want from what's here, but this is not a complete reference to the functionality available inside an Advanced Booking Rule. A complete programmer's API documentation may be made available at some time, but for now, to create an Advanced Booking Rule, either try to adapt the examples here, or contact us for help.

 

When creating an Advanced Rule a user needs to define how Advanced Rules will work with repeat bookings. Normally rules are run once for each instance of a repeat booking.

 

Option

Description

Run for each repeating instance

The Rule is given a copy of the repeat booking. Consequently, if the rule modifies this copy, changes made to it will not be saved to the database nor seen by subsequently run rules.

Run once

The rule is given the original repeat booking, and changes the Rule makes to the booking will then be persisted and also available to subsequent Rules.

 

Note: that for repeat bookings, advanced Rules marked as being run once will always be run before all other Rules regardless of requested Rule execution order.

 

When your Booking Rule is run, the following local variables will be set in the BeanShell environment:

 

Name

Class

Description

biskitDAO

com.springsolutions.biskit.persistence.HibernateBiskitDAO

Interface to the Calpendo database.

biskitDefs

com.springsolutions.biskit.core.def.BiskitDefStore

Provides access to all the Biskit definitions.

booking

com.springsolutions.calpendo.domain.Booking

The booking being created or the new version of the booking being updated. Identical to newBooking.

newBooking

com.springsolutions.calpendo.domain.Booking

The booking being created or the new version of the booking being updated. Identical to booking.

oldBooking

com.springsolutions.calpendo.domain.Booking

The original version of the booking for an update, and null otherwise.

realUser

com.springsolutions.calpendo.domain.CalpendoUser

The user making the change.

user

com.springsolutions.calpendo.domain.CalpendoUser

The effective user making the change. This can be different from the real user when running with The Booking Rule Validator.

isUpdate

Boolean

True if the user is making an update, and false if a booking is being created.

result

com.springsolutions.calpendo.domain.rule.RulesResult

The result that must be filled in by the Booking Rule.

rule

com.springsolutions.calpendo.domain.rule.AdvancedRule

The Booking Rule itself. Sometimes you may want to insert the Booking Rule name into the error message shown to a user.

API

The RulesResult class, as represented by local variable result, is the way to pass information back to Calpendo. It has the following API:

 

public interface RulesResult
{
  /** Sets the message that should be sent to the user. */
  public void setMessage(String msg);
 
  /** Sets an output string to be displayed when calling via
    * The Rule Validator. */

  public void setOutput(String output);
 
  /** Specify whether to reject, warn or accept the booking. */
  public void setRejectionLevel(RejectionLevel level);
 
  /** Specify whether to retry the booking as a request. 
    * This is ignored if the booking status was not Approved. */
  public void setTryBookingRequest(boolean tryRequest);

}

 

RejectionLevel is an enum as follows:

 

package com.springsolutions.calpendo.domain.rule;
 
public enum RejectionLevel
{
   ACCEPT("Accept booking"), 
   WARN("Warn"), 
   REJECT("Reject");
   
   private String m_label;
   
   private RejectionLevel(String label)
   {
      m_label = label;
   }
   
   @Override
   public String toString()
   {
      return m_label;
   }
}

Example: Reject Bookings More Than 6 Weeks Into The Future

Here is an example of an Advanced Booking Rule that will reject bookings made more than 6 weeks into the future. Note that this can be done both with a Simple Booking Rule and also (better still) with Time Templates. However, this serves as an example of how to use advanced Booking Rules.

 

import com.springsolutions.calpendo.domain.*;
import com.springsolutions.calpendo.domain.rule.*;
import java.util.Calendar;
 
// Throw away daylight savings effect.
// This is technically incorrect, but is what people expect when 
// crossing DST boundaries (ie use the time shown on the wall
// clock, not the number of minutes between now and then)
 
then_cal = Calendar.getInstance();
then_cal.setTime(booking.getDateRange().getStart());
then_cal.set(Calendar.DST_OFFSET, 0);
then_time = then_cal.getTime().getTime();
 
now_cal = Calendar.getInstance();
now_cal.set(Calendar.DST_OFFSET, 0);
now_time = now_cal.getTime().getTime();
 
days = (then_time - now_time)/(1000.0*60*60*24);
 
if (days > 42)
{
  int idays = (int) days;
  int ihours = (int) ((days-idays)*24);
  int imins = (int) ((days - idays)*24*60 - ihours*60);
  result.setRejectionLevel(RejectionLevel.REJECT);
  result.setMessage(
        "You cannot book more than 6 weeks in advance "
        + "(your request was " + idays + " days, "
        + ihours + " hours and " + imins 
        + " minutes in advance)."
        + " Please discuss any special requirements with a "
        + "moderator. A list of moderators can be found under "
        + "the search menu." );
}

Example: Enforcing Project Resource Settings

Calpendo uses Hibernate for accessing its database. The database can be accessed directly by obtaining a Hibernate SessionFactory as shown by this example. If this is done, make sure that the Hibernate session is closed. This Booking Rule does the following:

Only run for bookings that have a project associated

Reject the booking if trying to book for a resource that's not represented by the Project's Resource Settings

Reject the booking if it is for a time after the project's finish property, where finish is a dynamic property.

Reject the booking if its duration is more than the minutesPerSession property of the Project's Resource Settings

Use Hibernate to count the total number of Approved and Requested bookings for the project, and reject the booking if there would be more bookings than the numberOfSessions property of the Project's Resource Settings

These tasks are more easily done using the dedicated Booking Rule Types, but this Booking Rule serves as a good example of how to interact with the Calpendo database directly through Hibernate.

 

import com.springsolutions.calpendo.domain.*;
import com.springsolutions.calpendo.domain.rule.*;
import com.springsolutions.exprodo.core.server.HibernateUtil;
import java.util.Calendar;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
 
resource = booking.getResource();
rpk = resource.getPrimaryKey();
project = booking.getProject();
if (project == null) return;
 
prsSet = project.getResourceSettings();
prs = null;
for (ProjectResourceSettings tmp : prsSet)
{
  if (tmp.getResource().getPrimaryKey() == rpk)
  {
    prs = tmp;
    break;
  }
}
 
if (prs == null)
{
  result.setRejectionLevel(RejectionLevel.REJECT);
  result.setMessage(
    "You are trying to book for a project (" 
    + project.getProjectCode()
    + ", " + project.getName() 
    + ") that has not been approved for using "
    + resource.getName());
  return;
}
 
endDateProp = project.get("finish");
endDate = (endDateProp == null) ? null : endDateProp.getValue();
if (endDate != null 
    && booking.getDateRange().getFinish().after(endDate))
{
  result.setRejectionLevel(RejectionLevel.REJECT);
  result.setMessage(
    "You are trying to book for a project ("
    + project.getProjectCode()
    + ", " + project.getName() 
    + ") after its project end date (" + endDate + ")");
  return;
}
 
duration = booking.getDurationInMinutes();
maxDuration = prs.getMinutesPerSession();
if (duration > maxDuration)
{
  result.setRejectionLevel(RejectionLevel.REJECT);
  result.setMessage(
    "Bookings for project (" + project.getProjectCode()
    + ", " + project.getName() 
    + ") should be no longer than " + maxDuration 
    + " minutes, but you've tried to make a booking for " 
    + duration + " minutes");
  return;
}
 
hql = "select count(*) from " + Booking.TYPE + " where project.id = " 
       + project.getPrimaryKey()
       + " and (status = 'REQUESTED' or status = 'APPROVED') "
       + " and resource.id = " + rpk;
 
if (booking.getPrimaryKey() != 0)
  hql = hql + " and id != " + booking.getPrimaryKey();
 
SessionFactory sf = HibernateUtil.getSessionFactory();
Session session = null;
try
{
  session = sf.openSession();
  count = session.createQuery(hql).uniqueResult();
  print("Query found a total of " + count 
        + " bookings for this project and resource");
  if (booking.getPrimaryKey() == 0)
    count++;
 
  maxCount = prs.getNumberOfSessions();
 
  if (count > maxCount)
  {
    result.setRejectionLevel(RejectionLevel.REJECT);
    result.setMessage(
      "There can be a total of " + maxCount 
      + " bookings for project (" + project.getProjectCode()
      + ", " + project.getName() 
      + " on " + resource.getName()
      + "), but this booking would make a total of " + count);
    return;
  }
}
finally
{
  if (session != null)
    session.close();
}

Example: Enforce 15 Minute Gaps Between Bookings

This Booking Rule will ensure that there is always a 15 minute gap between bookings. If using this Booking Rule, it may be a good idea to use the Rule's Applies To --> Resources tab to make sure that it only applies to the appropriate resources. Before using this please look at the standard Booking Interval Rule as this may be much easier to implement what you want.

 

import com.springsolutions.biskit.core.Biskit;

import com.springsolutions.calpendo.domain.Booking;

import com.springsolutions.calpendo.domain.rule.RejectionLevel;

import com.springsolutions.exprodo.core.server.HibernateUtil;

import com.springsolutions.timeRepeating.domain.RepeatUtil;

import java.text.SimpleDateFormat;

import java.util.Calendar;

import java.util.Date;

import java.util.List;

import org.hibernate.Session;

import org.hibernate.Query;

 

session = HibernateUtil.getSessionFactory().openSession();

sqlFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

 

try

{

  Calendar cal = Calendar.getInstance();

  cal.setTime(booking.getDateRange().getStart());

  cal.add(Calendar.MINUTE, -15);

  Date start = cal.getTime();

  cal.setTime(booking.getDateRange().getFinish());

  cal.add(Calendar.MINUTE, +15);

  Date finish = cal.getTime();

 

  String hql = "from " + Booking.TYPE + " b where not (b.dateRange.start > '"

          + sqlFormatter.format(finish)

          + "' or b.dateRange.finish < '"

          + sqlFormatter.format(start)

          + "') and b.resource.id=" 

          + booking.getResource().getPrimaryKey()

          + " and b.status != 'CANCELLED' "

          + " and b.status != 'DENIED'";

 

  searchResult = session.createQuery(hql).list();

 

  found = RepeatUtil.expandRepeatables(searchResult, start, finish);

  boolean doubleBooking=false, bufferInfringed=false;

  bookingStart = booking.getDateRange().getStart().getTime();

  bookingFinish = booking.getDateRange().getFinish().getTime();

 

  for (Biskit b : found)

  {

    Booking clash = (Booking) b;

    dr = clash.getDateRange();

    if (b.getPrimaryKey() == booking.getPrimaryKey()

      || dr.getStart().getTime() >= finish.getTime()

      || dr.getFinish().getTime() <= start.getTime())

      continue;

 

    if (dr.getStart().getTime() >= bookingFinish

      || dr.getFinish().getTime() <= bookingStart)

      bufferInfringed = true;

    else

      doubleBooking = true;

  }

 

  String msg;

  if (doubleBooking)

    result.setMessage("Double bookings are not allowed");

  else if (bufferInfringed)

    result.setMessage(

      "You must leave a gap of 15 minutes between bookings");

 

  if (doubleBooking || bufferInfringed)

    result.setRejectionLevel(RejectionLevel.REJECT);

}

finally

{

  session.close();

}