configFareMethod = $configVarService->getVariableFromConst(ConfigVariableConsts::CONFIG_PRICES_FAREMETHOD)->getValue(); $this->configPriceMethod = $configVarService->getVariableFromConst(ConfigVariableConsts::CONFIG_PRICES_PRICEMETHOD)->getValue(); } /** * Calcula los precios de la estancia para la categoría y periodo indicados. * Devolverá para cada numero de personas, el precio final de la estancia. * * e. g. --> [ 2 => 120€, 3 => 180€ ] * * @return array Contiene los precios en función del número de personas. */ public function getFinalPricesForSearch(RoomCategory $category, \DateTimeInterface $startDate, \DateTimeInterface $endDate): array { $daysDiff = $startDate->diff($endDate)->days; if (!$daysDiff) { $this->logger->warning("PricesService.getFinalPricesForSearch - Invalid search period"); return []; } $fareTablesForPeriod = $this->repoFareTable->findFareTable($category->getId(), $startDate, $endDate); $roomPriceList = $this->repoRoomPrices->getListForPeriod($startDate, $endDate, $category->getId()); //"Organize" RoomPrices. $filteredRoomPriceList = []; foreach ($roomPriceList as $roomPrice) { $priceDate = $roomPrice->getDateString(); $filteredRoomPriceList[$roomPrice->getGuestNumber()][$priceDate] = $roomPrice->getPrice(); } //Obtenemos el número de personas permitido por todos los tarifarios activos. $allowedGuestNumberList = $this->getAllowedGuestNumberFromFaretableList($fareTablesForPeriod); //Calculate stay price for each allowed GuestNumber. $priceItems = []; foreach ($allowedGuestNumberList as $currentGuestNumber) { //Get RoomPrices for current GuestNumber. $roomPricesForGuestNum = []; if (array_key_exists($currentGuestNumber, $filteredRoomPriceList)) { $roomPricesForGuestNum = $filteredRoomPriceList[$currentGuestNumber]; } //Calculate the price for each day. $dailyPrices = $this->getDailyPricesForSearch($startDate, $endDate, $currentGuestNumber, $fareTablesForPeriod, $roomPricesForGuestNum); $stayPrice = $this->getTotalPriceForStay($dailyPrices); $item = new SearchPricesResponsePriceItem(); $item->guestNumber = $currentGuestNumber; $item->price = $stayPrice; $priceItems[] = $item; } return $priceItems; } /** * Calculate stay total price on each roomCategory for the given time period * Returns a list with all roomCategories, containing the total price by guestnumber. * * @return SearchPricesResponse[] Total prices for each guestnumber of each roomCategory. */ public function getRoomSearch(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array { $daysDiff = $startDate->diff($endDate)->days; if (!$daysDiff) { $this->logger->warning("PricesService.getFinalPricesForSearch - Invalid search period"); return []; } $rs = []; $roomCategoryList = $this->repoRoomCategory->findAll(); $fareTableList = $this->repoFareTable->getFareTableListForPeriod($startDate, $endDate); $faretablesByRoomCategory = []; foreach ($fareTableList as $faretable) { $faretablesByRoomCategory[$faretable->getRoomCategory()->getId()][] = $faretable; } //Get and filter prices. $priceList = $this->repoRoomPrices->getListForPeriod($startDate, $endDate); $filteredPrices = []; foreach ($priceList as $price) { $priceDate = $price->getDateString(); //Store price in multi-level array by: RoomCategoryId, GuestNumber and Date. $filteredPrices[$price->getRoomCategory()->getId()][$price->getGuestNumber()][$priceDate] = $price->getPrice(); } foreach ($roomCategoryList as $roomCat) { $currentRoomCategoryId = $roomCat->getId(); //Get Faretables for RoomCategory. $roomFaretables = []; if (array_key_exists($currentRoomCategoryId, $faretablesByRoomCategory)) { $roomFaretables = $faretablesByRoomCategory[$currentRoomCategoryId]; } //Get RoomPrices for current RoomCategory. $roomPricesForCategory = []; if (array_key_exists($currentRoomCategoryId, $filteredPrices)) { $roomPricesForCategory = $filteredPrices[$currentRoomCategoryId]; } //Calculate distinct GuestNumber for RoomCategory by it's faretables. $allowedGuestNumberList = $this->getAllowedGuestNumberFromFaretableList($roomFaretables); //Calculate stay price for each allowed GuestNumber. $priceItems = []; foreach ($allowedGuestNumberList as $currentGuestNumber) { //Get RoomPrices for current GuestNumber. $roomPricesForGuestNum = []; if (array_key_exists($currentGuestNumber, $roomPricesForCategory)) { $roomPricesForGuestNum = $roomPricesForCategory[$currentGuestNumber]; } //Calculate the price for each day. $dailyPrices = $this->getDailyPricesForSearch($startDate, $endDate, $currentGuestNumber, $roomFaretables, $roomPricesForGuestNum); $stayPrice = $this->getTotalPriceForStay($dailyPrices); $item = new SearchPricesResponsePriceItem(); $item->guestNumber = $currentGuestNumber; $item->price = $stayPrice; $priceItems[] = $item; } $roomItem = new SearchPricesResponse(); $roomItem->roomCategoryId = $roomCat->getId(); $roomItem->prices = $priceItems; $rs[] = $roomItem; } return $rs; } /** * Dado un número de huéspedes, calcula el precio de cada día del periodo indicado. * @param \DateTimeInterface $startDate Fecha de inicio del periodo. * @param \DateTimeInterface $endDate Fecha fin del periodo. * @param int $guestNumber Número de huéspedes para los que calcular los precios. * @param array $fareTableList Array con los tarifarios que aplican en el periodo indicado. * @return array Devuelve un array con los precios: e.g. --> [ "2024-04-30" => 2500, "2024-05-01" => 3500, "2024-05-02" => 3000, ...]. */ private function getDailyPricesForSearch(\DateTimeInterface $startDate, \DateTimeInterface $endDate, int $guestNumber, array $fareTableList, array $roomPricesByGuestNum): array { $daysDiff = $startDate->diff($endDate)->days; $dailyPrices = []; $currentDate = new \DateTime($startDate->format("Y-m-d")); //Copy start date to operate. for ($i = 0; $i < $daysDiff; $i++) { $strCurrentDate = $currentDate->format("Y-m-d"); //Get Faretable for current day. //TODO: Possible optimization - This data doesn't need to be recalculated so often. $currentFareTableList = \array_filter($fareTableList, function (FareTable $element) use ($currentDate) { return $element->isActiveOnDate($currentDate); }); if (count($currentFareTableList) != 1) { $this->logger->error( "PricesService.getDailyPricesForSearch: Can't calculate prices: Invalid number of FareTable found: " . count($currentFareTableList), [ "startDate" => $startDate, "endDate" => $endDate, "fareTableList" => $fareTableList, "currentFareTableList" => $currentFareTableList ] ); return []; } //Check for Prices. $roomPrice = null; if (array_key_exists($strCurrentDate, $roomPricesByGuestNum)) { $roomPrice = $roomPricesByGuestNum[$strCurrentDate]; } //Si no hay precio, calcular mediante tarifarios. $fareAmount = null; if ($roomPrice == null) { //Get Faretables for guestNumber. $currentFareTable = array_values($currentFareTableList)[0]; $roomFares = $currentFareTable->getRoomFaresByGuestNumber($guestNumber); if (count($roomFares) < 1) { $this->logger->error( "PricesService.getDailyPricesForSearch: Can't calculate prices: Found faretable without fares", [ "currentFareTable" => $currentFareTable, "guestNumber" => $guestNumber, "roomFaresForGuestNumber" => $roomFares ] ); return []; } //Calculate RoomFare. $fareAmount = $this->calculateFareApplyingConditions($roomFares, $currentDate); } //If any price can't be calculated, NO price should be returned. if ($roomPrice == null && $fareAmount == null) { return []; } $dailyPrices[$strCurrentDate] = $roomPrice ?? $fareAmount; //Add 1 day to current date. $currentDate->add(new \DateInterval("P1D")); } return $dailyPrices; } /** * Calcula el precio a aplicar según las condiciones de las tarifas. * @param Collection $roomFareList Lista con las posibles tarifas. * @param DateTimeInterface $fareCondDate Fecha utilizada para las condiciones. * */ private function calculateFareApplyingConditions(Collection $roomFareList, \DateTimeInterface $fareCondDate): int|null { if (count($roomFareList) == 1) { return $roomFareList->first()->getFareAmount(); } $currentDayOfWeek = $fareCondDate->format("w"); //Filter applicable fares. //TODO: Possible optimization - This data doesn't need to be recalculated so often; just once for each FareTable. /** @var RoomFare[] */ $applFaresByDay = []; /** @var RoomFare[] */ $applFaresNonCondition = []; foreach ($roomFareList as $fare) { $dayType = $fare->getDayType(); if ($dayType == null || $dayType == "") { $applFaresNonCondition[] = $fare; continue; } if ($dayType == $currentDayOfWeek) { $applFaresByDay[] = $fare; } } //Return final fare value. //Use fares for current weekday. $finalFare = $this->getPriceFromRoomFareList($applFaresByDay); if ($finalFare != null) { return $finalFare; } //Use non-conditional fares. $finalFare = $this->getPriceFromRoomFareList($applFaresNonCondition); if ($finalFare != null) { return $finalFare; } //No valid fare found! $this->logger->error( "PricesService.calculateFareApplyingConditions: No RoomFare found. Price can't be calculated.", ["RoomFareList" => $roomFareList, "Date" => $fareCondDate] ); return null; } /** * Get the fare with bigger priority from a list of fares. * @param RoomFare[] $roomFareList List of RoomFares. */ private function getPriceFromRoomFareList(array $roomFareList): int|null { $countFares = count($roomFareList); if ($roomFareList == null || $countFares == 0) { return null; } if ($countFares == 1) { return $roomFareList[0]->getFareAmount(); } switch ($this->configFareMethod) { case 'HIGHER': return PriceSelectionMethods::selectFare_Highest($roomFareList); case 'LOWER': return PriceSelectionMethods::selectFare_Lowest($roomFareList); case 'AVERAGE': return PriceSelectionMethods::selectFare_Average($roomFareList); default: return null; } } /** * Gets a list of allowed GuestNumbers from a list of FareTables. * @param FareTable[] $faretableList List of Faretables * @return int[] Array of allowed guestNumbers from faretable list. */ private function getAllowedGuestNumberFromFaretableList(array $faretableList): array { if (count($faretableList) == 0) { return []; } $guestSet = new Set(); foreach ($faretableList as $table) { $fares = $table->getRoomFares(); foreach ($fares as $roomFare) { $guestSet->add($roomFare->getGuestNumber()); } } return $guestSet->toArray(); } /** * Calculate total stay price given a list of prices. * @param array $dailyPrices Array containing the price for each day. * @return int Total stay price. */ private function getTotalPriceForStay(array $dailyPrices): int|null { switch ($this->configPriceMethod) { case 'SUM': return PriceSelectionMethods::priceMethod_Sum($dailyPrices); case 'AVERAGE': return PriceSelectionMethods::priceMethod_Average($dailyPrices); case 'HIGHER': return PriceSelectionMethods::priceMethod_Highest($dailyPrices); case 'LOWER': return PriceSelectionMethods::priceMethod_Lowest($dailyPrices); default: return null; } } }