Compare commits
15
Commits
7673521708
...
fe2bc7b78c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe2bc7b78c | ||
|
|
850e8afaa1 | ||
|
|
bb688a724a | ||
|
|
74cf4621f4 | ||
|
|
bce27d5fa4 | ||
|
|
2960bf29d8 | ||
|
|
e4a68d2aa0 | ||
|
|
65605d6c5f | ||
|
|
da71f813cb | ||
|
|
ca13863c41 | ||
|
|
734a4a8fee | ||
|
|
ed6b8335c8 | ||
|
|
c9e1f95c81 | ||
|
|
f703727ccb | ||
|
|
77f8e9ec31 |
@@ -21,6 +21,9 @@ indent_size = 2
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
[{compose.yaml,compose.*.yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
|
||||
@@ -16,19 +16,25 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=VerySecretKey
|
||||
APP_SECRET=
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||
#
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
|
||||
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://app:[email protected]:54195/app?serverVersion=16&charset=utf8"
|
||||
DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
||||
###> symfony/routing ###
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
DEFAULT_URI=http://localhost
|
||||
###< symfony/routing ###
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_SECRET=344d36e897253340ff9de6cfacce2da9
|
||||
###< symfony/framework-bundle ###
|
||||
@@ -12,6 +12,9 @@
|
||||
# Draw.io backup file.
|
||||
*bkp
|
||||
|
||||
# Bolt db files
|
||||
[Bb]olt.db
|
||||
|
||||
# JS files
|
||||
/node_modules/
|
||||
|
||||
|
||||
@@ -35,5 +35,5 @@ services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
ports:
|
||||
- 54195:5432
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ services:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER:-app}"]
|
||||
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
|
||||
+5
-5
@@ -13,12 +13,12 @@
|
||||
"doctrine/doctrine-bundle": "^2.14",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||
"doctrine/orm": "^3.3",
|
||||
"easycorp/easyadmin-bundle": "^4.10",
|
||||
"nelmio/api-doc-bundle": "^4.30",
|
||||
"easycorp/easyadmin-bundle": "^4.25",
|
||||
"nelmio/api-doc-bundle": "^5.0",
|
||||
"nelmio/cors-bundle": "^2.4",
|
||||
"php-ds/php-ds": "^1.5",
|
||||
"phpdocumentor/reflection-docblock": "^5.4",
|
||||
"phpstan/phpdoc-parser": "^1.28",
|
||||
"phpstan/phpdoc-parser": "^2.0",
|
||||
"runtime/frankenphp-symfony": "^0.2.0",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/asset-mapper": "7.3.*",
|
||||
@@ -41,8 +41,8 @@
|
||||
"symfony/validator": "7.3.*",
|
||||
"symfony/var-exporter": "7.3.*",
|
||||
"symfony/yaml": "7.3.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
"twig/extra-bundle": "^3.0",
|
||||
"twig/twig": "^3.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
|
||||
Generated
+711
-514
File diff suppressed because it is too large
Load Diff
@@ -3,3 +3,9 @@ framework:
|
||||
# The paths to make available to the asset mapper.
|
||||
paths:
|
||||
- assets/
|
||||
missing_import_mode: strict
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
asset_mapper:
|
||||
missing_import_mode: warn
|
||||
|
||||
@@ -14,6 +14,8 @@ doctrine:
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
identity_generation_preferences:
|
||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
@@ -23,7 +25,7 @@ doctrine:
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
controller_resolver:
|
||||
auto_mapping: true
|
||||
auto_mapping: false
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
#csrf_protection: true
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
nelmio_api_doc:
|
||||
type_info: true
|
||||
documentation:
|
||||
info:
|
||||
title: SomniaRooms
|
||||
|
||||
@@ -2,7 +2,7 @@ framework:
|
||||
router:
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
#default_uri: http://localhost
|
||||
default_uri: '%env(DEFAULT_URI)%'
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
|
||||
@@ -2,6 +2,4 @@ framework:
|
||||
default_locale: en
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
- en
|
||||
providers:
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
only_exceptions: false
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
||||
profiler:
|
||||
collect: false
|
||||
collect_serializer_data: true
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20251017201308 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE room_price (date VARCHAR(10) NOT NULL, guest_number SMALLINT NOT NULL, price INT NOT NULL, room_category_id INT NOT NULL, PRIMARY KEY(room_category_id, date, guest_number))
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_8074837267333DD ON room_price (room_category_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE room_price ADD CONSTRAINT FK_8074837267333DD FOREIGN KEY (room_category_id) REFERENCES room_category (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE room_price DROP CONSTRAINT FK_8074837267333DD
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE room_price
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class UserCrudController extends AbstractCrudController
|
||||
->allowMultipleChoices(true);
|
||||
|
||||
|
||||
yield FormField::addPanel('Set password')->setIcon('fa fa-key');
|
||||
yield FormField::addFieldset('Set password')->setIcon('fa fa-key');
|
||||
|
||||
$fieldPassword = Field::new('plainPassword', 'New password')
|
||||
->hideOnIndex()
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\Request\BookingDetailsRequest;
|
||||
use App\Response\BookingCreateResponse;
|
||||
use App\Service\PricesService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
@@ -9,7 +9,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use App\Repository\ConfigVariableRepository;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
class ConfigVariableController extends AbstractController
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Repository\RoomCategoryRepository;
|
||||
use App\Entity\RoomCategory;
|
||||
use App\Entity\RoomCategoryPhoto;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use OpenApi\Annotations\Property;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class RoomCategoryController extends AbstractController
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Controller;
|
||||
|
||||
use App\ApiErrorException;
|
||||
use App\Consts\ConfigVariableConsts;
|
||||
use App\DTO\ApiErrorMessage;
|
||||
use App\Repository\RoomAvailabilityRepository;
|
||||
use App\Repository\RoomCategoryRepository;
|
||||
@@ -11,16 +12,28 @@ use App\Response\SearchAvailabilityResponse;
|
||||
use App\Request\SearchPricesForCategoryRequest;
|
||||
use App\Response\SearchPricesResponse;
|
||||
use App\Response\SearchPricesResponsePriceItem;
|
||||
use App\Service\ConfigVariableService;
|
||||
use App\Service\PricesService;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use DateTimeInterface;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||
|
||||
class SearchRoomsController extends AbstractController
|
||||
{
|
||||
private string $configMaxAllowedNightAmount;
|
||||
|
||||
public function __construct(
|
||||
private ConfigVariableService $configVarService,
|
||||
private LoggerInterface $logger
|
||||
) {
|
||||
$this->configMaxAllowedNightAmount = $configVarService->getVariableFromConst(ConfigVariableConsts::CONFIG_API_NAXNIGHTS)->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get availability for all rooms on period.
|
||||
* For each room, the minimal availability will be returned.
|
||||
@@ -52,12 +65,11 @@ class SearchRoomsController extends AbstractController
|
||||
#[MapRequestPayload()] SearchPeriodRequest $searchRequest
|
||||
): JsonResponse {
|
||||
|
||||
if ($searchRequest->startDate >= $searchRequest->endDate) {
|
||||
throw new ApiErrorException(
|
||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_INVALID_DATE),
|
||||
'startDate cant be before endDate',
|
||||
);
|
||||
$datesValid = $this->areRequestDatesValid($searchRequest->startDate, $searchRequest->endDate);
|
||||
if ($datesValid != null) {
|
||||
throw $datesValid;
|
||||
}
|
||||
|
||||
$rs = $repoAvailability->getAvailabilityForPeriod($searchRequest->startDate, $searchRequest->endDate);
|
||||
|
||||
return $this->json(["results" => $rs]);
|
||||
@@ -93,11 +105,10 @@ class SearchRoomsController extends AbstractController
|
||||
PricesService $priceService,
|
||||
#[MapRequestPayload()] SearchPeriodRequest $searchRequest
|
||||
): JsonResponse {
|
||||
if ($searchRequest->startDate >= $searchRequest->endDate) {
|
||||
throw new ApiErrorException(
|
||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_INVALID_DATE),
|
||||
'startDate cant be before endDate',
|
||||
);
|
||||
|
||||
$datesValid = $this->areRequestDatesValid($searchRequest->startDate, $searchRequest->endDate);
|
||||
if ($datesValid != null) {
|
||||
throw $datesValid;
|
||||
}
|
||||
|
||||
$rs = $priceService->getRoomSearch($searchRequest->startDate, $searchRequest->endDate);
|
||||
@@ -138,6 +149,11 @@ class SearchRoomsController extends AbstractController
|
||||
#[MapRequestPayload()] SearchPricesForCategoryRequest $searchRequest
|
||||
): JsonResponse {
|
||||
|
||||
$datesValid = $this->areRequestDatesValid($searchRequest->startDate, $searchRequest->endDate);
|
||||
if ($datesValid != null) {
|
||||
throw $datesValid;
|
||||
}
|
||||
|
||||
$roomCategory = $repoCat->find($searchRequest->roomCategory);
|
||||
if ($roomCategory === null) {
|
||||
throw new ApiErrorException(
|
||||
@@ -146,19 +162,29 @@ class SearchRoomsController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
if ($searchRequest->startDate >= $searchRequest->endDate) {
|
||||
throw new ApiErrorException(
|
||||
$rs = $priceService->getFinalPricesForSearch($roomCategory, $searchRequest->startDate, $searchRequest->endDate);
|
||||
|
||||
return $this->json(["results" => $rs]);
|
||||
}
|
||||
|
||||
|
||||
private function areRequestDatesValid(DateTimeInterface $dateStart, DateTimeInterface $dateEnd): ApiErrorException|null
|
||||
{
|
||||
if ($dateStart >= $dateEnd) {
|
||||
return new ApiErrorException(
|
||||
new ApiErrorMessage(422, ApiErrorMessage::TYPE_INVALID_DATE),
|
||||
'startDate cant be before endDate',
|
||||
);
|
||||
}
|
||||
|
||||
$fareList = $priceService->getFinalPricesForSearch($roomCategory, $searchRequest->startDate, $searchRequest->endDate);
|
||||
$rs = [];
|
||||
foreach ($fareList as $key => $value) {
|
||||
$rs[] = ["guestNumber" => $key, "price" => $value];
|
||||
$daysDiff = $dateStart->diff($dateEnd)->days;
|
||||
if ($daysDiff > $this->configMaxAllowedNightAmount) {
|
||||
return new ApiErrorException(
|
||||
new ApiErrorMessage(400, ApiErrorMessage::TYPE_API_LIMIT),
|
||||
'Query exceeded max allowed night amount',
|
||||
);
|
||||
}
|
||||
|
||||
return $this->json(["results" => $rs]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class ApiErrorMessage
|
||||
const TYPE_LOGICAL_ERROR = 'logical_error';
|
||||
const TYPE_SERVER_ERROR = 'server_error';
|
||||
const TYPE_INVALID_DATE = 'invalid_date';
|
||||
const TYPE_API_LIMIT = 'api_limit';
|
||||
|
||||
private static $titles = array(
|
||||
self::TYPE_VALIDATION_ERROR => 'There was a validation error',
|
||||
@@ -18,6 +19,7 @@ class ApiErrorMessage
|
||||
self::TYPE_LOGICAL_ERROR => 'Invalid request due to a logic error',
|
||||
self::TYPE_SERVER_ERROR => 'Error on server side',
|
||||
self::TYPE_INVALID_DATE => 'Invalid date on request',
|
||||
self::TYPE_API_LIMIT => 'Reached API Limit'
|
||||
);
|
||||
|
||||
private int $statusCode;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\RoomPriceRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: RoomPriceRepository::class)]
|
||||
class RoomPrice
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?RoomCategory $roomCategory = null;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(length: 10, nullable: false)]
|
||||
private ?string $date = null;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: Types::SMALLINT, nullable: false)]
|
||||
private ?int $guestNumber = null;
|
||||
|
||||
#[ORM\Column(nullable: false)]
|
||||
private ?int $price = null;
|
||||
|
||||
|
||||
public function getRoomCategory(): ?RoomCategory
|
||||
{
|
||||
return $this->roomCategory;
|
||||
}
|
||||
|
||||
public function setRoomCategory(?RoomCategory $roomCategory): static
|
||||
{
|
||||
$this->roomCategory = $roomCategory;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTime
|
||||
{
|
||||
return new \DateTime($this->date);
|
||||
}
|
||||
|
||||
public function getDateString(): ?string
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(\DateTime $date): static
|
||||
{
|
||||
$this->date = $date->format('Y-m-d');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGuestNumber(): ?int
|
||||
{
|
||||
return $this->guestNumber;
|
||||
}
|
||||
|
||||
public function setGuestNumber(int $guestNumber): static
|
||||
{
|
||||
$this->guestNumber = $guestNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPrice(): ?int
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
public function setPrice(int $price): static
|
||||
{
|
||||
$this->price = $price;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Entity;
|
||||
|
||||
use App\Enum\UserRoleEnum;
|
||||
use App\Repository\UserRepository;
|
||||
use Deprecated;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
@@ -120,6 +121,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
#[Deprecated("Not used after Symfony 7.3")]
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\RoomPrice;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<RoomPrice>
|
||||
*/
|
||||
class RoomPriceRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, RoomPrice::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pricer for the given period.
|
||||
* @param DateTimeInterface $startDate Period start date (included)
|
||||
* @param DateTimeInterface $endDate Period end date (included)
|
||||
* @return RoomPrice[] Array of RoomPrices
|
||||
*/
|
||||
public function getListForPeriod(DateTimeInterface $startDate, DateTimeInterface $endDate, ?int $roomCategoryId = null): array
|
||||
{
|
||||
$strStartDate = $startDate->format('Y-m-d');
|
||||
$strEndDate = $endDate->format('Y-m-d');
|
||||
$hasRoomCategory = $roomCategoryId != null;
|
||||
|
||||
$dql = 'SELECT p, rc FROM App\Entity\RoomPrice p
|
||||
JOIN p.roomCategory rc
|
||||
WHERE p.date >= :startDate
|
||||
AND p.date <= :endDate';
|
||||
|
||||
if ($hasRoomCategory) {
|
||||
$dql .= ' AND IDENTITY(p.roomCategory) = :roomCategoryId';
|
||||
}
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql)
|
||||
->setParameter('startDate', $strStartDate)
|
||||
->setParameter('endDate', $strEndDate);
|
||||
|
||||
if ($hasRoomCategory) {
|
||||
$query->setParameter('roomCategoryId', $roomCategoryId);
|
||||
}
|
||||
|
||||
return $query->getResult();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return RoomPrice[] Returns an array of RoomPrice objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('r.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?RoomPrice
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Response;
|
||||
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use OpenApi\Attributes as OA;
|
||||
use OpenApi\Attributes\Items;
|
||||
|
||||
|
||||
@@ -28,6 +28,16 @@ class ConfigVariableService
|
||||
return $this->repoVariables->findVariableByKey($key);
|
||||
}
|
||||
|
||||
public function getVariableFromConst(array $varConst): ConfigVariable|null
|
||||
{
|
||||
$key = ConfigVariableConsts::getKeyFromConstant($varConst);
|
||||
if (is_null($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getVariable($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and init all variables.
|
||||
* To be used during Setup process.
|
||||
|
||||
+155
-67
@@ -2,13 +2,17 @@
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Consts\ConfigVariableConsts;
|
||||
use App\Entity\FareTable;
|
||||
use App\Entity\RoomCategory;
|
||||
use App\Entity\RoomFare;
|
||||
use App\Repository\FareTableRepository;
|
||||
use App\Repository\RoomCategoryRepository;
|
||||
use App\Repository\RoomFareRepository;
|
||||
use App\Repository\RoomPriceRepository;
|
||||
use App\Response\SearchPricesResponse;
|
||||
use App\Response\SearchPricesResponsePriceItem;
|
||||
use App\Util\PriceSelectionMethods;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Ds\Set;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -19,13 +23,19 @@ use Psr\Log\LoggerInterface;
|
||||
*/
|
||||
class PricesService
|
||||
{
|
||||
private string $configFareMethod;
|
||||
private string $configPriceMethod;
|
||||
|
||||
public function __construct(
|
||||
private RoomCategoryRepository $repoRoomCategory,
|
||||
private FareTableRepository $repoFareTable,
|
||||
private RoomFareRepository $repoRoomFares,
|
||||
private RoomPriceRepository $repoRoomPrices,
|
||||
private ConfigVariableService $configVarService,
|
||||
private LoggerInterface $logger
|
||||
) {}
|
||||
) {
|
||||
$this->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.
|
||||
@@ -44,43 +54,46 @@ class PricesService
|
||||
}
|
||||
|
||||
$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.
|
||||
$allowedGuestNumbers = $this->getAllowedGuestNumberFromFaretableList($fareTablesForPeriod);
|
||||
$allowedGuestNumberList = $this->getAllowedGuestNumberFromFaretableList($fareTablesForPeriod);
|
||||
|
||||
$rs = [];
|
||||
//Calculate stay price for each allowed GuestNumber.
|
||||
$priceItems = [];
|
||||
foreach ($allowedGuestNumberList as $currentGuestNumber) {
|
||||
|
||||
//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;
|
||||
//Get RoomPrices for current GuestNumber.
|
||||
$roomPricesForGuestNum = [];
|
||||
if (array_key_exists($currentGuestNumber, $filteredRoomPriceList)) {
|
||||
$roomPricesForGuestNum = $filteredRoomPriceList[$currentGuestNumber];
|
||||
}
|
||||
|
||||
//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".
|
||||
//Calculate the price for each day.
|
||||
$dailyPrices = $this->getDailyPricesForSearch($startDate, $endDate, $currentGuestNumber, $fareTablesForPeriod, $roomPricesForGuestNum);
|
||||
|
||||
$finalResult = [];
|
||||
foreach ($rs as $guestNum => $priceArray) {
|
||||
$finalAmount = \array_reduce($priceArray, function ($acc, $element) {
|
||||
return $acc + $element;
|
||||
});
|
||||
$stayPrice = $this->getTotalPriceForStay($dailyPrices);
|
||||
|
||||
$finalResult[$guestNum] = $finalAmount;
|
||||
$item = new SearchPricesResponsePriceItem();
|
||||
$item->guestNumber = $currentGuestNumber;
|
||||
$item->price = $stayPrice;
|
||||
$priceItems[] = $item;
|
||||
}
|
||||
|
||||
return $finalResult;
|
||||
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.
|
||||
*
|
||||
* e. g. --> [ 2 => 120€, 3 => 180€ ]
|
||||
*
|
||||
* @return SearchPricesResponse[] Total prices for each guestnumber of each roomCategory.
|
||||
*/
|
||||
public function getRoomSearch(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array
|
||||
@@ -100,41 +113,57 @@ class PricesService
|
||||
$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($roomCat->getId(), $faretablesByRoomCategory)) {
|
||||
$roomFaretables = $faretablesByRoomCategory[$roomCat->getId()];
|
||||
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.
|
||||
$roomPricesByGuestNum = [];
|
||||
$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);
|
||||
$dailyPrices = $this->getDailyPricesForSearch($startDate, $endDate, $currentGuestNumber, $roomFaretables, $roomPricesForGuestNum);
|
||||
|
||||
$stayPrice = $this->getTotalPriceForStay($dailyPrices);
|
||||
|
||||
$roomPricesByGuestNum[$currentGuestNumber] = $stayPrice;
|
||||
}
|
||||
|
||||
$roomPrices = [];
|
||||
foreach ($roomPricesByGuestNum as $guestNum => $price) {
|
||||
$item = new SearchPricesResponsePriceItem();
|
||||
$item->guestNumber = $guestNum;
|
||||
$item->price = $price;
|
||||
|
||||
$roomPrices[] = $item;
|
||||
$item->guestNumber = $currentGuestNumber;
|
||||
$item->price = $stayPrice;
|
||||
$priceItems[] = $item;
|
||||
}
|
||||
|
||||
$roomItem = new SearchPricesResponse();
|
||||
$roomItem->roomCategoryId = $roomCat->getId();
|
||||
$roomItem->prices = $roomPrices;
|
||||
$roomItem->prices = $priceItems;
|
||||
|
||||
$rs[] = $roomItem;
|
||||
}
|
||||
@@ -150,7 +179,7 @@ class PricesService
|
||||
* @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
|
||||
private function getDailyPricesForSearch(\DateTimeInterface $startDate, \DateTimeInterface $endDate, int $guestNumber, array $fareTableList, array $roomPricesByGuestNum): array
|
||||
{
|
||||
$daysDiff = $startDate->diff($endDate)->days;
|
||||
|
||||
@@ -159,7 +188,8 @@ class PricesService
|
||||
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.
|
||||
//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);
|
||||
});
|
||||
@@ -177,11 +207,18 @@ class PricesService
|
||||
return [];
|
||||
}
|
||||
|
||||
$currentFareTable = array_values($currentFareTableList)[0];
|
||||
|
||||
//TODOME: -> Obtener precio específico (RoomPrices).
|
||||
//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(
|
||||
@@ -195,15 +232,16 @@ class PricesService
|
||||
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 {
|
||||
//Calculate RoomFare.
|
||||
$fareAmount = $this->calculateFareApplyingConditions($roomFares, $currentDate);
|
||||
}
|
||||
|
||||
$dailyPrices[$strCurrentDate] = $fareAmount;
|
||||
//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"));
|
||||
@@ -218,32 +256,76 @@ class PricesService
|
||||
* @param DateTimeInterface $fareCondDate Fecha utilizada para las condiciones.
|
||||
*
|
||||
*/
|
||||
private function calculateFareApplyingConditions(Collection $roomFareList, \DateTimeInterface $fareCondDate): int
|
||||
private function calculateFareApplyingConditions(Collection $roomFareList, \DateTimeInterface $fareCondDate): int|null
|
||||
{
|
||||
$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();
|
||||
$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) {
|
||||
return $roomFare->getFareAmount();
|
||||
$applFaresByDay[] = $fare;
|
||||
}
|
||||
}
|
||||
|
||||
//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 final fare value.
|
||||
//Use fares for current weekday.
|
||||
$finalFare = $this->getPriceFromRoomFareList($applFaresByDay);
|
||||
if ($finalFare != null) {
|
||||
return $finalFare;
|
||||
}
|
||||
|
||||
return $faresWithoutConditions->first()->getFareAmount();
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,13 +355,19 @@ class PricesService
|
||||
* @param array $dailyPrices Array containing the price for each day.
|
||||
* @return int Total stay price.
|
||||
*/
|
||||
private function getTotalPriceForStay(array $dailyPrices): int
|
||||
private function getTotalPriceForStay(array $dailyPrices): int|null
|
||||
{
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Util;
|
||||
|
||||
use App\Entity\RoomFare;
|
||||
|
||||
//Utils - Functions to select prices and fares.
|
||||
class PriceSelectionMethods
|
||||
{
|
||||
#region Fare Selection
|
||||
|
||||
/**
|
||||
* Fare Selection - Get fare with highest price.
|
||||
* @param RoomFare[] $fareList List of fares.
|
||||
*/
|
||||
public static function selectFare_Highest(array $fareList): int
|
||||
{
|
||||
$pricesArr = array_map(function (RoomFare $f) {
|
||||
return $f->getFareAmount();
|
||||
}, $fareList);
|
||||
|
||||
return max($pricesArr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fare Selection - Get fare with lowest price.
|
||||
* @param RoomFare[] $fareList List of fares.
|
||||
*/
|
||||
public static function selectFare_Lowest(array $fareList): int
|
||||
{
|
||||
$pricesArr = array_map(function (RoomFare $f) {
|
||||
return $f->getFareAmount();
|
||||
}, $fareList);
|
||||
|
||||
return min($pricesArr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fare Selection - Get average price of given fares.
|
||||
* @param RoomFare[] $fareList List of fares.
|
||||
*/
|
||||
public static function selectFare_Average(array $fareList): int|null
|
||||
{
|
||||
$totalAmount = 0;
|
||||
$count = 0;
|
||||
foreach ($fareList as $fare) {
|
||||
$fareAmount = $fare->getFareAmount();
|
||||
if ($fareAmount != null) {
|
||||
$totalAmount += $fareAmount;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count > 0 ? ($totalAmount / $count) : null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Price Selection
|
||||
|
||||
/**
|
||||
* Price Method - Total price is the sum of daily prices.
|
||||
* @param array $prices
|
||||
* @return int Total price.
|
||||
*/
|
||||
public static function priceMethod_Sum(array $prices): int
|
||||
{
|
||||
$values = array_values($prices);
|
||||
return array_sum($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Price Method - Apply the hightest daily price to all days.
|
||||
* @param array $prices
|
||||
* @return int Total price.
|
||||
*/
|
||||
public static function priceMethod_Highest(array $prices): int
|
||||
{
|
||||
$highestPrice = max($prices);
|
||||
$numDays = count($prices);
|
||||
|
||||
return $numDays * $highestPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Price Method - Apply the lowest daily price to all days.
|
||||
* @param array $prices
|
||||
* @return int Total price.
|
||||
*/
|
||||
public static function priceMethod_Lowest(array $prices): int
|
||||
{
|
||||
$highestPrice = min($prices);
|
||||
$numDays = count($prices);
|
||||
|
||||
return $numDays * $highestPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Price Method - Calculate average daily price and apply it to all days.
|
||||
* @param array $prices
|
||||
* @return int Total price.
|
||||
*/
|
||||
public static function priceMethod_Average(array $prices): int|null
|
||||
{
|
||||
$sum = array_sum($prices);
|
||||
$numDays = count($prices);
|
||||
$averagePrice = $numDays > 0 ? $sum / $numDays : null;
|
||||
|
||||
return $averagePrice != null ? $averagePrice * $numDays : null;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
+42
-37
@@ -9,17 +9,17 @@
|
||||
}
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.12",
|
||||
"version": "2.18",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.12",
|
||||
"ref": "32f890075233ac4a1176e848a0f8e932ac6536d6"
|
||||
"version": "2.13",
|
||||
"ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb"
|
||||
},
|
||||
"files": [
|
||||
"./config/packages/doctrine.yaml",
|
||||
"./src/Entity/.gitignore",
|
||||
"./src/Repository/.gitignore"
|
||||
"config/packages/doctrine.yaml",
|
||||
"src/Entity/.gitignore",
|
||||
"src/Repository/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-migrations-bundle": {
|
||||
@@ -36,13 +36,16 @@
|
||||
]
|
||||
},
|
||||
"easycorp/easyadmin-bundle": {
|
||||
"version": "4.10",
|
||||
"version": "4.26",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.0",
|
||||
"ref": "b131e6cbfe1b898a508987851963fff485986285"
|
||||
}
|
||||
"version": "4.14",
|
||||
"ref": "13b3e524d1038a6861ad6c7fc64e31b644ba0c54"
|
||||
},
|
||||
"files": [
|
||||
"config/routes/easyadmin.yaml"
|
||||
]
|
||||
},
|
||||
"nelmio/api-doc-bundle": {
|
||||
"version": "4.30",
|
||||
@@ -70,18 +73,18 @@
|
||||
]
|
||||
},
|
||||
"symfony/asset-mapper": {
|
||||
"version": "7.0",
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.4",
|
||||
"ref": "6c28c471640cc2c6e60812ebcb961c526ef8997f"
|
||||
"ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a"
|
||||
},
|
||||
"files": [
|
||||
"./assets/app.js",
|
||||
"./assets/styles/app.css",
|
||||
"./config/packages/asset_mapper.yaml",
|
||||
"./importmap.php"
|
||||
"assets/app.js",
|
||||
"assets/styles/app.css",
|
||||
"config/packages/asset_mapper.yaml",
|
||||
"importmap.php"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
@@ -97,15 +100,16 @@
|
||||
]
|
||||
},
|
||||
"symfony/flex": {
|
||||
"version": "2.4",
|
||||
"version": "2.8",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
|
||||
"version": "2.4",
|
||||
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
|
||||
},
|
||||
"files": [
|
||||
".env"
|
||||
".env",
|
||||
".env.dev"
|
||||
]
|
||||
},
|
||||
"symfony/form": {
|
||||
@@ -121,14 +125,15 @@
|
||||
]
|
||||
},
|
||||
"symfony/framework-bundle": {
|
||||
"version": "7.0",
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "6356c19b9ae08e7763e4ba2d9ae63043efc75db5"
|
||||
"version": "7.3",
|
||||
"ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9"
|
||||
},
|
||||
"files": [
|
||||
".editorconfig",
|
||||
"config/packages/cache.yaml",
|
||||
"config/packages/framework.yaml",
|
||||
"config/preload.php",
|
||||
@@ -173,12 +178,12 @@
|
||||
]
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "7.0",
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "21b72649d5622d8f7da329ffb5afb232a023619d"
|
||||
"ref": "ab1e60e2afd5c6f4a6795908f646e235f2564eb2"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/routing.yaml",
|
||||
@@ -199,16 +204,16 @@
|
||||
]
|
||||
},
|
||||
"symfony/translation": {
|
||||
"version": "7.0",
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "e28e27f53663cc34f0be2837aba18e3a1bef8e7b"
|
||||
"ref": "620a1b84865ceb2ba304c8f8bf2a185fbf32a843"
|
||||
},
|
||||
"files": [
|
||||
"./config/packages/translation.yaml",
|
||||
"./translations/.gitignore"
|
||||
"config/packages/translation.yaml",
|
||||
"translations/.gitignore"
|
||||
]
|
||||
},
|
||||
"symfony/twig-bundle": {
|
||||
@@ -246,15 +251,15 @@
|
||||
]
|
||||
},
|
||||
"symfony/ux-twig-component": {
|
||||
"version": "2.25",
|
||||
"version": "2.30",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.13",
|
||||
"ref": "9b627037a510e20eddb559e06050a34854ce51d6"
|
||||
"ref": "f367ae2a1faf01c503de2171f1ec22567febeead"
|
||||
},
|
||||
"files": [
|
||||
"./config/packages/twig_component.yaml"
|
||||
"config/packages/twig_component.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/validator": {
|
||||
@@ -270,16 +275,16 @@
|
||||
]
|
||||
},
|
||||
"symfony/web-profiler-bundle": {
|
||||
"version": "7.0",
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.1",
|
||||
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
||||
"version": "7.3",
|
||||
"ref": "a363460c1b0b4a4d0242f2ce1a843ca0f6ac9026"
|
||||
},
|
||||
"files": [
|
||||
"./config/packages/web_profiler.yaml",
|
||||
"./config/routes/web_profiler.yaml"
|
||||
"config/packages/web_profiler.yaml",
|
||||
"config/routes/web_profiler.yaml"
|
||||
]
|
||||
},
|
||||
"twig/extra-bundle": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
|
||||
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
|
||||
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
|
||||
{% if ea.crud.currentAction == 'detail' %}
|
||||
{% if ea().crud.currentAction == 'detail' %}
|
||||
<div class="field-icon-div d-flex gap-3">
|
||||
<span title="{{ field.value }}">{{ field.formattedValue|raw }}</span>
|
||||
<div class="badge text-bg-secondary">
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
{% block stylesheets %}
|
||||
{% endblock %}
|
||||
|
||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||
{% block javascripts %}
|
||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user