diff --git a/lib/Application/Reservation/Validation/AccessoryResourceRule.php b/lib/Application/Reservation/Validation/AccessoryResourceRule.php index cc358b2cc..46cd860c4 100644 --- a/lib/Application/Reservation/Validation/AccessoryResourceRule.php +++ b/lib/Application/Reservation/Validation/AccessoryResourceRule.php @@ -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 */ @@ -79,7 +108,6 @@ private function GetResourcesAndRequiredAccessories($accessories) $association->AddRelationship($resource, $accessory); } } - return $association; } } @@ -92,6 +120,8 @@ class ResourceAccessoryAssociation private $accessories = []; /** + * Adds a relationship between a resource and an accessory + * * @param ResourceAccessory $resource * @param Accessory $accessory */ @@ -101,6 +131,8 @@ public function AddRelationship($resource, $accessory) } /** + * Checks if a resource exists in the association + * * @param int $resourceId * @return bool */ @@ -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) @@ -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[] @@ -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(); @@ -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];