7 Commits
Author SHA1 Message Date
Daniel-Garmig e8bfcd9c6b Room Availability - Edit + bulk edit 2024-05-11 00:45:35 +02:00
Daniel-Garmig ed4ba9d82f Booking Creation - Finish initial implement.
- Check and update availability.
- Set prices.
- Generate booking refNumber.
2024-05-10 21:31:24 +02:00
Daniel-Garmig 528d1a39ab Booking API - "api/booking/create"
- New endpoint to create bookings.
2024-05-08 23:29:19 +02:00
Daniel-Garmig 4f451c2f75 Bookings - Easyadmin support. 2024-05-08 20:38:03 +02:00
Daniel-Garmig 86abb7222e Bookings - Create model:
- Booking model.
- BookingCustomer model.
- BookingRoom
2024-05-08 20:02:18 +02:00
Daniel-Garmig 7b6d1408f8 API response adjustments. 2024-05-07 22:50:09 +02:00
Daniel-Garmig a4ec188cff Add nelmio/cors-bundle - Add CORS support for Front-end requests. 2024-05-04 01:44:22 +02:00
33 changed files with 1319 additions and 45 deletions
+4
View File
@@ -28,3 +28,7 @@ APP_SECRET=d130654a5d6a8ab5945b20b22dfeee74
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=10.11.2-MariaDB&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]:54195/app?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ### ###< doctrine/doctrine-bundle ###
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###
+1 -1
View File
@@ -16,7 +16,7 @@ docker compose build --no-cache
Start Containers: Start Containers:
``` ```
docker compose up --pull always -d --wait docker compose up --pull always -d --wait
docker compose --env-file .\.env.local up -d --watch docker compose --env-file .\.env.local up -d --wait
``` ```
Stop containers Stop containers
+2
View File
@@ -14,12 +14,14 @@
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.1", "doctrine/orm": "^3.1",
"easycorp/easyadmin-bundle": "^4.10", "easycorp/easyadmin-bundle": "^4.10",
"nelmio/cors-bundle": "^2.4",
"phpdocumentor/reflection-docblock": "^5.4", "phpdocumentor/reflection-docblock": "^5.4",
"phpstan/phpdoc-parser": "^1.28", "phpstan/phpdoc-parser": "^1.28",
"runtime/frankenphp-symfony": "^0.2.0", "runtime/frankenphp-symfony": "^0.2.0",
"symfony/console": "7.0.*", "symfony/console": "7.0.*",
"symfony/dotenv": "7.0.*", "symfony/dotenv": "7.0.*",
"symfony/flex": "^2", "symfony/flex": "^2",
"symfony/form": "7.0.*",
"symfony/framework-bundle": "7.0.*", "symfony/framework-bundle": "7.0.*",
"symfony/property-access": "7.0.*", "symfony/property-access": "7.0.*",
"symfony/property-info": "7.0.*", "symfony/property-info": "7.0.*",
Generated
+69 -7
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "e0a5e615a56df791d0b9a2dbbe533659", "content-hash": "f177a4872c4ced09856f94d423de0b21",
"packages": [ "packages": [
{ {
"name": "doctrine/cache", "name": "doctrine/cache",
@@ -1320,6 +1320,68 @@
], ],
"time": "2024-04-19T18:13:15+00:00" "time": "2024-04-19T18:13:15+00:00"
}, },
{
"name": "nelmio/cors-bundle",
"version": "2.4.0",
"source": {
"type": "git",
"url": "https://github.com/nelmio/NelmioCorsBundle.git",
"reference": "78fcdb91f76b080a1008133def9c7f613833933d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/78fcdb91f76b080a1008133def9c7f613833933d",
"reference": "78fcdb91f76b080a1008133def9c7f613833933d",
"shasum": ""
},
"require": {
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"mockery/mockery": "^1.3.6",
"symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Nelmio\\CorsBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nelmio",
"homepage": "http://nelm.io"
},
{
"name": "Symfony Community",
"homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors"
}
],
"description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application",
"keywords": [
"api",
"cors",
"crossdomain"
],
"support": {
"issues": "https://github.com/nelmio/NelmioCorsBundle/issues",
"source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.4.0"
},
"time": "2023-11-30T16:41:19+00:00"
},
{ {
"name": "phpdocumentor/reflection-common", "name": "phpdocumentor/reflection-common",
"version": "2.2.0", "version": "2.2.0",
@@ -3079,16 +3141,16 @@
}, },
{ {
"name": "symfony/form", "name": "symfony/form",
"version": "v7.0.6", "version": "v7.0.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/form.git", "url": "https://github.com/symfony/form.git",
"reference": "d5db6599775a563792391d0045decc240e7ebd1e" "reference": "b4df6a399a2b03782a0163807239db342659f54f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/d5db6599775a563792391d0045decc240e7ebd1e", "url": "https://api.github.com/repos/symfony/form/zipball/b4df6a399a2b03782a0163807239db342659f54f",
"reference": "d5db6599775a563792391d0045decc240e7ebd1e", "reference": "b4df6a399a2b03782a0163807239db342659f54f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3155,7 +3217,7 @@
"description": "Allows to easily create, process and reuse HTML forms", "description": "Allows to easily create, process and reuse HTML forms",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/form/tree/v7.0.6" "source": "https://github.com/symfony/form/tree/v7.0.7"
}, },
"funding": [ "funding": [
{ {
@@ -3171,7 +3233,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-03-28T09:20:36+00:00" "time": "2024-04-28T11:44:19+00:00"
}, },
{ {
"name": "symfony/framework-bundle", "name": "symfony/framework-bundle",
+1
View File
@@ -9,4 +9,5 @@ return [
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true], EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
]; ];
+15
View File
@@ -0,0 +1,15 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/api/':
allow_origin: ['*']
allow_headers: ['*']
allow_methods: ['POST', 'GET']
max_age: 3600
'^/': null
+86
View File
@@ -0,0 +1,86 @@
# SomniaRooms - API Endpoints and Objects
En este documento se indican los endpoints disponibles en la API de SomniaRooms, así como los mensajes de petición y respuesta en cada caso. (Vamos, un Swagger pero escrito a mano.)
## Config (`api/config`)
Permite al cliente obtener las variables de configuración del sistema. Ver más sobre las [variables de configuración](config-vars.md) en la documentación.
**TODO**
## Room Category (`api/room/category`)
Estos endpoints permiten obtener la información de las categorias del sistema.
**TODO**
## Search (`api/search`)
Conjunto de Endpoints para consultar el estado de las habitaciones.
## Reservas (`api/booking`)
Endpoint para la gestión y consulta de las reservas.
### `api/booking/details`
Permite obtener el detalle de una reserva.
Es necesario enviar la información de la reserva a consultar.
**Request (JSON):**
```json
{
"refNumber": "2024001",
"email": "[email protected]"
}
```
Se debe incluir el número de referencia de la reserva y el correo electrónico asociado.
**Response (JSON):**
TODO
### `api/booking/create`
Endpoint para crear nuevas reservas.
Se enviarán los datos de la reserva con el siguiente formato:
**Request (JSON)_**
```json
{
"arrivalDate": "20240510",
"departureDate": "20240514",
"customerDetails": {
"name": "Juan",
"surname": "Perez",
"email": "[email protected]",
"phoneNumber": "12345678"
},
"rooms": [
{
"roomCategory": 1,
"guestNumber": 2,
"amount": 1
},
{
"roomCategory": 1,
"guestNumber": 3,
"amount": 1
}
]
}
```
Si la reserva es correcta se devolverá el número de referencia de la reserva:
```json
{
"refNumber": "2024001",
"email": "[email protected]",
}
```
+51
View File
@@ -0,0 +1,51 @@
<?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 Version20240508180113 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('CREATE SEQUENCE booking_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE booking_customer_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE booking_room_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE booking (id INT NOT NULL, arrival_date DATE NOT NULL, departure_date DATE NOT NULL, booking_time TIMESTAMP(0) WITH TIME ZONE NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE TABLE booking_customer (id INT NOT NULL, booking_id INT NOT NULL, name VARCHAR(64) NOT NULL, surname VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_BA0D10843301C60 ON booking_customer (booking_id)');
$this->addSql('CREATE TABLE booking_room (id INT NOT NULL, booking_id INT NOT NULL, room_category_id INT NOT NULL, guest_number INT NOT NULL, room_price INT NOT NULL, amount INT NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_6A0E73D53301C60 ON booking_room (booking_id)');
$this->addSql('CREATE INDEX IDX_6A0E73D567333DD ON booking_room (room_category_id)');
$this->addSql('ALTER TABLE booking_customer ADD CONSTRAINT FK_BA0D10843301C60 FOREIGN KEY (booking_id) REFERENCES booking (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE booking_room ADD CONSTRAINT FK_6A0E73D53301C60 FOREIGN KEY (booking_id) REFERENCES booking (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE booking_room ADD CONSTRAINT FK_6A0E73D567333DD FOREIGN KEY (room_category_id) REFERENCES room_category (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('DROP SEQUENCE booking_id_seq CASCADE');
$this->addSql('DROP SEQUENCE booking_customer_id_seq CASCADE');
$this->addSql('DROP SEQUENCE booking_room_id_seq CASCADE');
$this->addSql('ALTER TABLE booking_customer DROP CONSTRAINT FK_BA0D10843301C60');
$this->addSql('ALTER TABLE booking_room DROP CONSTRAINT FK_6A0E73D53301C60');
$this->addSql('ALTER TABLE booking_room DROP CONSTRAINT FK_6A0E73D567333DD');
$this->addSql('DROP TABLE booking');
$this->addSql('DROP TABLE booking_customer');
$this->addSql('DROP TABLE booking_room');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?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 Version20240508180617 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('ALTER TABLE booking ADD ref_number VARCHAR(20) NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE booking DROP ref_number');
}
}
@@ -0,0 +1,42 @@
<?php
namespace App\Controller\Admin;
use App\Controller\Admin\BookingCustomerCrudController;
use App\Entity\Booking;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class BookingCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Booking::class;
}
public function configureFields(string $pageName): iterable
{
$fieldRefNumber = TextField::new ('refNumber');
$fieldBookingTime = DateTimeField::new ('BookingTime');
if ($pageName == Crud::PAGE_EDIT) {
$fieldRefNumber->setDisabled(true);
$fieldBookingTime->setDisabled(true);
}
yield FormField::addFieldset('Booking details');
yield $fieldRefNumber;
yield $fieldBookingTime;
yield DateField::new ('ArrivalDate');
yield DateField::new ('DepartureDate');
yield AssociationField::new ('bookingCustomer')->renderAsEmbeddedForm(BookingCustomerCrudController::class);
yield CollectionField::new ('bookingRooms')->useEntryCrudForm();
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Controller\Admin;
use App\Entity\BookingCustomer;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class BookingCustomerCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return BookingCustomer::class;
}
public function configureFields(string $pageName): iterable
{
yield TextField::new('Name');
yield TextField::new('Surname');
yield TextField::new('Email');
yield TextField::new('PhoneNumber');
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Controller\Admin;
use App\Controller\Admin\RoomCategoryCrudController;
use App\Entity\BookingRoom;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
class BookingRoomCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return BookingRoom::class;
}
public function configureFields(string $pageName): iterable
{
yield AssociationField::new ('RoomCategory')->setCrudController(RoomCategoryCrudController::class);
yield NumberField::new ('GuestNumber');
yield MoneyField::new ('RoomPrice')->setCurrency("EUR");
yield NumberField::new ('Amount');
}
}
@@ -2,22 +2,34 @@
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Repository\RoomAvailabilityRepository;
use App\Repository\RoomCategoryRepository;
use App\Entity\RoomAvailability; use App\Entity\RoomAvailability;
use App\Entity\RoomCategory; use App\Entity\RoomCategory;
use App\Repository\RoomAvailabilityRepository;
use App\Repository\RoomCategoryRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Component\Security\Http\Attribute\IsGranted;
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField, DateField, AssociationField, NumberField};
#[IsGranted("ROLE_ADMIN")] #[IsGranted("ROLE_ADMIN")]
class RoomAvailabilityAdminController extends AbstractController class RoomAvailabilityAdminController extends AbstractController
{ {
private $adminUrlGenerator;
private EntityManagerInterface $entityManager;
public function __construct(AdminUrlGenerator $adminUrlGenerator, EntityManagerInterface $em)
{
$this->adminUrlGenerator = $adminUrlGenerator;
$this->entityManager = $em;
}
#[Route('/admin/roomAvailability', name: 'admin_roomAvailability')] #[Route('/admin/roomAvailability', name: 'admin_roomAvailability')]
public function index(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo): Response public function index(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo): Response
{ {
@@ -28,6 +40,7 @@ class RoomAvailabilityAdminController extends AbstractController
]); ]);
} }
// List RoomAvailability items from category.
#[Route('/admin/roomAvailability/{categoryId}', name: 'admin_roomAvailability_category')] #[Route('/admin/roomAvailability/{categoryId}', name: 'admin_roomAvailability_category')]
public function categoryAvailability(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo, string $categoryId): Response public function categoryAvailability(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo, string $categoryId): Response
{ {
@@ -40,6 +53,83 @@ class RoomAvailabilityAdminController extends AbstractController
]); ]);
} }
// Edit a RoomAvailability numAvailable.
#[Route('/admin/roomAvailability/edit/{categoryId}/{day}', name: 'admin_roomAvailability_category_update')]
public function updateCategoryAvailability(Request $request, RoomAvailabilityRepository $repo, string $categoryId, string $day): Response
{
$availabilityItem = $repo->find(["day" => $day, "roomCategory" => $categoryId]);
if ($availabilityItem == null) {
//TODOME: Aquí deberíamos llevar a una página de error o algo.
$url = $this->adminUrlGenerator->setRoute('admin_roomAvailability_category', [
'categoryId' => $item->getId(),
])->generateUrl();
return $this->redirect($url);
}
$form = $this->createFormBuilder($availabilityItem)
->add('numAvailable', NumberType::class, ['label' => 'Num Available'])
->add('save', SubmitType::class, ['label' => 'Update value'])
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// $form->getData() holds the submitted values
// but, the original `$availabilityItem` variable has also been updated
$this->entityManager->flush();
$url = $this->adminUrlGenerator->setRoute('admin_roomAvailability_category', [
'categoryId' => $categoryId,
])->generateUrl();
return $this->redirect($url);
}
return $this->render('admin/room_availability/availability_day_edit.html.twig', [
'form' => $form,
'availabilityData' => $availabilityItem,
]);
}
// Bulk edit all roomAvailability of a category on period.
#[Route('/admin/roomAvailability/{categoryId}/bulkedit', name: 'admin_roomAvailability_category_bulkupdate')]
public function bulkUpdateCategoryAvailability(Request $request, RoomAvailabilityRepository $repo, string $categoryId): Response
{
$form = $this->createFormBuilder()
->add('startDate', DateType::class, [
'widget' => 'single_text',
])
->add('endDate', DateType::class, [
'widget' => 'single_text',
])
->add('numAvailable', NumberType::class, [
'label' => 'Num Available',
])
->add('save', SubmitType::class, ['label' => 'Update value'])
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// $form->getData() holds the submitted values
$data = $form->getData();
$categoryItem = $this->entityManager->find(RoomCategory::class, $categoryId);
$repo->bulkEditRoomAvailabilityForCategoryOnPeriod($categoryItem, $data['startDate'], $data['endDate'], $data['numAvailable']);
$url = $this->adminUrlGenerator->setRoute('admin_roomAvailability')->generateUrl();
return $this->redirect($url);
}
return $this->render('admin/room_availability/availability_bulk_edit.html.twig', [
'form' => $form,
'categoryId' => $categoryId,
]);
}
#[Route('/admin/roomAvailabilityTEST/', name: 'admin_roomAvailability_test')] #[Route('/admin/roomAvailabilityTEST/', name: 'admin_roomAvailability_test')]
public function availabilityTest(EntityManagerInterface $entityManager): Response public function availabilityTest(EntityManagerInterface $entityManager): Response
{ {
@@ -5,6 +5,7 @@ namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\Field; use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField; use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
@@ -27,6 +28,7 @@ class RoomCategoryCrudController extends AbstractCrudController
public function configureFields(string $pageName): iterable public function configureFields(string $pageName): iterable
{ {
return [ return [
//IdField::new('id'),
TextField::new('name'), TextField::new('name'),
TextField::new('description'), TextField::new('description'),
TextField::new('bedType'), TextField::new('bedType'),
@@ -2,20 +2,18 @@
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Entity\Booking;
use App\Entity\ConfigVariable;
use App\Entity\FareTable;
use App\Entity\RoomCategory;
use App\Entity\RoomFare;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard; use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem; use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use App\Entity\RoomCategory;
use App\Entity\RoomAvailability;
use App\Entity\FareTable;
use App\Entity\RoomFare;
use App\Entity\User;
use App\Entity\ConfigVariable;
class SomniaDashboardController extends AbstractDashboardController class SomniaDashboardController extends AbstractDashboardController
{ {
#[Route('/admin', name: 'admin')] #[Route('/admin', name: 'admin')]
@@ -26,7 +24,7 @@ class SomniaDashboardController extends AbstractDashboardController
public function configureDashboard(): Dashboard public function configureDashboard(): Dashboard
{ {
return Dashboard::new() return Dashboard::new ()
->setTitle('SomniaRooms'); ->setTitle('SomniaRooms');
} }
@@ -34,11 +32,14 @@ class SomniaDashboardController extends AbstractDashboardController
{ {
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::Section('Booking Management');
yield MenuItem::linkToCrud('Bookings', 'fas fa-book', Booking::class);
yield MenuItem::Section('Room Management'); yield MenuItem::Section('Room Management');
yield MenuItem::linkToCrud('Category', 'fas fa-door-open', RoomCategory::class); yield MenuItem::linkToCrud('Category', 'fas fa-layer-group', RoomCategory::class);
yield MenuItem::linkToRoute('Room Availability', 'fas fa-calendar-check', 'admin_roomAvailability'); yield MenuItem::linkToRoute('Room Availability', 'fas fa-calendar-check', 'admin_roomAvailability');
yield MenuItem::linkToCrud('Fare Tables', 'fas fa-door-open', FareTable::class); yield MenuItem::linkToCrud('Fare Tables', 'fas fa-table', FareTable::class);
yield MenuItem::linkToCrud('Room Fares', 'fas fa-door-open', RoomFare::class); yield MenuItem::linkToCrud('Room Fares', 'fas fa-tag', RoomFare::class);
yield MenuItem::Section('App Management'); yield MenuItem::Section('App Management');
yield MenuItem::linkToCrud('Config Vars', 'fas fa-gear', ConfigVariable::class); yield MenuItem::linkToCrud('Config Vars', 'fas fa-gear', ConfigVariable::class);
+161
View File
@@ -0,0 +1,161 @@
<?php
namespace App\Controller;
use App\Entity\Booking;
use App\Entity\BookingCustomer;
use App\Entity\BookingRoom;
use App\Repository\BookingRepository;
use App\Repository\RoomCategoryRepository;
use App\Repository\RoomAvailabilityRepository;
use App\Service\PricesService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Attribute\Route;
class BookingController extends AbstractController
{
#[Route('/api/booking/details', name: 'app_booking_details')]
public function getBookingDetails(Request $request, BookingRepository $repoBooking): JsonResponse
{
$payload = $request->toArray();
if (\is_null($payload)) {
throw new \Exception('Invalid Payload', 400);
}
$refNumber = $payload["refNumber"];
$customerEmail = $payload["email"];
$bookingList = $repoBooking->findBy(["refNumber" => $refNumber]);
if (\is_null($bookingList) || \count($bookingList) == 0) {
throw new HttpException(404, 'Invalid Booking');
}
$customerBooking = $bookingList[0];
if ($customerBooking->getBookingCustomer()->getEmail() != $customerEmail) {
throw new HttpException(404, 'Invalid Booking');
}
return $this->json($customerBooking);
}
#[Route('/api/booking/create', name: 'app_booking_create')]
public function newBooking(Request $request, BookingRepository $repoBooking, EntityManagerInterface $entityManager,
RoomCategoryRepository $repoCat, RoomAvailabilityRepository $repoAvailability,
PricesService $priceSrv): JsonResponse
{
$payload = $request->toArray();
if (\is_null($payload)) {
throw new \Exception('Invalid Payload', 400);
}
$booking = new Booking();
try {
$reqArrivalDate = $payload["arrivalDate"];
$reqDepartureDate = $payload["departureDate"];
$arrivalDate = new \DateTime($reqArrivalDate);
$departureDate = new \DateTime($reqDepartureDate);
if ($arrivalDate >= $departureDate) {
throw new HttpException(400, "Arrival cant be before departure");
}
$entityManager->beginTransaction();
$currentTime = new \DateTime();
// Create booking from request data.
$booking->setBookingTime($currentTime);
$booking->setArrivalDate($arrivalDate);
$booking->setDepartureDate($departureDate);
$highest_id = $entityManager->createQueryBuilder()
->select('MAX(e.id)')
->from('App\Entity\Booking', 'e')
->getQuery()
->getSingleScalarResult();
$booking->setRefNumber($currentTime->format("Y") . $highest_id + 1);
$entityManager->persist($booking);
// Create booking customer.
$reqCustomer = $payload["customerDetails"];
$bookingCustomer = new BookingCustomer();
$bookingCustomer->setName($reqCustomer["name"]);
$bookingCustomer->setSurname($reqCustomer["surname"]);
$bookingCustomer->setEmail($reqCustomer["email"]);
$bookingCustomer->setPhoneNumber($reqCustomer["phoneNumber"]);
$bookingCustomer->setBooking($booking);
$entityManager->persist($bookingCustomer);
$reqRoomList = $payload["rooms"];
if (\count($reqRoomList) == 0) {
throw new HttpException(400, "Se debe indicar al menos una habitación");
}
// Create each room.
foreach ($reqRoomList as $roomItem) {
$room = new BookingRoom();
$room->setBooking($booking);
//Get Room category.
$roomCategory = $repoCat->find($roomItem["roomCategory"]);
if ($roomCategory == null) {
throw new HttpException(400, "La RoomCategory no es válida.");
}
//Check room availability.
$amountAvailable = $repoAvailability->getAvailabilityForCategoryInPeriod($roomCategory, $arrivalDate, $departureDate);
$selectedAmount = $roomItem['amount'];
$selectedGuestAmount = $roomItem["guestNumber"];
if($amountAvailable == null || $selectedAmount > $amountAvailable) {
throw new HttpException(400, "No se pueden reservar más habitaciones de las disponibles.");
}
$room->setRoomCategory($roomCategory);
$room->setGuestNumber($selectedGuestAmount);
$room->setAmount($selectedAmount);
//Get Price for period.
$priceList = $priceSrv->getFinalPricesForSearch($roomCategory, $arrivalDate, $departureDate);
$priceFound = false;
foreach ($priceList as $key => $value) {
if($key == $selectedGuestAmount) {
$room->setRoomPrice($value);
$priceFound = true;
break;
}
}
if(!$priceFound) {
throw new HttpException(500,
"No se puedo obtener el precio para la categoría: " . $roommCategory->getId() . " , guests: " . $selecctedGuestAmount);
}
$entityManager->persist($room);
//If booking is nice, let's update current availability.
$repoAvailability->updateAvailabilityForRoomCategoryOnPeriod($roomCategory, $arrivalDate, $departureDate, $selectedAmount, true); //Update using "diff" mode.
}
$entityManager->commit();
$entityManager->flush();
} catch (\Throwable $th) {
$entityManager->rollback();
throw $th;
}
$results = ["refNumber" => $booking->getRefNumber()];
return $this->json($results);
}
}
+4 -4
View File
@@ -9,7 +9,7 @@ use App\Repository\ConfigVariableRepository;
class ConfigVariableController extends AbstractController class ConfigVariableController extends AbstractController
{ {
#[Route('/config', name: 'app_config_variable')] #[Route('/api/config', name: 'app_config_variable')]
public function getAllVariables(ConfigVariableRepository $repo): Response public function getAllVariables(ConfigVariableRepository $repo): Response
{ {
$varList = $repo->findAllVariables(); $varList = $repo->findAllVariables();
@@ -17,7 +17,7 @@ class ConfigVariableController extends AbstractController
return $this->json(['results' => $varList]); return $this->json(['results' => $varList]);
} }
#[Route('/config/section', name: 'app_config_sections')] #[Route('/api/config/section', name: 'app_config_sections')]
public function getSections(ConfigVariableRepository $repo): Response public function getSections(ConfigVariableRepository $repo): Response
{ {
$sectionList = $repo->getAllSections(); $sectionList = $repo->getAllSections();
@@ -30,7 +30,7 @@ class ConfigVariableController extends AbstractController
return $this->json(['results' => $returnList]); return $this->json(['results' => $returnList]);
} }
#[Route('/config/section/{section}', name: 'app_config_variableBySection')] #[Route('/api/config/section/{section}', name: 'app_config_variableBySection')]
public function getVariablesBySection(ConfigVariableRepository $repo, string $section): Response public function getVariablesBySection(ConfigVariableRepository $repo, string $section): Response
{ {
if(\is_null($section) || $section == '') { if(\is_null($section) || $section == '') {
@@ -42,7 +42,7 @@ class ConfigVariableController extends AbstractController
return $this->json(['results' => $varList]); return $this->json(['results' => $varList]);
} }
#[Route('/config/var/{key}', name: 'app_config_variable')] #[Route('/api/config/var/{key}', name: 'app_config_variable')]
public function getByKey(ConfigVariableRepository $repo, string $key): Response public function getByKey(ConfigVariableRepository $repo, string $key): Response
{ {
$var = $repo->findVariableByKey($key); $var = $repo->findVariableByKey($key);
+2 -2
View File
@@ -17,7 +17,7 @@ use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuild
class RoomCategoryController extends AbstractController class RoomCategoryController extends AbstractController
{ {
#[Route('/room/category', name: 'app_room_AllCategories')] #[Route('/api/room/category', name: 'app_room_AllCategories')]
public function getAll(RoomCategoryRepository $catRepo): Response public function getAll(RoomCategoryRepository $catRepo): Response
{ {
$catList = $catRepo->findAll(); $catList = $catRepo->findAll();
@@ -25,7 +25,7 @@ class RoomCategoryController extends AbstractController
return $this->json(["results" => $catList]); return $this->json(["results" => $catList]);
} }
#[Route('/room/category/{id}', name: 'app_room_category')] #[Route('/api/room/category/{id}', name: 'app_room_category')]
public function getById(RoomCategory $category): Response public function getById(RoomCategory $category): Response
{ {
return $this->json($category); return $this->json($category);
+14 -7
View File
@@ -13,7 +13,7 @@ use Symfony\Component\Routing\Attribute\Route;
class SearchRoomsController extends AbstractController class SearchRoomsController extends AbstractController
{ {
#[Route('/search/availability', name: 'app_search_availability')] #[Route('/api/search/availability', name: 'app_search_availability')]
public function getAvailabilityForPeriod(Request $request, RoomAvailabilityRepository $repoAvailability): Response public function getAvailabilityForPeriod(Request $request, RoomAvailabilityRepository $repoAvailability): Response
{ {
$payload = $request->toArray(); $payload = $request->toArray();
@@ -27,10 +27,10 @@ class SearchRoomsController extends AbstractController
$rs = $repoAvailability->getAvailabilityForPeriod($startDate, $endDate); $rs = $repoAvailability->getAvailabilityForPeriod($startDate, $endDate);
return $this->json($rs); return $this->json(["results" => $rs]);
} }
#[Route('/search/availabilityDetails', name: 'app_search_availabilityDetails')] #[Route('/api/search/availabilityDetails', name: 'app_search_availabilityDetails')]
public function getAvailabilityForPeriodDetails(Request $request, RoomAvailabilityRepository $repoAvailability): Response public function getAvailabilityForPeriodDetails(Request $request, RoomAvailabilityRepository $repoAvailability): Response
{ {
$payload = $request->toArray(); $payload = $request->toArray();
@@ -47,8 +47,8 @@ class SearchRoomsController extends AbstractController
return $this->json($rs); return $this->json($rs);
} }
#[Route('/search/fares', name: 'app_search_faresForCategory')] #[Route('/api/search/fares', name: 'app_search_faresForCategory')]
public function getFaresForCategoryOnPeriod(Request $request, RoomCategoryRepository $repoCat): Response public function getFaresForCategoryOnPeriod(Request $request, RoomCategoryRepository $repoCat, PricesService $priceService): Response
{ {
$payload = $request->toArray(); $payload = $request->toArray();
if (\is_null($payload)) { if (\is_null($payload)) {
@@ -57,7 +57,8 @@ class SearchRoomsController extends AbstractController
$filter = $payload["filter"]; $filter = $payload["filter"];
$catId = $filter["roomCategory"]; $catId = $filter["roomCategory"];
if ($repoCat->find($catId) === null) { $roomCategory = $repoCat->find($catId);
if ($roomCategory === null) {
throw new \Exception('Invalid Category', 400); throw new \Exception('Invalid Category', 400);
} }
@@ -69,7 +70,13 @@ class SearchRoomsController extends AbstractController
throw new \Exception('Invalid time interval', 400); throw new \Exception('Invalid time interval', 400);
} }
return $this->json(["prices" => $dailyPrices]); $fareList = $priceService->getFinalPricesForSearch($roomCategory, $startDate, $endDate);
$rs = [];
foreach ($fareList as $key => $value) {
$rs[] = [ "guestNumber" => $key, "price" => $value];
}
return $this->json(["results" => $rs]);
} }
#[Route('/search/debug', name: 'app_search_faresDebug')] #[Route('/search/debug', name: 'app_search_faresDebug')]
+149
View File
@@ -0,0 +1,149 @@
<?php
namespace App\Entity;
use App\Repository\BookingRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: BookingRepository::class)]
class Booking
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::DATE_MUTABLE)]
private ?\DateTimeInterface $ArrivalDate = null;
#[ORM\Column(type : Types::DATE_MUTABLE)]
private ?\DateTimeInterface $DepartureDate = null;
#[ORM\Column(type : Types::DATETIMETZ_MUTABLE)]
private ?\DateTimeInterface $BookingTime = null;
#[ORM\OneToOne(mappedBy : 'Booking', cascade: ['persist', 'remove'])]
private ?BookingCustomer $bookingCustomer = null;
/**
* @var Collection<int, BookingRoom>
*/
#[ORM\OneToMany(targetEntity: BookingRoom::class, mappedBy: 'Booking', orphanRemoval: true, cascade: ['persist'])]
private Collection $bookingRooms;
#[ORM\Column(length: 20)]
private ?string $refNumber = null;
public function __construct()
{
$this->bookingRooms = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getArrivalDate(): ?\DateTimeInterface
{
return $this->ArrivalDate;
}
public function setArrivalDate(\DateTimeInterface $ArrivalDate): static
{
$this->ArrivalDate = $ArrivalDate;
return $this;
}
public function getDepartureDate(): ?\DateTimeInterface
{
return $this->DepartureDate;
}
public function setDepartureDate(\DateTimeInterface $DepartureDate): static
{
$this->DepartureDate = $DepartureDate;
return $this;
}
public function getBookingTime(): ?\DateTimeInterface
{
return $this->BookingTime;
}
public function setBookingTime(\DateTimeInterface $BookingTime): static
{
$this->BookingTime = $BookingTime;
return $this;
}
public function getBookingCustomer(): ?BookingCustomer
{
return $this->bookingCustomer;
}
public function setBookingCustomer(BookingCustomer $bookingCustomer): static
{
// set the owning side of the relation if necessary
if ($bookingCustomer->getBooking() !== $this) {
$bookingCustomer->setBooking($this);
}
$this->bookingCustomer = $bookingCustomer;
return $this;
}
/**
* @return Collection<int, BookingRoom>
*/
public function getBookingRooms(): Collection
{
return $this->bookingRooms;
}
public function addBookingRoom(BookingRoom $bookingRoom): static
{
if (!$this->bookingRooms->contains($bookingRoom)) {
$this->bookingRooms->add($bookingRoom);
$bookingRoom->setBooking($this);
}
return $this;
}
public function removeBookingRoom(BookingRoom $bookingRoom): static
{
if ($this->bookingRooms->removeElement($bookingRoom)) {
// set the owning side to null (unless already changed)
if ($bookingRoom->getBooking() === $this) {
$bookingRoom->setBooking(null);
}
}
return $this;
}
public function getRefNumber(): ?string
{
return $this->refNumber;
}
public function setRefNumber(string $refNumber): static
{
$this->refNumber = $refNumber;
return $this;
}
function __toString(): string
{
return $this->getRefNumber();
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace App\Entity;
use App\Repository\BookingCustomerRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Ignore;
#[ORM\Entity(repositoryClass: BookingCustomerRepository::class)]
class BookingCustomer
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\OneToOne(inversedBy: 'bookingCustomer', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
#[Ignore]
private ?Booking $Booking = null;
#[ORM\Column(length: 64)]
private ?string $Name = null;
#[ORM\Column(length: 255)]
private ?string $Surname = null;
#[ORM\Column(length: 255)]
private ?string $Email = null;
#[ORM\Column(length: 255)]
private ?string $PhoneNumber = null;
public function getId(): ?int
{
return $this->id;
}
#[Ignore]
public function getBooking(): ?Booking
{
return $this->Booking;
}
#[Ignore]
public function setBooking(Booking $Booking): static
{
$this->Booking = $Booking;
return $this;
}
public function getName(): ?string
{
return $this->Name;
}
public function setName(string $Name): static
{
$this->Name = $Name;
return $this;
}
public function getSurname(): ?string
{
return $this->Surname;
}
public function setSurname(string $Surname): static
{
$this->Surname = $Surname;
return $this;
}
public function getEmail(): ?string
{
return $this->Email;
}
public function setEmail(string $Email): static
{
$this->Email = $Email;
return $this;
}
public function getPhoneNumber(): ?string
{
return $this->PhoneNumber;
}
public function setPhoneNumber(string $PhoneNumber): static
{
$this->PhoneNumber = $PhoneNumber;
return $this;
}
function __toString(): string
{
return $this->getName() . " " . $this->getSurname();
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace App\Entity;
use App\Repository\BookingRoomRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Ignore;
#[ORM\Entity(repositoryClass: BookingRoomRepository::class)]
class BookingRoom
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'bookingRooms')]
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
#[Ignore]
private ?Booking $Booking = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
#[Ignore]
private ?RoomCategory $RoomCategory = null;
#[ORM\Column]
private ?int $GuestNumber = null;
#[ORM\Column]
private ?int $RoomPrice = null;
#[ORM\Column]
private ?int $Amount = null;
public function getId(): ?int
{
return $this->id;
}
#[Ignore]
public function getBooking(): ?Booking
{
return $this->Booking;
}
#[Ignore]
public function setBooking(?Booking $Booking): static
{
$this->Booking = $Booking;
return $this;
}
#[Ignore]
public function getRoomCategory(): ?RoomCategory
{
return $this->RoomCategory;
}
#[Ignore]
public function setRoomCategory(?RoomCategory $RoomCategory): static
{
$this->RoomCategory = $RoomCategory;
return $this;
}
public function getRoomCategoryId(): int
{
return $this->getRoomCategory()->getId();
}
public function getGuestNumber(): ?int
{
return $this->GuestNumber;
}
public function setGuestNumber(int $GuestNumber): static
{
$this->GuestNumber = $GuestNumber;
return $this;
}
public function getRoomPrice(): ?int
{
return $this->RoomPrice;
}
public function setRoomPrice(int $RoomPrice): static
{
$this->RoomPrice = $RoomPrice;
return $this;
}
public function getAmount(): ?int
{
return $this->Amount;
}
public function setAmount(int $Amount): static
{
$this->Amount = $Amount;
return $this;
}
function __toString(): string
{
return $this->getAmount() . " " . $this->getRoomCategory()->getName() . " - " . $this->getGuestNumber() . " Guests";
}
}
+5 -1
View File
@@ -29,7 +29,7 @@ class RoomAvailability
public function setDay(\DateTimeInterface $day): static public function setDay(\DateTimeInterface $day): static
{ {
$this->day = $day->format("Ymd"); $this->day = $day->format("Y-m-d");
return $this; return $this;
} }
@@ -57,4 +57,8 @@ class RoomAvailability
return $this; return $this;
} }
public function modNumAvailable(int $diffAmount):void {
$this->numAvailable -= $diffAmount;
}
} }
@@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\BookingCustomer;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<BookingCustomer>
*
* @method BookingCustomer|null find($id, $lockMode = null, $lockVersion = null)
* @method BookingCustomer|null findOneBy(array $criteria, array $orderBy = null)
* @method BookingCustomer[] findAll()
* @method BookingCustomer[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class BookingCustomerRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, BookingCustomer::class);
}
// /**
// * @return BookingCustomer[] Returns an array of BookingCustomer objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('b.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?BookingCustomer
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\Booking;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Booking>
*
* @method Booking|null find($id, $lockMode = null, $lockVersion = null)
* @method Booking|null findOneBy(array $criteria, array $orderBy = null)
* @method Booking[] findAll()
* @method Booking[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class BookingRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Booking::class);
}
// /**
// * @return Booking[] Returns an array of Booking objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('b.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Booking
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\BookingRoom;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<BookingRoom>
*
* @method BookingRoom|null find($id, $lockMode = null, $lockVersion = null)
* @method BookingRoom|null findOneBy(array $criteria, array $orderBy = null)
* @method BookingRoom[] findAll()
* @method BookingRoom[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class BookingRoomRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, BookingRoom::class);
}
// /**
// * @return BookingRoom[] Returns an array of BookingRoom objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('b.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?BookingRoom
// {
// return $this->createQueryBuilder('b')
// ->andWhere('b.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
+3
View File
@@ -52,6 +52,9 @@ class FareTableRepository extends ServiceEntityRepository
public function getAllowedGuestNumberOnFareTableList(array $fareTableList): array public function getAllowedGuestNumberOnFareTableList(array $fareTableList): array
{ {
$fareTableCount = count($fareTableList); $fareTableCount = count($fareTableList);
if($fareTableCount == 0) {
return [];
}
$fareTableIdList = \array_map(function (FareTable $element) { $fareTableIdList = \array_map(function (FareTable $element) {
return $element->getId(); return $element->getId();
+108 -5
View File
@@ -3,6 +3,7 @@
namespace App\Repository; namespace App\Repository;
use App\Entity\RoomAvailability; use App\Entity\RoomAvailability;
use App\Entity\RoomCategory;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
@@ -34,7 +35,6 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->getResult(); ->getResult();
} }
/** /**
* @param \DateTimeInterface Period start (included in range). * @param \DateTimeInterface Period start (included in range).
* @param \DateTimeInterface Period end (not included in range). * @param \DateTimeInterface Period end (not included in range).
@@ -53,8 +53,30 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->setParameter('startDate', $strStartDate) ->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate) ->setParameter('endDate', $strEndDate)
->getQuery() ->getQuery()
->getArrayResult() ->getArrayResult();
; }
/**
* @param \DateTimeInterface Period start (included in range).
* @param \DateTimeInterface Period end (not included in range).
* @return int Returns number of available room of that category on selected period.
*/
public function getAvailabilityForCategoryInPeriod(RoomCategory $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate): int | null
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
$roomCatId = $roomCategory->getId();
return $this->createQueryBuilder('ra')
->select('MIN(ra.numAvailable) as availability')
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->andWhere('ra.roomCategory = :roomCategory')
->setParameter('roomCategory', $roomCatId)
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getSingleScalarResult();
} }
public function getAvailabilityForPeriodDetails(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array public function getAvailabilityForPeriodDetails(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array
@@ -68,7 +90,88 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->setParameter('startDate', $strStartDate) ->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate) ->setParameter('endDate', $strEndDate)
->getQuery() ->getQuery()
->getArrayResult() ->getArrayResult();
;
} }
/**
* Updates All Availability of selected roomCategory during period. It can operate in 2 modes, normal and diff mode.
* - Normal mode -> Sets value.
* - Diff mode -> apply value as sum ( currentAvailability += diff )
* NOTE: A Flush is needed after this operation.
*/
public function updateAvailabilityForRoomCategoryOnPeriod(RoomCategory $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate, int $amount, bool $isDiff = false): void
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
$roomCatId = $roomCategory->getId();
$availabilityItemList = $this->createQueryBuilder('ra')
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->andWhere('ra.roomCategory = :roomCategory')
->setParameter('roomCategory', $roomCatId)
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getResult();
$em = $this->getEntityManager();
foreach ($availabilityItemList as $item) {
if ($isDiff) {
$item->modNumAvailable($amount);
} else {
$item->setNumAvailable($amount);
}
}
}
/**
* For a given RoomCategory, sets its availability for all days on a period with the given amount.
*/
public function bulkEditRoomAvailabilityForCategoryOnPeriod(RoomCategory $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate, int $amount): void
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
$roomCatId = $roomCategory->getId();
$entityManager = $this->getEntityManager();
$entityManager->beginTransaction();
try {
//First, delete old data.
$this->createQueryBuilder('ra')
->delete()
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->andWhere('ra.roomCategory = :roomCategory')
->setParameter('roomCategory', $roomCatId)
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->execute();
$entityManager->flush();
//For each day, create item and persist it.
$daterange = new \DatePeriod($startDate, new \DateInterval('P1D'), $endDate);
foreach ($daterange as $date) {
$item = new RoomAvailability();
$item->setRoomCategory($roomCategory);
$item->setDay($date);
$item->setNumAvailable($amount);
$entityManager->persist($item);
}
$entityManager->flush();
$entityManager->commit();
} catch (\Throwable $th) {
$entityManager->rollback();
throw $th;
}
}
} }
+12
View File
@@ -35,6 +35,18 @@
"ref": "b131e6cbfe1b898a508987851963fff485986285" "ref": "b131e6cbfe1b898a508987851963fff485986285"
} }
}, },
"nelmio/cors-bundle": {
"version": "2.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.5",
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
},
"files": [
"./config/packages/nelmio_cors.yaml"
]
},
"symfony/console": { "symfony/console": {
"version": "7.0", "version": "7.0",
"recipe": { "recipe": {
@@ -0,0 +1,10 @@
{# templates/admin/room_availability/availability_day_edit.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Category - Room Availability - Edit' %}
{% block main %}
<div>
<h3>{{categoryId}}</h3>
</div>
{{ form(form) }}
{% endblock %}
@@ -0,0 +1,10 @@
{# templates/admin/room_availability/availability_day_edit.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Room Availability - Edit' %}
{% block main %}
<div>
<h3>{{availabilityData.day |date('d-m-Y')}}</h3>
</div>
{{ form(form) }}
{% endblock %}
@@ -1,4 +1,4 @@
{# templates/admin/room_availability/index.html.twig #} {# templates/admin/room_availability/category_view.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %} {% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Room Availability' %} {% block content_title 'Room Availability' %}
@@ -18,6 +18,11 @@
<tr> <tr>
<td>{{availability.day |date('d-m-Y')}}</td> <td>{{availability.day |date('d-m-Y')}}</td>
<td>{{availability.numAvailable}}</td> <td>{{availability.numAvailable}}</td>
<td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category_update', { day: availability.day |date('Y-m-d'), categoryId: categoryData.id }) }}">
Edit
</a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -6,19 +6,27 @@
<table class="datagrid"> <table class="datagrid">
<thead> <thead>
<tr> <tr>
<td>ID</td>
<td>Category</td> <td>Category</td>
<td>Details</td> <td>Details</td>
<td>Period update</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for category in roomCategoryList %} {% for category in roomCategoryList %}
<tr> <tr>
<td>{{category.id}}</td>
<td>{{category.name}}</td> <td>{{category.name}}</td>
<td> <td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category', { categoryId: category.id }) }}"> <a href="{{ ea_url().setRoute('admin_roomAvailability_category', { categoryId: category.id }) }}">
View Details View Details
</a> </a>
</td> </td>
<td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category_bulkupdate', { categoryId: category.id }) }}">
Update Period
</a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>