Author SHA1 Message Date
Daniel-Garmig 236cfe44e9 Generate Nightly Build - 202405132248 2024-05-13 22:47:54 +02:00
Daniel-Garmig 2fae2fb37f Setup - Create new setup endpoint to init DB and instance config. 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 2024-05-13 18:43:29 +02:00
Daniel-Garmig d6ad52c044 Update .gitea/workflows/pub-on-tag.yaml
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 2024-05-13 18:22:21 +02:00
Daniel-Garmig faea267b1f Add Gitea Actions Support. 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
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
Daniel-Garmig 15a884fafa PriceService + XDEBUG.
- Arreglados problemas que impedian el funcionamiento de xdebug para hacer debug.
- Correción errores PriceService.
2024-05-01 20:21:14 +02:00
Daniel-Garmig 9c8759e24d FareTable y RoomFares
- Implementación modelo tarifarios y tarifas.
- Implementación inicial servicio precios.
2024-05-01 18:07:49 +02:00
Daniel-Garmig 7ec989a76a FareTables and RoomFares - Implementación inicial
Creación del modelo y pantallas base para administración.
2024-04-29 23:14:28 +02:00
Daniel-Garmig 8e7bad7771 RoomAvailability - Initial implementation 2024-04-28 03:19:19 +02:00
Daniel-Garmig 7a56158563 Config Variables - Model & Easyadmin. 2024-04-27 18:46:35 +02:00
57 changed files with 2982 additions and 56 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="postgresql://app:[email protected]:54195/app?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ###
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###
+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}}
+1 -1
View File
@@ -10,7 +10,7 @@
"request": "launch",
"port": 9003,
"pathMappings": {
"/app" : "/src"
"/app" : "${workspaceFolder}"
}
},
{
+18 -3
View File
@@ -1,10 +1,10 @@
# InstantRooms
# SomniaRooms
A Hotel Room booking system.
Powered by:
- PHP Synfony.
- Easyadmin
- PHP Symfony.
- Easyadmin.
## Commands:
@@ -16,9 +16,24 @@ docker compose build --no-cache
Start Containers:
```
docker compose up --pull always -d --wait
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
```
+2
View File
@@ -14,12 +14,14 @@
"doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.1",
"easycorp/easyadmin-bundle": "^4.10",
"nelmio/cors-bundle": "^2.4",
"phpdocumentor/reflection-docblock": "^5.4",
"phpstan/phpdoc-parser": "^1.28",
"runtime/frankenphp-symfony": "^0.2.0",
"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
+69 -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": "e0a5e615a56df791d0b9a2dbbe533659",
"content-hash": "f177a4872c4ced09856f94d423de0b21",
"packages": [
{
"name": "doctrine/cache",
@@ -1320,6 +1320,68 @@
],
"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",
"version": "2.2.0",
@@ -3079,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": {
@@ -3155,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": [
{
@@ -3171,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",
+1
View File
@@ -9,4 +9,5 @@ return [
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::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]",
}
```
+54
View File
@@ -0,0 +1,54 @@
# SomniaRooms - App Configuration Variables.
This document contains a list of all config variables of SomniaRooms. Variables are stored in DB (table _`config_variable`_) and can be configured from admin panel.
Variables are divided in sections. Some sections are ony accesible from SomniaRoom, but others can be retrieved by anyone using config endpoint (_`/config`_).
## List of sections:
WIP
TODO -> Add default table with basic information to all sections.
### **Company**
Variables in this section contains information about the Hotel.
- SectionName -> company
- Availability -> public
- Nº variables -> X
**Variable list:**
- Name - Name of hotel.
### **Price Management**
Configure how backend services calculates prices for bookings.
- SectionName -> price_mng
- Availability -> internal
- Nº variables -> X
**Variable list:**
WIP
#### FinalPriceCalculation.
Establece cómo se deben tratar los precios de cada día para generar el precio final de la estancia.
Algunos algoritmos para estos serán:
- Precio más alto.
- Precio más bajo.
- Suma de precios. --> Por defecto.
- Media de precios.
#### MultipleRoomFareValid
Estrategia de selección de tarifa en caso de que haya varias válidas (mientras no exista un sistema de prioridad de tarifas...)
Posibles algoritmos:
- Precio más alto.
- Precio más bajo.
- Media.
+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
```
+4 -1
View File
@@ -4,4 +4,7 @@
; Add `start_with_request=yes` to start debug session on each request
xdebug.client_host=host.docker.internal
xdebug.start_with_request=yes
xdebug.mode=debug
;xdebug.mode=debug
xdebug.discover_client_host=1
xdebug.idekey=docker
;xdebug.log=/dev/stdout
+34
View File
@@ -0,0 +1,34 @@
<?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 Version20240427153125 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 config_variable_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE config_variable (id INT NOT NULL, key VARCHAR(32) NOT NULL, value VARCHAR(255) NOT NULL, section VARCHAR(64) DEFAULT NULL, PRIMARY KEY(id))');
}
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 config_variable_id_seq CASCADE');
$this->addSql('DROP TABLE config_variable');
}
}
+37
View File
@@ -0,0 +1,37 @@
<?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 Version20240427171934 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 TABLE room_availability (day DATE NOT NULL, room_category_id INT NOT NULL, num_available INT NOT NULL, PRIMARY KEY(day, room_category_id))');
$this->addSql('CREATE INDEX IDX_89C5BA2C67333DD ON room_availability (room_category_id)');
$this->addSql('ALTER TABLE room_availability ADD CONSTRAINT FK_89C5BA2C67333DD FOREIGN KEY (room_category_id) REFERENCES room_category (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE UNIQUE INDEX UNIQ_718569538A90ABA9 ON config_variable (key)');
}
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 room_availability DROP CONSTRAINT FK_89C5BA2C67333DD');
$this->addSql('DROP TABLE room_availability');
$this->addSql('DROP INDEX UNIQ_718569538A90ABA9');
}
}
+46
View File
@@ -0,0 +1,46 @@
<?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 Version20240429200025 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 fare_table_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE room_fare_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE fare_table (id INT NOT NULL, room_category_id INT NOT NULL, start_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_8E43BE7067333DD ON fare_table (room_category_id)');
$this->addSql('CREATE TABLE room_fare (id INT NOT NULL, fare_table_id INT NOT NULL, guest_number INT NOT NULL, fare_amount INT NOT NULL, day_type VARCHAR(2) DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_35C9B213A7233EF2 ON room_fare (fare_table_id)');
$this->addSql('ALTER TABLE fare_table ADD CONSTRAINT FK_8E43BE7067333DD FOREIGN KEY (room_category_id) REFERENCES room_category (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE room_fare ADD CONSTRAINT FK_35C9B213A7233EF2 FOREIGN KEY (fare_table_id) REFERENCES fare_table (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE room_availability ALTER day TYPE VARCHAR(255)');
}
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 fare_table_id_seq CASCADE');
$this->addSql('DROP SEQUENCE room_fare_id_seq CASCADE');
$this->addSql('ALTER TABLE fare_table DROP CONSTRAINT FK_8E43BE7067333DD');
$this->addSql('ALTER TABLE room_fare DROP CONSTRAINT FK_35C9B213A7233EF2');
$this->addSql('DROP TABLE fare_table');
$this->addSql('DROP TABLE room_fare');
$this->addSql('ALTER TABLE room_availability ALTER day TYPE DATE');
}
}
+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');
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Controller\Admin;
use App\Entity\ConfigVariable;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
class ConfigVariableCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return ConfigVariable::class;
}
public function configureFields(string $pageName): iterable
{
yield TextField::new('section');
yield TextField::new('key');
yield TextField::new('value');
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\{AssociationField, TextField, DateField};
use App\Entity\FareTable;
use App\Entity\RoomCategory;
use App\Controller\Admin\RoomCategoryCrudController;
class FareTableCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return FareTable::class;
}
public function configureFields(string $pageName): iterable
{
//$catRepo = $this->entityManager->getRepository(SomeEntity::class);
yield TextField::new('comment')->setLabel('Name');
yield AssociationField::new('roomCategory');
yield DateField::new('startDate');
yield DateField::new('endDate');
}
}
@@ -0,0 +1,147 @@
<?php
namespace App\Controller\Admin;
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;
#[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
{
$categoryList = $catRepo->findAll();
return $this->render('admin/room_availability/index.html.twig', [
'roomCategoryList' => $categoryList,
]);
}
// List RoomAvailability items from category.
#[Route('/admin/roomAvailability/{categoryId}', name: 'admin_roomAvailability_category')]
public function categoryAvailability(RoomAvailabilityRepository $repo, RoomCategoryRepository $catRepo, string $categoryId): Response
{
$category = $catRepo->find($categoryId);
$availabilityList = $repo->findByRoomCategory($categoryId);
return $this->render('admin/room_availability/category_view.html.twig', [
'categoryData' => $category,
'availabilityList' => $availabilityList,
]);
}
// 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
{
$avl = new RoomAvailability();
$avl->setDay(new \DateTime("2024-04-24"));
$avl->setRoomCategory($entityManager->find(RoomCategory::class, 4));
$avl->setNumAvailable(2);
$entityManager->persist($avl);
$entityManager->flush();
return $this->json($avl);
}
}
@@ -5,6 +5,7 @@ namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\Field;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
@@ -27,8 +28,9 @@ class RoomCategoryCrudController extends AbstractCrudController
public function configureFields(string $pageName): iterable
{
return [
//IdField::new('id'),
TextField::new('name'),
TextEditorField::new('description'),
TextField::new('description'),
TextField::new('bedType'),
IntegerField::new('maxGuestNum'),
CollectionField::new('roomCategoryDetails')->useEntryCrudForm(),
@@ -4,8 +4,6 @@ namespace App\Controller\Admin;
use App\Entity\RoomCategoryDetails;
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 RoomCategoryDetailsCrudController extends AbstractCrudController
@@ -20,7 +18,7 @@ class RoomCategoryDetailsCrudController extends AbstractCrudController
{
return [
TextField::new('detailsSection'),
TextEditorField::new('detailValue'),
TextField::new('detailValue'),
];
}
}
@@ -5,10 +5,7 @@ namespace App\Controller\Admin;
use App\Entity\RoomCategoryPhoto;
use App\Entity\RoomCategoryPhotoKinds;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\{TextField, ChoiceField};
class RoomCategoryPhotoCrudController extends AbstractCrudController
{
@@ -20,7 +17,7 @@ class RoomCategoryPhotoCrudController extends AbstractCrudController
public function configureFields(string $pageName): iterable
{
yield TextField::new('path');
yield TextEditorField::new('altText');
yield TextField::new('altText');
yield ChoiceField::new('kind')->setChoices(['Main' => 'main', 'Bathroom' => 'bathroom', 'Other' => 'other']);
}
@@ -0,0 +1,36 @@
<?php
namespace App\Controller\Admin;
use App\Entity\RoomFare;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
class RoomFareCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return RoomFare::class;
}
public function configureFields(string $pageName): iterable
{
$fieldFareTable = AssociationField::new ('fareTable')->setRequired(true);
if ($pageName == Crud::PAGE_EDIT) {
$fieldFareTable->setDisabled(true);
}
yield $fieldFareTable;
yield NumberField::new ('guestNumber');
yield MoneyField::new ('fareAmount')->setCurrency('EUR');
$dayTypeList = ["MONDAY" => 1, "TUESDAY" => 2, "WEDNESDAY" => 3, "THURSDAY" => 4, "FRIDAY" => 5, "SATURDAY" => 6, "SUNDAY" => 0];
yield ChoiceField::new('dayType')->setChoices($dayTypeList);
}
}
@@ -2,15 +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\User;
class SomniaDashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
@@ -29,10 +32,18 @@ class SomniaDashboardController extends AbstractDashboardController
{
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-list', 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-table', FareTable::class);
yield MenuItem::linkToCrud('Room Fares', 'fas fa-tag', RoomFare::class);
yield MenuItem::Section('App Management');
yield MenuItem::linkToCrud('Users', 'fas fa-user', User::class);
yield MenuItem::linkToCrud('Config Vars', 'fas fa-gear', ConfigVariable::class);
yield MenuItem::linkToCrud('Users', 'fas fa-user', User::class)
->setPermission("ROLE_SUPERADMIN");
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ class UserCrudController extends AbstractCrudController
yield TextField::new ('username');
yield TextField::new ('username')->onlyWhenUpdating()->setDisabled(true);
$availableRoles = ['User' => 'ROLE_USER', 'Admin' => 'ROLE_ADMIN', 'Test' => 'ROLE_TEST'];
$availableRoles = ['User' => 'ROLE_USER', 'Admin' => 'ROLE_ADMIN', 'Test' => 'ROLE_TEST', 'SuperAdmin' => 'ROLE_SUPERADMIN'];
yield ChoiceField::new ('roles')
->setChoices($availableRoles)
->allowMultipleChoices(true);
+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);
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use App\Repository\ConfigVariableRepository;
class ConfigVariableController extends AbstractController
{
#[Route('/api/config', name: 'app_config_variable')]
public function getAllVariables(ConfigVariableRepository $repo): Response
{
$varList = $repo->findAllVariables();
return $this->json(['results' => $varList]);
}
#[Route('/api/config/section', name: 'app_config_sections')]
public function getSections(ConfigVariableRepository $repo): Response
{
$sectionList = $repo->getAllSections();
$returnList = [];
foreach ($sectionList as $section) {
$sectionValue = $section["section"];
$returnList[] = $sectionValue;
}
return $this->json(['results' => $returnList]);
}
#[Route('/api/config/section/{section}', name: 'app_config_variableBySection')]
public function getVariablesBySection(ConfigVariableRepository $repo, string $section): Response
{
if(\is_null($section) || $section == '') {
return new Response("Invalid section value", 404);
}
$varList = $repo->findAllBySection($section);
return $this->json(['results' => $varList]);
}
#[Route('/api/config/var/{key}', name: 'app_config_variable')]
public function getByKey(ConfigVariableRepository $repo, string $key): Response
{
$var = $repo->findVariableByKey($key);
return $this->json($var);
}
}
+4 -4
View File
@@ -17,16 +17,16 @@ use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuild
class RoomCategoryController extends AbstractController
{
#[Route('/room/category', name: 'app_room_AllCategories')]
public function getAll(RoomCategoryRepository $catRepo, SerializerInterface $serializer): Response
#[Route('/api/room/category', name: 'app_room_AllCategories')]
public function getAll(RoomCategoryRepository $catRepo): Response
{
$catList = $catRepo->findAll();
return $this->json(["results" => $catList]);
}
#[Route('/room/category/{id}', name: 'app_room_category')]
public function getById(RoomCategory $category, SerializerInterface $serializer): Response
#[Route('/api/room/category/{id}', name: 'app_room_category')]
public function getById(RoomCategory $category): Response
{
return $this->json($category);
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace App\Controller;
use App\Repository\FareTableRepository;
use App\Repository\RoomAvailabilityRepository;
use App\Repository\RoomCategoryRepository;
use App\Service\PricesService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class SearchRoomsController extends AbstractController
{
#[Route('/api/search/availability', name: 'app_search_availability')]
public function getAvailabilityForPeriod(Request $request, RoomAvailabilityRepository $repoAvailability): Response
{
$payload = $request->toArray();
if (\is_null($payload)) {
throw new \Exception('Invalid Payload', 400);
}
$filter = $payload["filter"];
$startDate = new \DateTime($filter["startDate"]);
$endDate = new \DateTime($filter["endDate"]);
$rs = $repoAvailability->getAvailabilityForPeriod($startDate, $endDate);
return $this->json(["results" => $rs]);
}
#[Route('/api/search/availabilityDetails', name: 'app_search_availabilityDetails')]
public function getAvailabilityForPeriodDetails(Request $request, RoomAvailabilityRepository $repoAvailability): Response
{
$payload = $request->toArray();
if (\is_null($payload)) {
throw new \Exception('Invalid Payload', 400);
}
$filter = $payload["filter"];
$startDate = new \DateTime($filter["startDate"]);
$endDate = new \DateTime($filter["endDate"]);
$rs = $repoAvailability->getAvailabilityForPeriodDetails($startDate, $endDate);
return $this->json($rs);
}
#[Route('/api/search/fares', name: 'app_search_faresForCategory')]
public function getFaresForCategoryOnPeriod(Request $request, RoomCategoryRepository $repoCat, PricesService $priceService): Response
{
$payload = $request->toArray();
if (\is_null($payload)) {
throw new \Exception('Invalid Payload', 400);
}
$filter = $payload["filter"];
$catId = $filter["roomCategory"];
$roomCategory = $repoCat->find($catId);
if ($roomCategory === null) {
throw new \Exception('Invalid Category', 400);
}
$startDate = new \DateTime($filter["startDate"]);
$endDate = new \DateTime($filter["endDate"]);
$daysDiff = $startDate->diff($endDate)->days;
if (!$daysDiff || $startDate > $endDate) {
throw new \Exception('Invalid time interval', 400);
}
$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')]
public function faresDebug(Request $request, PricesService $priceService, FareTableRepository $repoFareTable, RoomCategoryRepository $repoCat): Response
{
$fareTable[] = $repoFareTable->find(1);
$fareTable[] = $repoFareTable->find(2);
$cat = $repoCat->find(4);
$startDate = new \DateTime("2024-04-27");
$endDate = new \DateTime("2024-05-02");
$rs = $priceService->getFinalPricesForSearch($cat, $startDate, $endDate);
return $this->json($rs);
}
}
+35 -9
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,12 +34,32 @@ 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
{
$setupStatus = $entityManager->find(ConfigVariable::class, "SETUP_STATUS");
if($setupStatus == null || $setupStatus == 1) {
return new Response("Setup was already done");
}
$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"]);
$user->setRoles(["ROLE_USER", "ROLE_ADMIN", "ROLE_SUPERADMIN"]);
$plaintextPassword = "admin";
// hash the password (based on the security.yaml config for the $user class)
@@ -51,8 +70,15 @@ class SecurityController extends AbstractController
$user->setPassword($hashedPassword);
$entityManager->persist($user);
$entityManager->flush();
return new Response("Created user - Admin");
$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";
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace App\Entity;
use App\Repository\ConfigVariableRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ConfigVariableRepository::class)]
class ConfigVariable
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 32, unique: true)]
private ?string $key = null;
#[ORM\Column(length: 255)]
private ?string $value = null;
#[ORM\Column(length: 64, nullable: true)]
private ?string $section = null;
public function getId(): ?int
{
return $this->id;
}
public function getKey(): ?string
{
return $this->key;
}
public function setKey(string $key): static
{
$this->key = $key;
return $this;
}
public function getValue(): ?string
{
return $this->value;
}
public function setValue(string $value): static
{
$this->value = $value;
return $this;
}
public function getSection(): ?string
{
return $this->section;
}
public function setSection(?string $section): static
{
$this->section = $section;
return $this;
}
}
+179
View File
@@ -0,0 +1,179 @@
<?php
namespace App\Entity;
use App\Repository\FareTableRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: FareTableRepository::class)]
class FareTable
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
private ?RoomCategory $roomCategory = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $startDate = null;
#[ORM\Column(type : Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $endDate = null;
#[ORM\Column(length : 255, nullable: true)]
private ?string $comment = null;
/**
* @var Collection<int, RoomFare>
*/
#[ORM\OneToMany(targetEntity: RoomFare::class, mappedBy: 'fareTable', orphanRemoval: true)]
private Collection $roomFares;
public function __construct()
{
$this->roomFares = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getRoomCategory(): ?RoomCategory
{
return $this->roomCategory;
}
public function setRoomCategory(?RoomCategory $roomCategory): static
{
$this->roomCategory = $roomCategory;
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->startDate;
}
public function setStartDate(\DateTimeInterface $startDate): static
{
$this->startDate = $startDate;
return $this;
}
public function getEndDate(): ?\DateTimeInterface
{
return $this->endDate;
}
public function setEndDate(?\DateTimeInterface $endDate): static
{
$this->endDate = $endDate;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): static
{
$this->comment = $comment;
return $this;
}
/**
* @return Collection<int, RoomFare>
*/
public function getRoomFares(): Collection
{
return $this->roomFares;
}
public function addRoomFare(RoomFare $roomFare): static
{
if (!$this->roomFares->contains($roomFare)) {
$this->roomFares->add($roomFare);
$roomFare->setFareTable($this);
}
return $this;
}
public function removeRoomFare(RoomFare $roomFare): static
{
if ($this->roomFares->removeElement($roomFare)) {
// set the owning side to null (unless already changed)
if ($roomFare->getFareTable() === $this) {
$roomFare->setFareTable(null);
}
}
return $this;
}
public function __toString(): string
{
if (!\is_null($this->comment)) {
return $this->comment;
}
return $this->id;
}
/**
* Get max guest number for current roomFares.
*/
public function getMaxGuestNumber(): int
{
$maxGuests = $this->getRoomFares()->reduce(function (int $acc, RoomFare $value): int {
return \max($acc, $value->getGuestNumber());
}, -1);
return $maxGuests;
}
/**
* Get min guest number for current roomFares.
*/
public function getMinGuestNumber(): int
{
$firstGuestNum = $this->getRoomFares()->first()->getGuestNumber() ?? 999;
$maxGuests = $this->getRoomFares()->reduce(function (int $acc, RoomFare $value): int {
return \min($acc, $value->getGuestNumber());
}, $firstGuestNum);
return $maxGuests;
}
/**
* Obtiene las tarifas que aplican al GuestNumber indicado.
* @param int $guestNumber GuestNumber para búsqueda.
* @return RoomFare[] Lista de tarifas.
*/
public function getRoomFaresByGuestNumber(int $guestNumber)
{
$roomFareList = $this->getRoomFares()->filter(function (RoomFare $element) use ($guestNumber) {
return $element->getGuestNumber() == $guestNumber;
});
return $roomFareList;
}
public function isActiveOnDate(\DateTimeInterface $date): bool
{
$active = ($this->startDate <= $date && $this->endDate > $date);
return $active;
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace App\Entity;
use App\Repository\RoomAvailabilityRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: RoomAvailabilityRepository::class)]
class RoomAvailability
{
#[ORM\Id]
#[ORM\Column]
private ?string $day = null;
#[ORM\Id]
#[ORM\ManyToOne(targetEntity: RoomCategory::class)]
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
private ?RoomCategory $roomCategory = null;
#[ORM\Column]
private ?int $numAvailable = null;
public function getDay(): ?\DateTimeInterface
{
return new \DateTime($this->day);
}
public function setDay(\DateTimeInterface $day): static
{
$this->day = $day->format("Y-m-d");
return $this;
}
public function getRoomCategory(): ?RoomCategory
{
return $this->roomCategory;
}
public function setRoomCategory(?RoomCategory $roomCategory): static
{
$this->roomCategory = $roomCategory;
return $this;
}
public function getNumAvailable(): ?int
{
return $this->numAvailable;
}
public function setNumAvailable(int $numAvailable): static
{
$this->numAvailable = $numAvailable;
return $this;
}
public function modNumAvailable(int $diffAmount):void {
$this->numAvailable -= $diffAmount;
}
}
+5 -1
View File
@@ -5,7 +5,6 @@ namespace App\Entity;
use App\Repository\RoomCategoryRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Annotation\Ignore;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
@@ -159,4 +158,9 @@ class RoomCategory
return $this;
}
public function __toString()
{
return $this->name;
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Entity;
use App\Repository\RoomFareRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: RoomFareRepository::class)]
class RoomFare
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'roomFares')]
#[ORM\JoinColumn(nullable: false)]
private ?FareTable $fareTable = null;
#[ORM\Column]
private ?int $guestNumber = null;
#[ORM\Column]
private ?int $fareAmount = null;
#[ORM\Column(length: 2, nullable: true)]
private ?string $dayType = null;
public function getId(): ?int
{
return $this->id;
}
public function getFareTable(): ?FareTable
{
return $this->fareTable;
}
public function setFareTable(?FareTable $fareTable): static
{
$this->fareTable = $fareTable;
return $this;
}
public function getGuestNumber(): ?int
{
return $this->guestNumber;
}
public function setGuestNumber(int $guestNumber): static
{
$this->guestNumber = $guestNumber;
return $this;
}
public function getFareAmount(): ?int
{
return $this->fareAmount;
}
public function setFareAmount(int $fareAmount): static
{
$this->fareAmount = $fareAmount;
return $this;
}
public function getDayType(): ?string
{
return $this->dayType;
}
public function setDayType(?string $dayType): static
{
$this->dayType = $dayType;
return $this;
}
}
+1 -1
View File
@@ -104,7 +104,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
}
public function getPlainPassword(): string
public function getPlainPassword(): ?string
{
return $this->plainPassword;
}
@@ -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()
// ;
// }
}
@@ -0,0 +1,76 @@
<?php
namespace App\Repository;
use App\Entity\ConfigVariable;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<ConfigVariable>
*
* @method ConfigVariable|null find($id, $lockMode = null, $lockVersion = null)
* @method ConfigVariable|null findOneBy(array $criteria, array $orderBy = null)
* @method ConfigVariable[] findAll()
* @method ConfigVariable[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ConfigVariableRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ConfigVariable::class);
}
/**
* Get all config variables for API.
*/
public function findAllVariables(): array
{
return $this->createQueryBuilder('c')
->select('c.key, c.value, c.section')
->getQuery()
->getResult();
}
/**
* @param string Config section.
* @return ConfigVariable[] Returns an array of ConfigVariable objects
*/
public function findAllBySection(string $section): array
{
return $this->createQueryBuilder('c')
->select('c.key, c.value')
->andWhere('c.section = :val')
->setParameter('val', $section)
->getQuery()
->getResult();
}
/**
* Get Config Variable by it's key.
* @param string Variable Key
* @return ConfigVariable|null Config Variable.
*/
public function findVariableByKey($varKey)
{
return $this->createQueryBuilder('c')
->select('c.key, c.value, c.section')
->andWhere('c.key = :val')
->setParameter('val', $varKey)
->getQuery()
->getOneOrNullResult();
}
/**
* Get all section names that contains config variables.
* @return string[] Returns an array of strings.
*/
public function getAllSections(): array
{
return $this->createQueryBuilder('c')
->select('c.section')
->distinct()
->getQuery()
->getResult();
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace App\Repository;
use App\Entity\FareTable;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<FareTable>
*
* @method FareTable|null find($id, $lockMode = null, $lockVersion = null)
* @method FareTable|null findOneBy(array $criteria, array $orderBy = null)
* @method FareTable[] findAll()
* @method FareTable[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class FareTableRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, FareTable::class);
}
/**
* Gets all fareTables for time period.
* @param integer|string ID Categoria.
* @param DateTimeInterface Period start date
* @param DateTimeInterface Period end date
* @return array Array with all faretables active in given period.
*/
public function findFareTable(int $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate): array
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
return $this->createQueryBuilder('f')
->andWhere('f.roomCategory = :category')
->andWhere('f.startDate < :endDate')
->andWhere('f.endDate >= :startDate')
->setParameter('category', $roomCategory)
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getResult();
}
/**
* Obtiene una lista con los números de huéspedes permitidos por todos los tarifarios pasados.
* @param FareTable[] $fareTableList Lista con los FareTable a consultar.
* @return int[] Lista con los números de huéspedes permitidos.
*/
public function getAllowedGuestNumberOnFareTableList(array $fareTableList): array
{
$fareTableCount = count($fareTableList);
if($fareTableCount == 0) {
return [];
}
$fareTableIdList = \array_map(function (FareTable $element) {
return $element->getId();
}, $fareTableList);
$strIdList = \implode(",", $fareTableIdList);
//Subquery -> Obtiene los números de huéspedes de las tarifas de un tarifario.
//Query -> Cuenta en cuantos tarifarios aparece cada número de huéspedes y queda con aquellos que aparecen en todos (having).
$sql = "SELECT guest_number
FROM (SELECT DISTINCT fare_table_id, guest_number FROM room_fare WHERE fare_table_id IN ($strIdList))
GROUP BY guest_number
HAVING COUNT(fare_table_id) = $fareTableCount";
$conn = $this->getEntityManager()->getConnection();
$rs = $conn->executeQuery($sql);
$allowedGuestNumbers = [];
while (true) {
$value = $rs->fetchOne();
if (!$value) {
break;
}
$allowedGuestNumbers[] = $value;
}
return $allowedGuestNumbers;
}
}
@@ -0,0 +1,177 @@
<?php
namespace App\Repository;
use App\Entity\RoomAvailability;
use App\Entity\RoomCategory;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<RoomAvailability>
*
* @method RoomAvailability|null find($id, $lockMode = null, $lockVersion = null)
* @method RoomAvailability|null findOneBy(array $criteria, array $orderBy = null)
* @method RoomAvailability[] findAll()
* @method RoomAvailability[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class RoomAvailabilityRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, RoomAvailability::class);
}
/**
* @return RoomAvailability[] Returns an array of RoomAvailability objects
*/
public function findByRoomCategory($categoryId): array
{
return $this->createQueryBuilder('a')
->andWhere('a.roomCategory = :val')
->setParameter('val', $categoryId)
->orderBy('a.day', 'ASC')
->getQuery()
->getResult();
}
/**
* @param \DateTimeInterface Period start (included in range).
* @param \DateTimeInterface Period end (not included in range).
* @return RoomAvailability[] Returns an array of RoomAvailability objects
*/
public function getAvailabilityForPeriod(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
return $this->createQueryBuilder('ra')
->select('IDENTITY(ra.roomCategory) as roomCategory, MIN(ra.numAvailable) as availability')
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->groupBy('ra.roomCategory')
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getArrayResult();
}
/**
* @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
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
return $this->createQueryBuilder('ra')
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getArrayResult();
}
/**
* 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;
}
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Repository;
use App\Entity\RoomFare;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<RoomFare>
*
* @method RoomFare|null find($id, $lockMode = null, $lockVersion = null)
* @method RoomFare|null findOneBy(array $criteria, array $orderBy = null)
* @method RoomFare[] findAll()
* @method RoomFare[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class RoomFareRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, RoomFare::class);
}
// /**
// * @return RoomFare[] Returns an array of RoomFare objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('r')
// ->andWhere('r.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('r.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?RoomFare
// {
// return $this->createQueryBuilder('r')
// ->andWhere('r.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
+157
View File
@@ -0,0 +1,157 @@
<?php
namespace App\Service;
use App\Entity\FareTable;
use App\Entity\RoomCategory;
use App\Repository\FareTableRepository;
use App\Repository\RoomFareRepository;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Servicio para calcular los precios de las estancias.
* En un futuro este servicio deberá admitir múltiples estrategias de cálculo de precios finales.
*/
class PricesService
{
public function __construct(
private FareTableRepository $repoFareTable,
private RoomFareRepository $repoRoomFares
) {
}
/**
* Calcula los precios de la estancia para la categoría y periodo indicados.
* Devolverá para cada numero de personas, el precio final de la estancia.
*
* e. g. --> [ 2 => 120€, 3 => 180€ ]
*
* @return array Contiene los precios en función del número de personas.
*/
public function getFinalPricesForSearch(RoomCategory $category, \DateTimeInterface $startDate, \DateTimeInterface $endDate): array
{
$daysDiff = $startDate->diff($endDate)->days;
if (!$daysDiff) {
return "Invalid date period :( ";
}
$fareTablesForPeriod = $this->repoFareTable->findFareTable($category->getId(), $startDate, $endDate);
//Obtenemos el número de personas permitido por todos los tarifarios activos.
$allowedGuestNumbers = $this->repoFareTable->getAllowedGuestNumberOnFareTableList($fareTablesForPeriod);
$rs = [];
//Calculamos los precios para cada GuestNumber permitido.
foreach ($allowedGuestNumbers as $currentGuestNumber) {
//Calculamos el precio de cada día.
// e.g. --> [ "2024-04-30" => 20, "2024-05-01" => 28, "2024-05-02" => 20, ...].
$dailyPrices = $this->getDailyPricesForSearch($startDate, $endDate, $currentGuestNumber, $fareTablesForPeriod);
$rs[$currentGuestNumber] = $dailyPrices;
}
//Aplicamos la estrategia de precios configurada para calcular el precio final de la estancia.
// TODOME: Crear y configurar diversas estrategias de precios -> Por el momento utilizamos "Suma del precio de cada día".
$finalResult = [];
foreach ($rs as $guestNum => $priceArray) {
$finalAmount = \array_reduce($priceArray, function ($acc, $element) {
return $acc + $element;
});
$finalResult[$guestNum] = $finalAmount;
}
return $finalResult;
}
/**
* Dado un número de huéspedes, calcula el precio de cada día del periodo indicado.
* @param \DateTimeInterface $startDate Fecha de inicio del periodo.
* @param \DateTimeInterface $endDate Fecha fin del periodo.
* @param int $guestNumber Número de huéspedes para los que calcular los precios.
* @param array $fareTableList Array con los tarifarios que aplican en el periodo indicado.
* @return array Devuelve un array con los precios: e.g. --> [ "2024-04-30" => 2500, "2024-05-01" => 3500, "2024-05-02" => 3000, ...].
*/
public function getDailyPricesForSearch(\DateTimeInterface $startDate, \DateTimeInterface $endDate, int $guestNumber, array $fareTableList): array
{
$daysDiff = $startDate->diff($endDate)->days;
$dailyPrices = [];
$currentDate = new \DateTime($startDate->format("Y-m-d")); //TODOME: Comprobar si es tipo referencia y está cambiando.
for ($i = 0; $i < $daysDiff; $i++) {
$strCurrentDate = $currentDate->format("Y-m-d");
//Obtenemos el tarifario que aplica --> TODOME: Buscar si se puede cachear esto, para no recarlcularlo para cada GuestNumber.
$currentFareTableList = \array_filter($fareTableList, function (FareTable $element) use ($currentDate) {
return $element->isActiveOnDate($currentDate);
});
if (count($currentFareTableList) != 1) {
$dailyPrices[$strCurrentDate] = "ERROR - Number of FareTable found: " . count($currentFareTableList);
continue;
}
$currentFareTable = array_values($currentFareTableList)[0];
//TODOME: -> Obtener precio específico (RoomPrices).
//Si no hay precio, calcular mediante tarifarios.
$roomFares = $currentFareTable->getRoomFaresByGuestNumber($guestNumber);
if (count($roomFares) < 1) {
$dailyPrices[$strCurrentDate] = "ERROR - Invalid RoomFares found";
continue;
}
//Si sólo hay 1 tarifa, aplico esa, sino calculo la apropiada según condiciones.
$fareAmount = null;
if (count($roomFares) == 1) {
$fareAmount = $roomFares->first()->getFareAmount();
} else {
$fareAmount = $this->calculateFareApplyingConditions($roomFares, $currentDate);
}
$dailyPrices[$strCurrentDate] = $fareAmount;
//Add 1 day to current date.
$currentDate->add(new \DateInterval("P1D"));
}
return $dailyPrices;
}
/**
* Calcula el precio a aplicar según las condiciones de las tarifas.
* @param RoomFare[] $roomFareList Lista con las posibles tarifas.
* @param DateTimeInterface $fareCondDate Fecha utilizada para las condiciones.
*
*/
public function calculateFareApplyingConditions(ArrayCollection $roomFareList, \DateTimeInterface $fareCondDate): int
{
$currentDayOfWeek = $fareCondDate->format("w");
//Comprobamos si a alguna de las tarifas se la aplica la condición de "DayType" y coincide con el actual.
// Si es así, devolvemos su valor.
foreach ($roomFareList as $roomFare) {
$dayType = $roomFare->getDayType();
if ($dayType == null || $dayType == "") {
continue;
}
if($dayType == $currentDayOfWeek) {
return $roomFare->getFareAmount();
}
}
//TODOME: Si existen múltiples tarifas posibles, debería haber una estrategia para elegir una u otra. Variable de configuración para esto.
$faresWithoutConditions = $roomFareList->filter(function ($element) {
return $element->getDayType() == null;
});
return $faresWithoutConditions->first()->getFareAmount();
}
}
+12
View File
@@ -35,6 +35,18 @@
"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": {
"version": "7.0",
"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 %}
@@ -0,0 +1,30 @@
{# templates/admin/room_availability/category_view.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Room Availability' %}
{% block main %}
<div>
<h3>{{categoryData.name}}</h3>
</div>
<table class="datagrid">
<thead>
<tr>
<td>Day</td>
<td>Amount</td>
</tr>
</thead>
<tbody>
{% for availability in availabilityList %}
<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>
</table>
{% endblock %}
@@ -0,0 +1,34 @@
{# templates/admin/room_availability/index.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Room Availability' %}
{% block main %}
<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>
</table>
{% endblock %}