Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 59 additions & 16 deletions lib/Application/Reservation/Validation/AccessoryResourceRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,59 +14,88 @@ class AccessoryResourceRule implements IReservationValidationRule
*/
private $strings;

/**
* Constructor for initializing dependencies
*
* @param IAccessoryRepository $accessoryRepository
*/
public function __construct(IAccessoryRepository $accessoryRepository)
{
$this->accessoryRepository = $accessoryRepository;
$this->strings = Resources::GetInstance();
}

/**
* Validates reservations for accessory-resource rules
*
* @param $reservationSeries
* @param $retryParameters
* @return ReservationRuleResult
*/
public function Validate($reservationSeries, $retryParameters)
{
$errors = [];

/** @var ReservationAccessory[] $bookedAccessories */
// Step 1: Collect booked accessories
$bookedAccessories = [];

foreach ($reservationSeries->Accessories() as $accessory) {
$bookedAccessories[$accessory->AccessoryId] = $accessory;
}

// Step 2: Load all accessories and create associations
$accessories = $this->accessoryRepository->LoadAll();

$association = $this->GetResourcesAndRequiredAccessories($accessories);

$bookedResourceIds = $reservationSeries->AllResourceIds();

// Step 3: Find invalid accessory-resource associations
$badAccessories = $association->GetAccessoriesThatCannotBeBookedWithGivenResources($bookedAccessories, $bookedResourceIds);

foreach ($badAccessories as $accessoryName) {
$errors[] = $this->strings->GetString('AccessoryResourceAssociationErrorMessage', $accessoryName);
}

// Step 4: Ensure all accessories have a QuantityReserved value (even if not booked)
foreach ($accessories as $accessory) {
if (!isset($bookedAccessories[$accessory->GetId()])) {
$bookedAccessories[$accessory->GetId()] = (object) ['QuantityReserved' => 0];
}
}

// Step 5: Validate min and max quantities for resources and accessories
foreach ($reservationSeries->AllResources() as $resource) {
$resourceId = $resource->GetResourceId();
if ($association->ContainsResource($resourceId)) {
/** @var Accessory[] $resourceAccessories */
$resourceAccessories = $association->GetResourceAccessories($resourceId);
foreach ($resourceAccessories as $accessory) {
$accessoryId = $accessory->GetId();

$resource = $accessory->GetResource($resourceId);
if (!empty($resource->MinQuantity) && $bookedAccessories[$accessoryId]->QuantityReserved < $resource->MinQuantity) {
$errors[] = $this->strings->GetString('AccessoryMinQuantityErrorMessage', [$resource->MinQuantity, $accessory->GetName()]);
}

if (!empty($resource->MaxQuantity) && $bookedAccessories[$accessoryId]->QuantityReserved > $resource->MaxQuantity) {
$errors[] = $this->strings->GetString('AccessoryMaxQuantityErrorMessage', [$resource->MaxQuantity, $accessory->GetName()]);
if (isset($bookedAccessories[$accessoryId]) && $bookedAccessories[$accessoryId] !== null) {
$resource = $accessory->GetResource($resourceId);

// Validate minimum quantity
if (!is_null($resource->MinQuantity) && $bookedAccessories[$accessoryId]->QuantityReserved < $resource->MinQuantity) {
$errors[] = $this->strings->GetString('AccessoryMinQuantityErrorMessage', [$resource->MinQuantity, $accessory->GetName()]);
}

// Validate maximum quantity
if (!is_null($resource->MaxQuantity) && $bookedAccessories[$accessoryId]->QuantityReserved > $resource->MaxQuantity) {
$errors[] = $this->strings->GetString('AccessoryMaxQuantityErrorMessage', [$resource->MaxQuantity, $accessory->GetName()]);
}
} else {
// Error for unbooked accessory
$errors[] = $this->strings->GetString('AccessoryNotBookedErrorMessage', $accessory->GetName());
}
}
}
}

// Return validation result
return new ReservationRuleResult(count($errors) == 0, implode("\n", $errors));
}

/**
* Builds relationships between resources and accessories
*
* @param Accessory[] $accessories
* @return ResourceAccessoryAssociation
*/
Expand All @@ -79,7 +108,6 @@ private function GetResourcesAndRequiredAccessories($accessories)
$association->AddRelationship($resource, $accessory);
}
}

return $association;
}
}
Expand All @@ -92,6 +120,8 @@ class ResourceAccessoryAssociation
private $accessories = [];

/**
* Adds a relationship between a resource and an accessory
*
* @param ResourceAccessory $resource
* @param Accessory $accessory
*/
Expand All @@ -101,6 +131,8 @@ public function AddRelationship($resource, $accessory)
}

/**
* Checks if a resource exists in the association
*
* @param int $resourceId
* @return bool
*/
Expand All @@ -110,15 +142,19 @@ public function ContainsResource($resourceId)
}

/**
* Gets accessories associated with a resource
*
* @param int $resourceId
* @return Accessory[]
*/
public function GetResourceAccessories($resourceId)
{
return $this->resources[$resourceId];
return $this->resources[$resourceId] ?? [];
}

/**
* Adds an accessory to the association
*
* @param Accessory $accessory
*/
public function AddAccessory(Accessory $accessory)
Expand All @@ -127,6 +163,8 @@ public function AddAccessory(Accessory $accessory)
}

/**
* Identifies accessories that cannot be booked with the given resources
*
* @param ReservationAccessory[] $bookedAccessories
* @param int[] $bookedResourceIds
* @return string[]
Expand All @@ -135,10 +173,8 @@ public function GetAccessoriesThatCannotBeBookedWithGivenResources($bookedAccess
{
$badAccessories = [];

$bookedAccessoryIds = [];
foreach ($bookedAccessories as $accessory) {
$accessoryId = $accessory->AccessoryId;
$bookedAccessoryIds[] = $accessoryId;

if ($this->AccessoryNeedsARequiredResourceToBeBooked($accessoryId, $bookedResourceIds)) {
$badAccessories[] = $this->accessories[$accessoryId]->GetName();
Expand All @@ -148,6 +184,13 @@ public function GetAccessoriesThatCannotBeBookedWithGivenResources($bookedAccess
return $badAccessories;
}

/**
* Checks if an accessory requires a specific resource to be booked
*
* @param int $accessoryId
* @param int[] $bookedResourceIds
* @return bool
*/
private function AccessoryNeedsARequiredResourceToBeBooked($accessoryId, $bookedResourceIds)
{
$accessory = $this->accessories[$accessoryId];
Expand Down
Loading