function doAllocate()
 {
     // query all (bed-level) resources available with no allocations for
     // any of the dates given ordered by resource_id
     $availResourceIds = AllocationDBO::fetchAvailableBeds($this->resourceId, $this->reqRoomType, $this->bookingDates, $this->resourceProps);
     // loop thru $existingAllocationRows removing those resources already present AND
     // where there is *any* overlap in dates
     foreach ($this->existingAllocationRows as $row) {
         if ($row->isExistsBookingForAnyDate($this - bookingDates)) {
             // remove from $availResourceIds if any of the booking dates overlap
             if (($key = array_search($row->resourceId, $availResourceIds)) !== false) {
                 unset($availResourceIds[$key]);
             }
         }
     }
     // of those that remain; find first available where they belong to the same room
     // and #available in room <= sum($numF, $numM, $numX)
     // this will be sorted first
     $availResourceIds = sortFirstResourcesThatWillFitCurrentAllocation($availResourceIds);
     // now we go through newAllocationRows and assign resourceIds in turn
     foreach ($availResourceIds as $bedResourceId) {
         foreach ($this->newAllocationRows as $row) {
             if ($row->resourceId == $this->resourceId) {
                 $row->resourceId = $bedResourceId;
                 break;
             }
         }
     }
 }
 /**
  * Toggles the checkout state of the given allocation and position in allocationCells.
  * $allocationId : id of allocation
  * $posn : position within allocationCells to toggle (multiple checkout dates may be possible for any given allocation)
  */
 function toggleCheckoutOnBookingDate($allocationId, $posn)
 {
     // find out the date that was toggled by adding the offset to minDate
     $checkoutDate = clone $this->showMinDate;
     $checkoutDate->add(new DateInterval('P' . $posn . 'D'));
     AllocationDBO::toggleCheckoutOnBookingDate($allocationId, $checkoutDate);
 }
 /**
  * Runs the search by allocation grouped by resource.
  * Saves the result in the current object.
  */
 function doSearch()
 {
     $bookingResources = AllocationDBO::getAllocationsByResourceForDateRange($this->showMinDate, $this->showMaxDate, $this->resourceId, null, null);
     if (sizeof($bookingResources) != 1) {
         error_log("Unexpected number of items in AllocationViewResource::doSearch() ");
         foreach ($bookingResources as $br) {
             error_log("Found BookingResource {$br->resourceId}");
         }
     }
     if (sizeof($bookingResources) >= 0) {
         $this->bookingResource = array_shift($bookingResources);
     } else {
         $this->bookingResource = null;
     }
 }
 /**
  * Returns a new BookingSummary object for the given booking id.
  * $bookingId : id of booking to get
  * Returns non-null object. Throws DatabaseException if not found.
  */
 static function fetchBookingSummaryForId($bookingId)
 {
     global $wpdb;
     $resultset = $wpdb->get_results($wpdb->prepare("SELECT bk.booking_id, bk.firstname, bk.lastname, bk.referrer, bk.created_by, bk.created_date\r\n               FROM " . $wpdb->prefix . "booking bk\r\n              WHERE bk.booking_id = %d", $bookingId));
     if ($wpdb->last_error) {
         error_log("Failed to execute query " . $wpdb->last_query);
         throw new DatabaseException($wpdb->last_error);
     }
     foreach ($resultset as $res) {
         $summary = new BookingSummary($res->booking_id, $res->firstname, $res->lastname, $res->referrer, $res->created_by, new DateTime($res->created_date));
         $summary->guests = AllocationDBO::fetchGuestNamesForBookingId($res->booking_id);
         $summary->statuses = AllocationDBO::fetchStatusesForBookingId($res->booking_id);
         $summary->resources = ResourceDBO::fetchResourcesForBookingId($res->booking_id);
         $summary->comments = self::fetchBookingComments($res->booking_id, BookingComment::COMMENT_TYPE_USER);
         $summary->bookingDates = AllocationDBO::fetchDatesForBookingId($res->booking_id);
         $summary->isCheckoutAllowed = AllocationDBO::isCheckoutAllowedForBookingId($res->booking_id);
         return $summary;
     }
     throw new DatabaseException("Booking ID {$bookingId} not found!");
 }
 /**
  * Saves current allocation to the db.
  * $mysqli : manual db connection (for transaction handling)
  * $bookingId : booking id for this allocation
  * Returns allocation id of newly created record
  * Throws AllocationException on resource conflict
  */
 function save($mysqli, $bookingId)
 {
     global $wpdb;
     // create the allocation if it doesn't exist
     if ($this->id == 0) {
         $allocationId = AllocationDBO::insertAllocation($mysqli, $bookingId, $this->resourceId, $this->name, $this->gender, $this->reqRoomSize, $this->reqRoomType);
         error_log("inserted allocation {$allocationId}");
         // then create the booking dates for the allocation
         AllocationDBO::insertBookingDates($mysqli, $allocationId, $this->bookingDates);
     } else {
         // update the existing allocation
         AllocationDBO::updateAllocation($mysqli, $this->id, $this->resourceId, $this->name, $this->gender, $this->resourceMap);
         error_log("updating allocation {$this->id}");
         // clear out those with blank statuses (these are dates now marked as 'available')
         foreach ($this->bookingDates as $dt => $bookingDate) {
             if ($bookingDate->status == '') {
                 unset($this->bookingDates[$dt]);
             }
         }
         // diff existing booking dates with the ones we want to save
         AllocationDBO::mergeUpdateBookingDates($mysqli, $this->id, $this->bookingDates);
     }
     return $allocationId;
 }
 public function testCancellationNoShowInDormStillBookable()
 {
     self::createTestBooking("NoShowFemales", "FemalesInMixedDorm", array("M" => 0, "F" => 2, "X" => 0), 132, "10", null, array('09.04.2014', '10.04.2014'), array());
     // $resourceProps
     // verify derived room type for the given dates
     $resourceIdDateRoomToRoomType = AllocationDBO::getDerivedRoomTypesForDates(DateTime::createFromFormat('!d.m.Y', "09.04.2014"), DateTime::createFromFormat('!d.m.Y', "10.04.2014"));
     $this->assertEquals("X", $resourceIdDateRoomToRoomType[132]["09.04.2014"], "mixed for 09.04.2014");
     $this->assertEquals("FX", $resourceIdDateRoomToRoomType[132]["10.04.2014"], "female/mixed for 10.04.2014");
     $freeBedIds = AllocationDBO::fetchAvailableBeds(132, 1, "X", array('09.04.2014', '10.04.2014'), array(), array());
     // $resourceProps
     $this->assertEquals(6, sizeof($freeBedIds), "Expecting 6 free beds in Snow White room");
     // find the booking that was just created
     $bookingSummaryArr = BookingDBO::getBookingsForDateRange(DateTime::createFromFormat('!d.m.Y', '09.04.2014'), DateTime::createFromFormat('!d.m.Y', '09.04.2014'), 'checkin', null, null, 'FemalesInMixedDorm');
     $this->assertEquals(1, sizeof($bookingSummaryArr), "Expecting 1 booking created");
     // verify booking summary query brings back the saved values
     $bookingSummary = array_shift(array_values($bookingSummaryArr));
     // load existing booking
     $booking = new AddBooking();
     $booking->load($bookingSummary->id);
     $allocationArr = $this->queryByXPath('/editbooking/allocations/allocation[name="NoShowFemales-1"]', $booking->toXml());
     $this->assertEquals(1, sizeof($allocationArr), "expecting 1 allocation with name 'NoShowFemales-1'");
     $allocation = array_shift($allocationArr);
     // toggle until cancelled
     $rowid = (string) $allocation->rowid;
     $booking->toggleBookingStateAt($rowid, "09.04.2014");
     $booking->toggleBookingStateAt($rowid, "09.04.2014");
     $booking->toggleBookingStateAt($rowid, "09.04.2014");
     $state = $booking->toggleBookingStateAt($rowid, "09.04.2014");
     $this->assertEquals("cancelled", $state, "expecting state on 09.04.2014 to be 'cancelled'");
     $booking->toggleBookingStateAt($rowid, "10.04.2014");
     $booking->toggleBookingStateAt($rowid, "10.04.2014");
     $booking->toggleBookingStateAt($rowid, "10.04.2014");
     $state = $booking->toggleBookingStateAt($rowid, "10.04.2014");
     $this->assertEquals("cancelled", $state, "expecting state on 10.04.2014 to be 'cancelled'");
     $booking->save();
     // i should be able to fit 9 more people on the 10/11 of April
     self::createTestBooking("FillUpTheRoom", "WithMaxOccupants", array("M" => 5, "F" => 4, "X" => 0), 132, "10", null, array('10.04.2014', '11.04.2014'), array());
     // $resourceProps
     // verify derived room type for the given dates
     $resourceIdDateRoomToRoomType = AllocationDBO::getDerivedRoomTypesForDates(DateTime::createFromFormat('!d.m.Y', "10.04.2014"), DateTime::createFromFormat('!d.m.Y', "11.04.2014"));
     $this->assertEquals("X", $resourceIdDateRoomToRoomType[132]["10.04.2014"], "mixed for 10.04.2014");
     $this->assertEquals("X", $resourceIdDateRoomToRoomType[132]["11.04.2014"], "mixed for 11.04.2014");
 }
 /**
  * Loads current allocations from the db and resets the min/max dates to default.
  * $bookingId : booking id for this allocation
  */
 function load($bookingId)
 {
     $this->allocationRows = AllocationDBO::fetchAllocationRowsForBookingId($bookingId, $this->resourceMap);
     $this->setDefaultMinMaxDates();
 }
 /**
  * Assigns all allocation rows matching $resourceId with a specific leaf-level resource (bed)
  * based on current availability, requested room type and existing allocations.
  * $resourceId : id of (parent) resource to update
  * $reqRoomType : requested room type (M/F/X/MX/FX)
  * $bookingDates : array() of booking dates in format d.M.y
  * $resourceProps : array of resource property ids (allocate only to resources with these properties)
  * $allocationRows : array() of current AllocationRows
  * $newAllocations : array() of uncommitted allocation rows. resourceIds that are assigned to a parent resource
  *                   will be allocated to a leaf-level resources if possible. Those unable to be allocated
  *                   will have their isAvailable flag set to false.
  */
 function allocateResources($resourceId, $reqRoomType, $bookingDates, $resourceProps, &$allocationRows, &$newAllocations)
 {
     // collect all dates for all allocations sharing this resourceId
     // (we should only need to check one as they *should* all be the same but just in case)
     $numUnallocated = $this->getNumAllocationsMatchingResource($alloc->resourceId, $reqRoomType, $newAllocations);
     error_log("number unallocated: {$numUnallocated}");
     $bookingDates = $this->collectDatesWithResourceId($alloc->resourceId, $reqRoomType, $newAllocations);
     error_log("after collectDatesWithResourceId: " . $numUnallocated . " bookingDates : " . sizeof($bookingDates));
     // check that the user didn't do anything stupid like try to assign two people into the same bed
     if ($numUnallocated > 1 && $alloc->resourceId != null && $this->resourceMap[$alloc->resourceId]->resource_type == 'bed') {
         throw new AllocationException("You cannot assign more than one person into the same bed!");
     }
     // don't include those we've already allocated but not yet committed
     $excludedResourceIds = $this->getLeafResourceIdsWithOverlappingDates($bookingDates, $allocationRows);
     // find all beds that can be assigned for the given requested room type and dates
     $bedResourceIds = AllocationDBO::fetchAvailableBeds($resourceId, $numUnallocated, $reqRoomType, $bookingDates, $excludedResourceIds, $resourceProps);
     $this->allocateBeds($resourceId, $bedResourceIds, $newAllocations);
     // if we didn't have enough availability to allocate based on reqRoomType, try and allocate just based on gender
     /* should have been handled above
             if (sizeof($bedResourceIds) < $numUnallocated) {
     
                 $numUnallocated = $this->getNumAllocationsMatchingResource($resourceId, $reqRoomType, $newAllocations);
                 if ($reqRoomType == 'X') {
                     $bedResourceIds = AllocationDBO::fetchAvailableBeds($resourceId, $numUnallocated, 
                         $this->inferRoomTypes($resourceId, $newAllocations), 
                         $bookingDates, $excludedResourceIds, $this->getResourceIds($allocationRows), $resourceProps);
                 } else if ($reqRoomType == 'M') {
                     $bedResourceIds = AllocationDBO::fetchAvailableBeds($resourceId, $numUnallocated, 
                         array('M', 'MX'), 
                         $bookingDates, $excludedResourceIds, $this->getResourceIds($allocationRows), $resourceProps);
                 } else if ($reqRoomType == 'F') {
                     $bedResourceIds = AllocationDBO::fetchAvailableBeds($resourceId, $numUnallocated, 
                         array('F', 'FX'), 
                         $bookingDates, $excludedResourceIds, $this->getResourceIds($allocationRows), $resourceProps);
                 }
     
                 $this->allocateBeds($resourceId, $bedResourceIds, $newAllocations);
             }
     */
     // no more availability, ... add to "overflow" group
     //        if (sizeof($bedResourceIds) < $numUnallocated) {
     //            $this->allocateBeds($resourceId, $allocationRows, array(ResourceDBO::OVERFLOW_RESOURCE_ID));
     //        }
     /*
     // update availability subtracting those that have been allocated but not committed
     foreach ($existingAllocationRows as $ar) {
         foreach ($rsAvailability as $key => $rowAvail) {
             if ($rowAvail->resource_id == $ar->resourceId) {
                 unset($rsAvailability[$key]);
             }
         }
     }
     
     // we may have a situation where the user has some uncommitted allocations
     // in a new room, or where previous (uncommitted) allocations may have changed
     // a room from MX to M or from FX to F  
     // (male/mixed to mixed with addition of a F or female/mixed to mixed with addition of a M)
     // so adding new allocations may bring the room into an invalid M/F state
     // ignore this for now; this will be validated again at the db level when saving
     
     // count the number of available beds per room
     $bedCounts = array();    // array() of bed resource id[] indexed by room resource ids
     $resourceIds = array();  // all available bed resource ids
     foreach ($rsAvailability as $rowAvail) {
         if (unset($bedCounts[$rowAvail->parent_resource_id])) {
             $bedCounts[$rowAvail->parent_resource_id] = array();
         } 
         $bedCounts[$rowAvail->parent_resource_id][] = $rowAvail->resource_id;
         $resourceIds[] = $rowAvail->resource_id;
     }
     
     // find resource ids where all can fit in the same room
     $roomTypes = AllocationDBO::fetchRoomTypes($resourceId, $bookingDates);
     foreach ($bedCounts as $parentResId => $bedIds) {
         if (sizeof($bedIds) >= $numGuests && 
                 // do not try to stuff a M/F allocation request into a mixed room at this point
                 (false === isset($roomTypes[$parentResId]) || ($roomTypes[$parentResId] != 'MX' && $roomTypes[$parentResId] != 'FX'))) {
     
             // if we have enough free beds in the room, use these resources
             $resourceIds = $bedIds;
             break;
         }
     }
     */
     //$this->inferRoomType($resourceId, $allocationRows)
     //        $this->doAssignAllocations($allocationRows, $resourceId, $resourceIds);
 }