Problem: Increasing quantity of product in cart using update cart for registration enabled product, allows user to register more spaces than currently available.

To recreate:
Manage Registrations -> Settings -> Set Capacity.
(for illustrative purposes, set to 2)

View the page/product with registrations enabled. Add to cart.
Navigate to the "cart". Change quantity of the product which is larger than the available slots.
For illustrative purposes, set here to say 3.
Cart will update. Checkout process will allow submitting the order and completion even though system has essentially overbooked.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

aj2’s picture

I'm not sure of the proper solution (and this solution seems inefficient), but here is the code I used to solve the problem. Sharing in case it is of use to somebody and also in hope that someone might be able to improve it.

Disclaimer: this is the first drupal module code I've ever written.

<?php
/**
 * Implements hook_commerce_cart_order_refresh().
 *
 * Alter the line items to make sure we are not overbooking a registration prod 
 */
function mymodule_commerce_cart_order_refresh($order_wrapper) {

  if (commerce_cart_order_is_cart($order_wrapper->value())) {

  // Keep an array with the sum of qty
  $qty_sum  = array();

  // Keep an array with the list of potentially overbooked line items
  $overbooked_prod_id = array();

  // Keep list of Warning Messages
  $msg_array = array();

  // ** First Pass:  
  // Make sure individual line items do not exceed available registrations

  // For each of the line items in the order
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper)
   {

     if (!in_array($line_item_wrapper->type->value(),
                commerce_product_line_item_types())) {
      return False;
     }

     // Get the line item  so we can work on it
     $line_item = $line_item_wrapper->value();

     // Gets the quantity of this line item in the cart
     $qty = $line_item_wrapper->quantity->value() ;

     // Gets the unique product id for the line item
     $product_id = $line_item_wrapper->commerce_product->product_id->value() ;

     // Attempt to get registration settings for this product
     $settings = registration_entity_settings('commerce_product', $product_id);

     // Check if this product is registration enabled
     if (!empty($settings) && $settings['status'] == 1 )
     {

       // For this product id, see how many are available
       $capacity = (int) $settings['capacity'];
       $filled = (int) registration_event_count('commerce_product',$product_id);
       $avail = $capacity - $filled;

       // If the line item qty is more than available, set the
       // line item to 1 to avoid over booking
       if ( $qty > $avail )
       {
         // Set line item qty to 1
         mymodule_set_line_item_qty( $line_item, 1.0 );
         if (!array_key_exists($product_id, $qty_sum))
         {
           $qty_sum[$product_id] = 1.0;
         }
         else
         {
           $qty_sum[$product_id] += 1.0;
         }

         //  Display a warning message.
         $msg_array[$product_id] = "There are only  {$avail} available slots for {$line_item_wrapper->commerce_product->title->value()}.  Please adjust your cart quantity." ;

       }
       else
       {
         // Let's add up the sum of all the same products in the cart
         // needed since  disabled the 
         //'Attempt to combine like products on the same line item in the cart.'
         if (!array_key_exists($product_id, $qty_sum))
         {
          $qty_sum[$product_id] = $line_item_wrapper->quantity->value() ;
         }
         else
         {
          $qty_sum[$product_id] += $line_item_wrapper->quantity->value() ;
         }
       }


       // Check if the sum in cart would result in overbooking
       if ( $qty_sum[$product_id] > $avail )
       {
         $overbooked_prod_id[$product_id] = True ;
       }



     } // if settings

   } // for each line item



  // ** Second Pass:  
  //  if Sum of line item quantities would cause overbooking
  //   First try setting all of those products quantity to 1
  //  then re-check qty sum.  if still exceeds, then remove line item

  $qty_sum  = array();   // reset

  // For each of the line items in the order
  if(!empty( $overbooked_prod_id))
  {
   foreach($order_wrapper->commerce_line_items as $delta => $line_item_wrapper)
   {
     // If this line item product id is in the overbooked array, set qty 1
     $line_item = $line_item_wrapper->value();

     // Gets the unique product id for the line item
     $product_id = $line_item_wrapper->commerce_product->product_id->value() ;

     if ( isset($overbooked_prod_id[$product_id]) )
      {
         // Set line item qty to 1
         mymodule_set_line_item_qty( $line_item, 1.0 );
         if (!array_key_exists($product_id, $qty_sum))
         {
           $qty_sum[$product_id] = 1.0;
         }
         else
         {
           $qty_sum[$product_id] += 1.0;
         }

         // For this product id, see how many are available
         // Attempt to get registration settings for this product
         $settings = registration_entity_settings('commerce_product', $product_id);
         $capacity = (int) $settings['capacity'];
         $filled = (int) registration_event_count('commerce_product',$product_id);
         $avail = $capacity - $filled;

         // Warning Message
         $msg_array[$product_id] = "There are only  {$avail} available slots for {$line_item_wrapper->commerce_product->title->value()}.  Please adjust your cart quantity." ;




         // Check if the sum in cart would result in overbooking
         // This time, we remove it.
         if ( $qty_sum[$product_id]  > $avail )
         {
           // Delete the line item
           // do not need to save order, as done after the hook for us
           commerce_cart_order_product_line_item_delete(
            $order_wrapper->value(), $line_item->line_item_id, $skip_save=TRUE);
            //$order_wrapper->value(), $line_item->line_item_id );
           $qty_sum[$product_id] -= 1.0;

           // Warning Message
           $msg_array[$product_id] = "There are only  {$avail} available slots for {$line_item_wrapper->commerce_product->title->value()}.  Item was removed from  your cart." ;


         }

      } // if isset overbook prod id

   } // for each line item
  } // not empty overbooked prod id array

 // Display warning messages for any products that were overbooked
 foreach ($msg_array as $msg)
 {
   drupal_set_message(  $msg  , 'warning', TRUE);
 }

 } // end if is order

}




function mymodule_set_line_item_qty( $line_item, $qty )
{

     // force the line item quantity
     $line_item->quantity = $qty;

     // Save the updated line item and reset cache
     commerce_line_item_save($line_item);
     entity_get_controller('commerce_line_item')->resetCache(array($line_item->line_item_id));

}


?>
rparree’s picture

Thanks for posting this!

I am testing your code in our site as well. Did you since December made any changes to the code?

rparree’s picture

I had an order which caused the event to get overbooked (before i used the code above). When i went to that order in the admin view, the script removed the registrations with the message "There are only -1 available slots for Xxx. Items was removed from your cart" (in this case 4 times)

I guess the status of the order was still "Shopping Cart". But just sharing in case there might be an issue

laughnan’s picture

Subscribing.

maxplus’s picture

Hi,
great for the cart update.

But I think there is also a problem on the initial add to cart action.
You can also overbook there.
If you have for example 1 space available, I can just add 3 items to my cart without getting a notice.
After the checkout is complete, you see that your event is overbooked without a single message.

The only capacity limit that I see that is working, is that if there are no spaces available anymore, you cannot add a single item to the cart, you get the message that no registrations are available at the moment.

blacklabel_tom’s picture

Version: 7.x-2.0-beta5 » 7.x-2.x-dev
Status: Active » Fixed

Hi All,

This has been merged into the dev branch: http://cgit.drupalcode.org/commerce_registration/commit/?id=e3d1c99

I've attempted to give credit, let me know if it doesn't appear on your profile.

Cheers

Tom

  • blacklabel_tom committed 7722426 on 7.x-2.x
    Issue #2126417 by aj2,imfaber: Fixed issue with overbookings.
    

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

aj2’s picture

Probably a good idea to add a check to verify if registration is possible as a third pass.

For example:

<?php
  // ** Third Pass:
  // Verify if registration is possible
  // We have already checked capacity, check open/close dates and reg status
  // if not available, then remove

   foreach($order_wrapper->commerce_line_items as $delta => $line_item_wrapper)
   {

     if (!in_array($line_item_wrapper->type->value(),
                commerce_product_line_item_types())) {
      continue ;
     }

     // Get the line item  so we can work on it
     $line_item = $line_item_wrapper->value();

     // Gets the unique product id for the line item
     $product_id = $line_item_wrapper->commerce_product->product_id->value() ;

     // Attempt to get registration settings for this product
     $settings = registration_entity_settings('commerce_product', $product_id);

     // Check if this product is registration enabled
     if (!empty($settings) )
     {
      $status = $settings['status'];
      $open = isset($settings['open']) ? strtotime($settings['open']) : NULL;
      $close = isset($settings['close']) ? strtotime($settings['close']) : NULL;
      $now = REQUEST_TIME;

      // remove line item if not open
      if ($status)
      {
        // check open date range
        if (isset($open) && ($now < $open)) {
          $status = FALSE;
        }
        // check close date range
        if (isset($close) && ($now >= $close)) {
          $status = FALSE;
        }
      }

      // Delete the line item
      if ($status == FALSE )
      {
       // do not need to save order, as done after the hook for us
       commerce_cart_order_product_line_item_delete(
            $order_wrapper->value(), $line_item->line_item_id, $skip_save=TRUE);

       // Warning Message
       $msg_array[$product_id] = "Registration is not available for {$line_item_wrapper->commerce_product->title->value()}.  Item was removed from your cart." ;
      }

     } // if settings


   } // for each line item
?>
blacklabel_tom’s picture

Status: Closed (fixed) » Needs review

  • blacklabel_tom committed 68a99ed on 7.x-2.x
    Issue #2126417 by aj2, blacklabel_tom: Update Cart Quantity Allows...
blacklabel_tom’s picture

Status: Needs review » Closed (fixed)

Hi Aj2,

Added your third pass to the dev branch.

Cheers

Tom