FareTable y RoomFares
- Implementación modelo tarifarios y tarifas. - Implementación inicial servicio precios.
This commit is contained in:
@@ -20,11 +20,12 @@ class FareTableCrudController extends AbstractCrudController
|
|||||||
{
|
{
|
||||||
//$catRepo = $this->entityManager->getRepository(SomeEntity::class);
|
//$catRepo = $this->entityManager->getRepository(SomeEntity::class);
|
||||||
|
|
||||||
|
yield TextField::new('comment')->setLabel('Name');
|
||||||
|
|
||||||
yield AssociationField::new('roomCategory');
|
yield AssociationField::new('roomCategory');
|
||||||
|
|
||||||
yield DateField::new('startDate');
|
yield DateField::new('startDate');
|
||||||
yield DateField::new('endDate');
|
yield DateField::new('endDate');
|
||||||
|
|
||||||
yield TextField::new('comment');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
|
|||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
|
||||||
use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
|
use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
|
||||||
|
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
|
||||||
|
|
||||||
class RoomFareCrudController extends AbstractCrudController
|
class RoomFareCrudController extends AbstractCrudController
|
||||||
{
|
{
|
||||||
@@ -27,6 +28,8 @@ class RoomFareCrudController extends AbstractCrudController
|
|||||||
|
|
||||||
yield NumberField::new ('guestNumber');
|
yield NumberField::new ('guestNumber');
|
||||||
yield MoneyField::new ('fareAmount')->setCurrency('EUR');
|
yield MoneyField::new ('fareAmount')->setCurrency('EUR');
|
||||||
|
|
||||||
|
yield TextField::new('dayType')->setMaxLength(2);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\FareTableRepository;
|
||||||
|
use App\Repository\RoomAvailabilityRepository;
|
||||||
|
use App\Repository\RoomCategoryRepository;
|
||||||
|
use App\Service\PricesService;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
class SearchRoomsController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/search/availability', name: 'app_search_availability')]
|
||||||
|
public function getAvailabilityForPeriod(Request $request, RoomAvailabilityRepository $repoAvailability): Response
|
||||||
|
{
|
||||||
|
$payload = $request->toArray();
|
||||||
|
if (\is_null($payload)) {
|
||||||
|
throw new \Exception('Invalid Payload', 400);
|
||||||
|
}
|
||||||
|
$filter = $payload["filter"];
|
||||||
|
|
||||||
|
$startDate = new \DateTime($filter["startDate"]);
|
||||||
|
$endDate = new \DateTime($filter["endDate"]);
|
||||||
|
|
||||||
|
$rs = $repoAvailability->getAvailabilityForPeriod($startDate, $endDate);
|
||||||
|
|
||||||
|
return $this->json($rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/search/availabilityDetails', name: 'app_search_availabilityDetails')]
|
||||||
|
public function getAvailabilityForPeriodDetails(Request $request, RoomAvailabilityRepository $repoAvailability): Response
|
||||||
|
{
|
||||||
|
$payload = $request->toArray();
|
||||||
|
if (\is_null($payload)) {
|
||||||
|
throw new \Exception('Invalid Payload', 400);
|
||||||
|
}
|
||||||
|
$filter = $payload["filter"];
|
||||||
|
|
||||||
|
$startDate = new \DateTime($filter["startDate"]);
|
||||||
|
$endDate = new \DateTime($filter["endDate"]);
|
||||||
|
|
||||||
|
$rs = $repoAvailability->getAvailabilityForPeriodDetails($startDate, $endDate);
|
||||||
|
|
||||||
|
return $this->json($rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/search/fares', name: 'app_search_faresForCategory')]
|
||||||
|
public function getFaresForCategoryOnPeriod(Request $request, RoomCategoryRepository $repoCat): Response
|
||||||
|
{
|
||||||
|
$payload = $request->toArray();
|
||||||
|
if (\is_null($payload)) {
|
||||||
|
throw new \Exception('Invalid Payload', 400);
|
||||||
|
}
|
||||||
|
$filter = $payload["filter"];
|
||||||
|
|
||||||
|
$catId = $filter["roomCategory"];
|
||||||
|
if ($repoCat->find($catId) === null) {
|
||||||
|
throw new \Exception('Invalid Category', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$startDate = new \DateTime($filter["startDate"]);
|
||||||
|
$endDate = new \DateTime($filter["endDate"]);
|
||||||
|
|
||||||
|
$daysDiff = $startDate->diff($endDate)->days;
|
||||||
|
if (!$daysDiff || $startDate > $endDate) {
|
||||||
|
throw new \Exception('Invalid time interval', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json(["prices" => $dailyPrices]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/search/debug', name: 'app_search_faresDebug')]
|
||||||
|
public function faresDebug(Request $request, PricesService $priceService, FareTableRepository $repoFareTable, RoomCategoryRepository $repoCat): Response
|
||||||
|
{
|
||||||
|
$fareTable[] = $repoFareTable->find(1);
|
||||||
|
$fareTable[] = $repoFareTable->find(2);
|
||||||
|
$cat = $repoCat->find(4);
|
||||||
|
|
||||||
|
//$rs = $repoFareTable->getAllowedGuestNumberOnFareTableList($fareTable);
|
||||||
|
$startDate = new \DateTime("2024-04-20");
|
||||||
|
$endDate = new \DateTime("2024-04-21");
|
||||||
|
|
||||||
|
$rs = $priceService->getCategoryPricesForSearch($cat, $startDate, $endDate);
|
||||||
|
|
||||||
|
return $this->json($rs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,10 +23,10 @@ class FareTable
|
|||||||
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||||
private ?\DateTimeInterface $startDate = null;
|
private ?\DateTimeInterface $startDate = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
#[ORM\Column(type : Types::DATETIME_MUTABLE, nullable: true)]
|
||||||
private ?\DateTimeInterface $endDate = null;
|
private ?\DateTimeInterface $endDate = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length : 255, nullable: true)]
|
||||||
private ?string $comment = null;
|
private ?string $comment = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,10 +123,57 @@ class FareTable
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString() : string {
|
public function __toString(): string
|
||||||
if(!\is_null($this->comment)) {
|
{
|
||||||
|
if (!\is_null($this->comment)) {
|
||||||
return $this->comment;
|
return $this->comment;
|
||||||
}
|
}
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max guest number for current roomFares.
|
||||||
|
*/
|
||||||
|
public function getMaxGuestNumber(): int
|
||||||
|
{
|
||||||
|
$maxGuests = $this->getRoomFares()->reduce(function (int $acc, RoomFare $value): int {
|
||||||
|
return \max($acc, $value->getGuestNumber());
|
||||||
|
}, -1);
|
||||||
|
|
||||||
|
return $maxGuests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get min guest number for current roomFares.
|
||||||
|
*/
|
||||||
|
public function getMinGuestNumber(): int
|
||||||
|
{
|
||||||
|
$firstGuestNum = $this->getRoomFares()->first()->getGuestNumber() ?? 999;
|
||||||
|
|
||||||
|
$maxGuests = $this->getRoomFares()->reduce(function (int $acc, RoomFare $value): int {
|
||||||
|
return \min($acc, $value->getGuestNumber());
|
||||||
|
}, $firstGuestNum);
|
||||||
|
|
||||||
|
return $maxGuests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene las tarifas que aplican al GuestNumber indicado.
|
||||||
|
* @param int $guestNumber GuestNumber para búsqueda.
|
||||||
|
* @return RoomFare[] Lista de tarifas.
|
||||||
|
*/
|
||||||
|
public function getRoomFaresByGuestNumber(int $guestNumber)
|
||||||
|
{
|
||||||
|
$roomFareList = $this->getRoomFares()->filter(function (RoomFare $element) use ($guestNumber) {
|
||||||
|
return $element->getGuestNumber() == $guestNumber;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $roomFareList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isActiveOnDate(\DateTimeInterface $date): bool
|
||||||
|
{
|
||||||
|
$active = ($this->startDate <= $date && $this->endDate > $date);
|
||||||
|
return $active;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,28 +21,62 @@ class FareTableRepository extends ServiceEntityRepository
|
|||||||
parent::__construct($registry, FareTable::class);
|
parent::__construct($registry, FareTable::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * @return FareTable[] Returns an array of FareTable objects
|
* Gets all fareTables for time period.
|
||||||
// */
|
* @param integer|string ID Categoria.
|
||||||
// public function findByExampleField($value): array
|
* @param DateTimeInterface Period start date
|
||||||
// {
|
* @param DateTimeInterface Period end date
|
||||||
// return $this->createQueryBuilder('f')
|
* @return array Array with all faretables active in given period.
|
||||||
// ->andWhere('f.exampleField = :val')
|
*/
|
||||||
// ->setParameter('val', $value)
|
public function findFareTable(int $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate): array
|
||||||
// ->orderBy('f.id', 'ASC')
|
{
|
||||||
// ->setMaxResults(10)
|
$strStartDate = $startDate->format('Y-m-d');
|
||||||
// ->getQuery()
|
$strEndDate = $endDate->format('Y-m-d');
|
||||||
// ->getResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?FareTable
|
return $this->createQueryBuilder('f')
|
||||||
// {
|
->andWhere('f.roomCategory = :category')
|
||||||
// return $this->createQueryBuilder('f')
|
->andWhere('f.startDate < :endDate')
|
||||||
// ->andWhere('f.exampleField = :val')
|
->andWhere('f.endDate >= :startDate')
|
||||||
// ->setParameter('val', $value)
|
->setParameter('category', $roomCategory)
|
||||||
// ->getQuery()
|
->setParameter('startDate', $strStartDate)
|
||||||
// ->getOneOrNullResult()
|
->setParameter('endDate', $strEndDate)
|
||||||
// ;
|
->getQuery()
|
||||||
// }
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene una lista con los números de huéspedes permitidos por todos los tarifarios pasados.
|
||||||
|
* @param FareTable[] $fareTableList Lista con los FareTable a consultar.
|
||||||
|
* @return int[] Lista con los números de huéspedes permitidos.
|
||||||
|
*/
|
||||||
|
public function getAllowedGuestNumberOnFareTableList(array $fareTableList): array
|
||||||
|
{
|
||||||
|
$fareTableCount = count($fareTableList);
|
||||||
|
|
||||||
|
$fareTableIdList = \array_map(function (FareTable $element) {
|
||||||
|
return $element->getId();
|
||||||
|
}, $fareTableList);
|
||||||
|
$strIdList = \implode(",", $fareTableIdList);
|
||||||
|
|
||||||
|
//Subquery -> Obtiene los números de huéspedes de las tarifas de un tarifario.
|
||||||
|
//Query -> Cuenta en cuantos tarifarios aparece cada número de huéspedes y queda con aquellos que aparecen en todos (having).
|
||||||
|
$sql = "SELECT guest_number
|
||||||
|
FROM (SELECT DISTINCT fare_table_id, guest_number FROM room_fare WHERE fare_table_id IN ($strIdList))
|
||||||
|
GROUP BY guest_number
|
||||||
|
HAVING COUNT(fare_table_id) = $fareTableCount";
|
||||||
|
|
||||||
|
$conn = $this->getEntityManager()->getConnection();
|
||||||
|
$rs = $conn->executeQuery($sql);
|
||||||
|
|
||||||
|
$allowedGuestNumbers = [];
|
||||||
|
while (true) {
|
||||||
|
$value = $rs->fetchOne();
|
||||||
|
if (!$value) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$allowedGuestNumbers[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $allowedGuestNumbers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,13 +34,41 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
|
|||||||
->getResult();
|
->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?RoomAvailability
|
|
||||||
// {
|
/**
|
||||||
// return $this->createQueryBuilder('r')
|
* @param \DateTimeInterface Period start (included in range).
|
||||||
// ->andWhere('r.exampleField = :val')
|
* @param \DateTimeInterface Period end (not included in range).
|
||||||
// ->setParameter('val', $value)
|
* @return RoomAvailability[] Returns an array of RoomAvailability objects
|
||||||
// ->getQuery()
|
*/
|
||||||
// ->getOneOrNullResult()
|
public function getAvailabilityForPeriod(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array
|
||||||
// ;
|
{
|
||||||
// }
|
$strStartDate = $startDate->format('Y-m-d');
|
||||||
|
$strEndDate = $endDate->format('Y-m-d');
|
||||||
|
|
||||||
|
return $this->createQueryBuilder('ra')
|
||||||
|
->select('IDENTITY(ra.roomCategory) as roomCategory, MIN(ra.numAvailable) as availability')
|
||||||
|
->andWhere('ra.day >= :startDate')
|
||||||
|
->andWhere('ra.day < :endDate')
|
||||||
|
->groupBy('ra.roomCategory')
|
||||||
|
->setParameter('startDate', $strStartDate)
|
||||||
|
->setParameter('endDate', $strEndDate)
|
||||||
|
->getQuery()
|
||||||
|
->getArrayResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvailabilityForPeriodDetails(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array
|
||||||
|
{
|
||||||
|
$strStartDate = $startDate->format('Y-m-d');
|
||||||
|
$strEndDate = $endDate->format('Y-m-d');
|
||||||
|
|
||||||
|
return $this->createQueryBuilder('ra')
|
||||||
|
->andWhere('ra.day >= :startDate')
|
||||||
|
->andWhere('ra.day < :endDate')
|
||||||
|
->setParameter('startDate', $strStartDate)
|
||||||
|
->setParameter('endDate', $strEndDate)
|
||||||
|
->getQuery()
|
||||||
|
->getArrayResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\FareTable;
|
||||||
|
use App\Entity\RoomCategory;
|
||||||
|
use App\Repository\FareTableRepository;
|
||||||
|
use App\Repository\RoomFareRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 FareTableRepository $repoFareTable,
|
||||||
|
private RoomFareRepository $repoRoomFares
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 getCategoryPricesForSearch(RoomCategory $category, \DateTimeInterface $startDate, \DateTimeInterface $endDate): array
|
||||||
|
{
|
||||||
|
$daysDiff = $startDate->diff($endDate)->days;
|
||||||
|
if (!$daysDiff) {
|
||||||
|
return "Invalid date period :( ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$fareTablesForPeriod = $this->repoFareTable->findFareTable($category->getId(), $startDate, $endDate);
|
||||||
|
|
||||||
|
//Obtenemos el número de personas permitido por todos los tarifarios activos.
|
||||||
|
$allowedGuestNumbers = $this->repoFareTable->getAllowedGuestNumberOnFareTableList($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 = [];
|
||||||
|
$currentDate = $startDate; //TODOME: Comprobar si es tipo referencia y está cambiando.
|
||||||
|
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($fareTablesForPeriod, function (FareTable $element) use ($currentDate) {
|
||||||
|
return $element->isActiveOnDate($currentDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count($currentFareTableList) != 1) {
|
||||||
|
//ERROR
|
||||||
|
$dailyPrices[$strCurrentDate] = "ERROR - Number of FareTable found: " . count($currentFareTableList);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentFareTable = $currentFareTableList[0];
|
||||||
|
|
||||||
|
//TODOME: -> Obtener precio específico (RoomPrices).
|
||||||
|
|
||||||
|
//Si no hay precio, calcular mediante tarifarios.
|
||||||
|
$roomFares = $currentFareTable->getRoomFaresByGuestNumber($currentGuestNumber);
|
||||||
|
if (count($roomFares) < 1) {
|
||||||
|
//ERROR
|
||||||
|
$dailyPrices[$strCurrentDate] = "ERROR - Invalid RoomFares found";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODOME: -> Handle conditions...
|
||||||
|
$fareAmount = $roomFares->first()->getFareAmount();
|
||||||
|
$dailyPrices[$strCurrentDate] = $fareAmount;
|
||||||
|
|
||||||
|
//Add 1 day to current date.
|
||||||
|
$currentDate->add(new \DateInterval("P1D"));
|
||||||
|
}
|
||||||
|
|
||||||
|
$rs[$currentGuestNumber] = $dailyPrices;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Aplicamos la estrategia de precios configurada para calcular el precio final de la estancia.
|
||||||
|
// TODOME: ...
|
||||||
|
|
||||||
|
return $rs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user