Author SHA1 Message Date
Daniel-Garmig 236cfe44e9 Generate Nightly Build - 202405132248
Publish_on_Tag / Build and Push (push) Successful in 14m25s
2024-05-13 22:47:54 +02:00
Daniel-Garmig 2fae2fb37f Setup - Create new setup endpoint to init DB and instance config.
CI / Docker Lint (push) Successful in 30s
2024-05-13 22:43:19 +02:00
Daniel-Garmig 901454df3c Docs - Add deploy docs. 2024-05-13 22:37:39 +02:00
Daniel-Garmig 4abe778657 Generate Nightly Build - 202405131843
Publish_on_Tag / Build and Push (push) Successful in 14m51s
2024-05-13 18:43:29 +02:00
Daniel-Garmig d6ad52c044 Update .gitea/workflows/pub-on-tag.yaml
CI / Docker Lint (push) Successful in 22s
Fix error on variable "repository_owner" being uppercase for docker image tag.
2024-05-13 18:40:37 +02:00
Daniel-Garmig 72a9f69799 Generate Nightly Build - 202405131822
Publish_on_Tag / Build and Push (push) Failing after 1m17s
2024-05-13 18:22:21 +02:00
Daniel-Garmig faea267b1f Add Gitea Actions Support.
CI / Docker Lint (push) Successful in 17s
2024-05-13 18:14:32 +02:00
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
28 changed files with 1524 additions and 51 deletions
+23
View File
@@ -0,0 +1,23 @@
name: CI
on:
push:
branches:
- main
pull_request: ~
workflow_dispatch: ~
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint:
name: Docker Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Lint Dockerfile
uses: hadolint/[email protected]
+45
View File
@@ -0,0 +1,45 @@
name: Publish_on_Tag
on:
push:
tags:
- devbuild_*
workflow_dispatch: ~
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
name: Build and Push
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to local registry
uses: docker/login-action@v3
with:
registry: ${{vars.REGISTRY_INSTANCE}}
username: ${{gitea.repository_owner}}
password: ${{secrets.REGISTRY_TOKEN}} # It should be a user/org token with package perms.
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{vars.REGISTRY_INSTANCE}}/somnia/somniarooms:devbuild
target: frankenphp_prod
platforms: linux/amd64,linux/arm64
secrets: |
GIT_AUTH_TOKEN=${{secrets.REGISTRY_TOKEN}}
+15 -1
View File
@@ -22,4 +22,18 @@ docker compose --env-file .\.env.local up -d --wait
Stop containers
```
docker compose down --remove-orphans
```
```
### Prod:
You can use docker compose for prod or build and deploy a prod image.
**Build Prod image**
```
docker build -t somniarooms:prod --target frankenphp_prod .
```
**Deploy prod image**
```
docker run -d -it -p 80:80 -p 443:443 --name somniarooms --env-file ./env.prod.local somniarooms:prod
```
+1
View File
@@ -21,6 +21,7 @@
"symfony/console": "7.0.*",
"symfony/dotenv": "7.0.*",
"symfony/flex": "^2",
"symfony/form": "7.0.*",
"symfony/framework-bundle": "7.0.*",
"symfony/property-access": "7.0.*",
"symfony/property-info": "7.0.*",
Generated
+7 -7
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0422b42b049fb3382babd77edbaac205",
"content-hash": "f177a4872c4ced09856f94d423de0b21",
"packages": [
{
"name": "doctrine/cache",
@@ -3141,16 +3141,16 @@
},
{
"name": "symfony/form",
"version": "v7.0.6",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
"reference": "d5db6599775a563792391d0045decc240e7ebd1e"
"reference": "b4df6a399a2b03782a0163807239db342659f54f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/d5db6599775a563792391d0045decc240e7ebd1e",
"reference": "d5db6599775a563792391d0045decc240e7ebd1e",
"url": "https://api.github.com/repos/symfony/form/zipball/b4df6a399a2b03782a0163807239db342659f54f",
"reference": "b4df6a399a2b03782a0163807239db342659f54f",
"shasum": ""
},
"require": {
@@ -3217,7 +3217,7 @@
"description": "Allows to easily create, process and reuse HTML forms",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/form/tree/v7.0.6"
"source": "https://github.com/symfony/form/tree/v7.0.7"
},
"funding": [
{
@@ -3233,7 +3233,7 @@
"type": "tidelift"
}
],
"time": "2024-03-28T09:20:36+00:00"
"time": "2024-04-28T11:44:19+00:00"
},
{
"name": "symfony/framework-bundle",
+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]",
}
```
+198
View File
@@ -0,0 +1,198 @@
# SomniaRooms - How to deploy.
SomniaRooms is designed to be easy to deploy and use.
Production images for Docker Containers are available from Gitea Registry.
Documentation on how to deploy them as containers can be found at [Readme](../README.md).
## Docker compose examples.
Here are some deploy examples using docker compose:
SomniaRooms back-end and front-end can run on the same machine, but some config is needed to work.
### Deploy back-end and front-end on different servers (different ip address).
If your servers have different IP addresses everything is a little bit easier. You can set-up subdomains for each one.
Use the following docker compose files for back-end and front-end.
Back-end:
```yml
services:
somniarooms-back:
image: gitea.uberelectronnetwork.cc/somnia/somniarooms:devbuild
restart: unless-stopped
environment:
SERVER_NAME: https://api.somnia.dev # <-- Your hostname here!
DATABASE_URL: postgresql://somnia:ChangeMe!@database/somniarooms?serverVersion=16&ch> MERCURE_PUBLISHER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
MERCURE_SUBSCRIBER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
APP_SECRET: ChangeMySecret
FRANKENPHP_CONFIG: "" # <-- Dont touch this line unless you know what are doing.
volumes:
- caddy_data:/data
- caddy_config:/config
ports:
# HTTP
- target: 80
published: 80
protocol: tcp
# HTTPS
- target: 443
published: 443
protocol: tcp
# HTTP/3
- target: 443
published: 443
protocol: udp
depends_on:
- database
database:
image: postgres:16-alpine
environment:
POSTGRES_DB: somniarooms
# You should definitely change the password in production
POSTGRES_PASSWORD: ChangeMe!
POSTGRES_USER: somnia
healthcheck:
test: ["CMD", "pg_isready"]
timeout: 5s
retries: 5
start_period: 60s
volumes:
- database_data:/var/lib/postgresql/data:rw
# You may use a bind-mounted host directory instead, so that it is harder to acciden> # - ./docker/db/data:/var/lib/postgresql/data:rw
volumes:
caddy_data:
caddy_config:
database_data:
```
Config details
- SERVER_NAME -> if set to https a SSL cert will be obtained. If you set http, SSL will be disabled.
Front-end:
```yml
services:
somniarooms-front:
image: gitea.uberelectronnetwork.cc/somnia/somniaroomsapp:devbuild
restart: unless-stopped
environment:
# You should set your public IP/hostname to back-end.
SOMNIAROOMS_BACKEND_HOST: https://api.somnia.dev # <-- Your back-end hostname here!
SOMNIAROOMS_BACKEND_PORT: 443 # <-- Your back-end port here! (443 if using https)
ports:
# HTTP
- target: 80
published: 80
# HTTPS
- target: 443
published: 443
```
### Deploy on the same server.
Right now, it's quite difficult to set back-end app on a port different from 80/443. Back-end uses Caddy as webserver and automatically try to get SSL certs from let's encrypt.
My go-to option will be using some reverse-proxy to redirect requests to back or front by domain name.
This is an example of deployment using Traefik.
```yml
services:
reverse-proxy:
# The official v3 Traefik docker image
image: traefik:v3.0
restart: unless-stopped
# Enables the web UI and tells Traefik to listen to docker
command:
- --api.insecure=true
- --providers.docker
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --certificatesresolvers.lets-encrypt.acme.tlschallenge=true
- --certificatesresolvers.lets-encrypt.acme.email=your_email # <-- Your email here!
- --certificatesresolvers.lets-encrypt.acme.storage=/letsencrypt/acme.json
ports:
# The HTTP port
- "80:80"
# The HTTPS port
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
# acme.json should be created on host instance
- .certs/:/letsencrypt/
somniarooms-back:
image: gitea.uberelectronnetwork.cc/somnia/somniarooms:devbuild
restart: unless-stopped
environment:
SERVER_NAME: https://api.somnia.dev # <-- Your hostname here!
DATABASE_URL: postgresql://somnia:ChangeMe!@database/somniarooms?serverVersion=16&charset=utf8
MERCURE_PUBLISHER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
MERCURE_SUBSCRIBER_JWT_KEY: ChangeThisMercureHubJWTSecretKey!
APP_SECRET: ChangeMySecret
FRANKENPHP_CONFIG: "" # <-- Dont touch this line unless you know what are doing.
volumes:
- caddy_data:/data
- caddy_config:/config
depends_on:
- database
labels:
# HTTPS YOUR APP
- "traefik.enable=true"
- "traefik.http.routers.somniarooms-back.rule=Host(`api.somnia.dev`)" # <-- Your back-end hostname here!
- "traefik.http.routers.somniarooms-back.entrypoints=websecure"
- "traefik.http.routers.somniarooms-back.tls=true"
- "traefik.http.routers.somniarooms-back.tls.certresolver=lets-encrypt"
somniarooms-front:
image: gitea.uberelectronnetwork.cc/somnia/somniaroomsapp:devbuild
restart: unless-stopped
environment:
# You should set your public IP/hostname to back-end.
SOMNIAROOMS_BACKEND_HOST: https://api.somnia.dev # <-- Your back-end hostname here!
SOMNIAROOMS_BACKEND_PORT: 443 # <-- Your back-end port here! (443 if using https)
depends_on:
- somniarooms-back
labels:
- "traefik.http.routers.somniarooms-front.rule=Host(`app.somnia.dev`)" # <-- Your front-end hostname here!
database:
image: postgres:16-alpine
environment:
POSTGRES_DB: somniarooms
# You should definitely change the password in production
POSTGRES_PASSWORD: ChangeMe!
POSTGRES_USER: somnia
healthcheck:
test: ["CMD", "pg_isready"]
timeout: 5s
retries: 5
start_period: 60s
volumes:
- database_data:/var/lib/postgresql/data:rw
# You may use a bind-mounted host directory instead, so that it is harder to acciden> # - ./docker/db/data:/var/lib/postgresql/data:rw
volumes:
caddy_data:
caddy_config:
database_data:
```
## What's next?
Remember you can run your docker compose files using:
```
docker compose up -d --wait
```
+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;
use App\Repository\RoomAvailabilityRepository;
use App\Repository\RoomCategoryRepository;
use App\Entity\RoomAvailability;
use App\Entity\RoomCategory;
use App\Repository\RoomAvailabilityRepository;
use App\Repository\RoomCategoryRepository;
use Doctrine\ORM\EntityManagerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
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\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField, DateField, AssociationField, NumberField};
#[IsGranted("ROLE_ADMIN")]
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')]
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')]
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')]
public function availabilityTest(EntityManagerInterface $entityManager): Response
{
@@ -2,20 +2,18 @@
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\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
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
{
#[Route('/admin', name: 'admin')]
@@ -26,19 +24,22 @@ class SomniaDashboardController extends AbstractDashboardController
public function configureDashboard(): Dashboard
{
return Dashboard::new()
return Dashboard::new ()
->setTitle('SomniaRooms');
}
public function configureMenuItems(): iterable
{
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::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::linkToCrud('Fare Tables', 'fas fa-door-open', FareTable::class);
yield MenuItem::linkToCrud('Room Fares', 'fas fa-door-open', RoomFare::class);
yield MenuItem::linkToCrud('Fare Tables', 'fas fa-table', FareTable::class);
yield MenuItem::linkToCrud('Room Fares', 'fas fa-tag', RoomFare::class);
yield MenuItem::Section('App Management');
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);
}
}
+45 -19
View File
@@ -2,15 +2,14 @@
namespace App\Controller;
use App\Entity\User;
use App\Entity\ConfigVariable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
use App\Repository\UserRepository;
class SecurityController extends AbstractController
{
@@ -35,24 +34,51 @@ class SecurityController extends AbstractController
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
#[Route(path: '/addAdmin', name: 'app_security_addAdmin')]
#[Route(path: '/setup', name: 'app_security_addAdmin')]
public function addAdmin(UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $entityManager): Response
{
$user = new User();
$user->setUsername("admin");
$user->setRoles(["ROLE_USER", "ROLE_ADMIN"]);
$plaintextPassword = "admin";
$setupStatus = $entityManager->find(ConfigVariable::class, "SETUP_STATUS");
// hash the password (based on the security.yaml config for the $user class)
$hashedPassword = $passwordHasher->hashPassword(
$user,
$plaintextPassword
);
$user->setPassword($hashedPassword);
if($setupStatus == null || $setupStatus == 1) {
return new Response("Setup was already done");
}
$entityManager->persist($user);
$entityManager->flush();
return new Response("Created user - Admin");
$entityManager->beginTransaction();
try {
//Create system variables.
$var_setup = new ConfigVariable();
$var_setup->setKey("SETUP_STATUS");
$var_setup->setValue("1");
$var_setup->setSection("SYSTEM");
$entityManager->persist($var_setup);
//Create user Admin.
$user = new User();
$user->setUsername("admin");
$user->setRoles(["ROLE_USER", "ROLE_ADMIN", "ROLE_SUPERADMIN"]);
$plaintextPassword = "admin";
// hash the password (based on the security.yaml config for the $user class)
$hashedPassword = $passwordHasher->hashPassword(
$user,
$plaintextPassword
);
$user->setPassword($hashedPassword);
$entityManager->persist($user);
$entityManager->flush();
$entityManager->commit();
} catch (\Throwable $th) {
$entityManager->rollback();
throw $th;
}
return new Response("Setup completed :)");
}
}
+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
{
$this->day = $day->format("Ymd");
$this->day = $day->format("Y-m-d");
return $this;
}
@@ -57,4 +57,8 @@ class RoomAvailability
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()
// ;
// }
}
+108 -5
View File
@@ -3,6 +3,7 @@
namespace App\Repository;
use App\Entity\RoomAvailability;
use App\Entity\RoomCategory;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -34,7 +35,6 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->getResult();
}
/**
* @param \DateTimeInterface Period start (included in range).
* @param \DateTimeInterface Period end (not included in range).
@@ -53,8 +53,30 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->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
@@ -68,7 +90,88 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->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;
}
}
}
@@ -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' %}
{% block content_title 'Room Availability' %}
@@ -18,6 +18,11 @@
<tr>
<td>{{availability.day |date('d-m-Y')}}</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>
{% endfor %}
</tbody>
@@ -6,19 +6,27 @@
<table class="datagrid">
<thead>
<tr>
<td>ID</td>
<td>Category</td>
<td>Details</td>
<td>Period update</td>
</tr>
</thead>
<tbody>
{% for category in roomCategoryList %}
<tr>
<td>{{category.id}}</td>
<td>{{category.name}}</td>
<td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category', { categoryId: category.id }) }}">
View Details
</a>
</td>
<td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category_bulkupdate', { categoryId: category.id }) }}">
Update Period
</a>
</td>
</tr>
{% endfor %}
</tbody>