Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
236cfe44e9 | ||
|
|
2fae2fb37f | ||
|
|
901454df3c | ||
|
|
4abe778657 | ||
|
|
d6ad52c044 | ||
|
|
72a9f69799 | ||
|
|
faea267b1f | ||
|
|
e8bfcd9c6b | ||
|
|
ed4ba9d82f |
@@ -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]
|
||||
@@ -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}}
|
||||
@@ -23,3 +23,17 @@ 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
|
||||
```
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
+198
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -33,13 +33,13 @@ class SomniaDashboardController extends AbstractDashboardController
|
||||
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
|
||||
|
||||
yield MenuItem::Section('Booking Management');
|
||||
yield MenuItem::linkToCrud('Bookings', 'fas fa-door-open', Booking::class);
|
||||
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);
|
||||
|
||||
@@ -7,6 +7,8 @@ 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;
|
||||
@@ -41,13 +43,16 @@ class BookingController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/api/booking/create', name: 'app_booking_create')]
|
||||
public function newBooking(Request $request, BookingRepository $repoBooking, EntityManagerInterface $entityManager, RoomCategoryRepository $repoCat): JsonResponse
|
||||
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"];
|
||||
@@ -65,11 +70,17 @@ class BookingController extends AbstractController
|
||||
$currentTime = new \DateTime();
|
||||
|
||||
// Create booking from request data.
|
||||
//$booking = new Booking();
|
||||
$booking->setBookingTime($currentTime);
|
||||
$booking->setArrivalDate($arrivalDate);
|
||||
$booking->setDepartureDate($departureDate);
|
||||
$booking->setRefNumber($currentTime->format("Y") . "001");
|
||||
|
||||
$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);
|
||||
|
||||
@@ -89,22 +100,50 @@ class BookingController extends AbstractController
|
||||
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) {
|
||||
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($roomItem["guestNumber"]);
|
||||
$room->setAmount($roomItem["amount"]);
|
||||
$room->setRoomPrice(5000);
|
||||
$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();
|
||||
|
||||
@@ -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 :)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user