forked from Somnia/SomniaRooms
- Added endpoint to get prices for all RoomCategories. - Improvements to Prices API.
286 lines
11 KiB
PHP
286 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Service;
|
|
|
|
use App\Entity\FareTable;
|
|
use App\Entity\RoomCategory;
|
|
use App\Repository\FareTableRepository;
|
|
use App\Repository\RoomCategoryRepository;
|
|
use App\Repository\RoomFareRepository;
|
|
use App\Response\SearchPricesResponse;
|
|
use App\Response\SearchPricesResponsePriceItem;
|
|
use Doctrine\Common\Collections\Collection;
|
|
use Ds\Set;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* Servicio para calcular los precios de las estancias.
|
|
* En un futuro este servicio deberá admitir múltiples estrategias de cálculo de precios finales.
|
|
*/
|
|
class PricesService
|
|
{
|
|
|
|
public function __construct(
|
|
private RoomCategoryRepository $repoRoomCategory,
|
|
private FareTableRepository $repoFareTable,
|
|
private RoomFareRepository $repoRoomFares,
|
|
private LoggerInterface $logger
|
|
) {}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
//Obtenemos el número de personas permitido por todos los tarifarios activos.
|
|
$allowedGuestNumbers = $this->getAllowedGuestNumberFromFaretableList($fareTablesForPeriod);
|
|
|
|
$rs = [];
|
|
|
|
//Calculamos los precios para cada GuestNumber permitido.
|
|
foreach ($allowedGuestNumbers as $currentGuestNumber) {
|
|
|
|
//Calculamos el precio de cada día.
|
|
// e.g. --> [ "2024-04-30" => 20, "2024-05-01" => 28, "2024-05-02" => 20, ...].
|
|
$dailyPrices = $this->getDailyPricesForSearch($startDate, $endDate, $currentGuestNumber, $fareTablesForPeriod);
|
|
|
|
$rs[$currentGuestNumber] = $dailyPrices;
|
|
}
|
|
|
|
//Aplicamos la estrategia de precios configurada para calcular el precio final de la estancia.
|
|
// TODOME: Crear y configurar diversas estrategias de precios -> Por el momento utilizamos "Suma del precio de cada día".
|
|
|
|
$finalResult = [];
|
|
foreach ($rs as $guestNum => $priceArray) {
|
|
$finalAmount = \array_reduce($priceArray, function ($acc, $element) {
|
|
return $acc + $element;
|
|
});
|
|
|
|
$finalResult[$guestNum] = $finalAmount;
|
|
}
|
|
|
|
return $finalResult;
|
|
}
|
|
|
|
/**
|
|
* Calculate stay total price on each roomCategory for the given time period
|
|
* Returns a list with all roomCategories, containing the total price by guestnumber.
|
|
*
|
|
* e. g. --> [ 2 => 120€, 3 => 180€ ]
|
|
*
|
|
* @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;
|
|
}
|
|
|
|
foreach ($roomCategoryList as $roomCat) {
|
|
|
|
//Get Faretables for RoomCategory.
|
|
$roomFaretables = [];
|
|
if (array_key_exists($roomCat->getId(), $faretablesByRoomCategory)) {
|
|
$roomFaretables = $faretablesByRoomCategory[$roomCat->getId()];
|
|
}
|
|
|
|
//Calculate distinct GuestNumber for RoomCategory by it's faretables.
|
|
$allowedGuestNumberList = $this->getAllowedGuestNumberFromFaretableList($roomFaretables);
|
|
|
|
//Calculate stay price for each allowed GuestNumber.
|
|
$roomPricesByGuestNum = [];
|
|
foreach ($allowedGuestNumberList as $currentGuestNumber) {
|
|
|
|
//Calculate the price for each day.
|
|
$dailyPrices = $this->getDailyPricesForSearch($startDate, $endDate, $currentGuestNumber, $roomFaretables);
|
|
|
|
$stayPrice = $this->getTotalPriceForStay($dailyPrices);
|
|
|
|
$roomPricesByGuestNum[$currentGuestNumber] = $stayPrice;
|
|
}
|
|
|
|
$roomPrices = [];
|
|
foreach ($roomPricesByGuestNum as $guestNum => $price) {
|
|
$item = new SearchPricesResponsePriceItem();
|
|
$item->guestNumber = $guestNum;
|
|
$item->price = $price;
|
|
|
|
$roomPrices[] = $item;
|
|
}
|
|
|
|
$roomItem = new SearchPricesResponse();
|
|
$roomItem->roomCategoryId = $roomCat->getId();
|
|
$roomItem->prices = $roomPrices;
|
|
|
|
$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
|
|
{
|
|
$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");
|
|
|
|
//Obtenemos el tarifario que aplica --> TODOME: Buscar si se puede cachear esto, para no recarlcularlo para cada GuestNumber.
|
|
$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 [];
|
|
}
|
|
|
|
$currentFareTable = array_values($currentFareTableList)[0];
|
|
|
|
//TODOME: -> Obtener precio específico (RoomPrices).
|
|
|
|
//Si no hay precio, calcular mediante tarifarios.
|
|
$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 [];
|
|
}
|
|
|
|
//Si sólo hay 1 tarifa, aplico esa, sino calculo la apropiada según condiciones.
|
|
$fareAmount = null;
|
|
if (count($roomFares) == 1) {
|
|
$fareAmount = $roomFares->first()->getFareAmount();
|
|
} else {
|
|
$fareAmount = $this->calculateFareApplyingConditions($roomFares, $currentDate);
|
|
}
|
|
|
|
$dailyPrices[$strCurrentDate] = $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<RoomFare> $roomFareList Lista con las posibles tarifas.
|
|
* @param DateTimeInterface $fareCondDate Fecha utilizada para las condiciones.
|
|
*
|
|
*/
|
|
private function calculateFareApplyingConditions(Collection $roomFareList, \DateTimeInterface $fareCondDate): int
|
|
{
|
|
$currentDayOfWeek = $fareCondDate->format("w");
|
|
|
|
if (count($roomFareList) == 1) {
|
|
return $roomFareList->first()->getFareAmount();
|
|
}
|
|
|
|
//Comprobamos si a alguna de las tarifas se la aplica la condición de "DayType" y coincide con el actual.
|
|
// Si es así, devolvemos su valor.
|
|
foreach ($roomFareList as $roomFare) {
|
|
$dayType = $roomFare->getDayType();
|
|
if ($dayType == null || $dayType == "") {
|
|
continue;
|
|
}
|
|
if ($dayType == $currentDayOfWeek) {
|
|
return $roomFare->getFareAmount();
|
|
}
|
|
}
|
|
|
|
//TODOME: Si existen múltiples tarifas posibles, debería haber una estrategia para elegir una u otra. Variable de configuración para esto.
|
|
$faresWithoutConditions = $roomFareList->filter(function ($element) {
|
|
return $element->getDayType() == null;
|
|
});
|
|
|
|
return $faresWithoutConditions->first()->getFareAmount();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
// TODOME: Crear y configurar diversas estrategias de precios -> Por el momento utilizamos "Suma del precio de cada día".
|
|
|
|
$finalAmount = \array_reduce($dailyPrices, function ($acc, $element) {
|
|
return $acc + $element;
|
|
});
|
|
return $finalAmount;
|
|
}
|
|
}
|