Author SHA1 Message Date
Daniel-Garmig 236cfe44e9 Generate Nightly Build - 202405132248
Publish_on_Tag / Build and Push (push) Successful in 14m25s
2024-05-13 22:47:54 +02:00
Daniel-Garmig 2fae2fb37f Setup - Create new setup endpoint to init DB and instance config.
CI / Docker Lint (push) Successful in 30s
2024-05-13 22:43:19 +02:00
Daniel-Garmig 901454df3c Docs - Add deploy docs. 2024-05-13 22:37:39 +02:00
Daniel-Garmig 4abe778657 Generate Nightly Build - 202405131843
Publish_on_Tag / Build and Push (push) Successful in 14m51s
2024-05-13 18:43:29 +02:00
Daniel-Garmig d6ad52c044 Update .gitea/workflows/pub-on-tag.yaml
CI / Docker Lint (push) Successful in 22s
Fix error on variable "repository_owner" being uppercase for docker image tag.
2024-05-13 18:40:37 +02:00
Daniel-Garmig 72a9f69799 Generate Nightly Build - 202405131822
Publish_on_Tag / Build and Push (push) Failing after 1m17s
2024-05-13 18:22:21 +02:00
Daniel-Garmig faea267b1f Add Gitea Actions Support.
CI / Docker Lint (push) Successful in 17s
2024-05-13 18:14:32 +02:00
Daniel-Garmig e8bfcd9c6b Room Availability - Edit + bulk edit 2024-05-11 00:45:35 +02:00
Daniel-Garmig ed4ba9d82f Booking Creation - Finish initial implement.
- Check and update availability.
- Set prices.
- Generate booking refNumber.
2024-05-10 21:31:24 +02:00
16 changed files with 625 additions and 49 deletions
+23
View File
@@ -0,0 +1,23 @@
name: CI
on:
push:
branches:
- main
pull_request: ~
workflow_dispatch: ~
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint:
name: Docker Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Lint Dockerfile
uses: hadolint/[email protected]
+45
View File
@@ -0,0 +1,45 @@
name: Publish_on_Tag
on:
push:
tags:
- devbuild_*
workflow_dispatch: ~
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
name: Build and Push
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to local registry
uses: docker/login-action@v3
with:
registry: ${{vars.REGISTRY_INSTANCE}}
username: ${{gitea.repository_owner}}
password: ${{secrets.REGISTRY_TOKEN}} # It should be a user/org token with package perms.
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{vars.REGISTRY_INSTANCE}}/somnia/somniarooms:devbuild
target: frankenphp_prod
platforms: linux/amd64,linux/arm64
secrets: |
GIT_AUTH_TOKEN=${{secrets.REGISTRY_TOKEN}}
+15 -1
View File
@@ -22,4 +22,18 @@ docker compose --env-file .\.env.local up -d --wait
Stop containers
```
docker compose down --remove-orphans
```
```
### Prod:
You can use docker compose for prod or build and deploy a prod image.
**Build Prod image**
```
docker build -t somniarooms:prod --target frankenphp_prod .
```
**Deploy prod image**
```
docker run -d -it -p 80:80 -p 443:443 --name somniarooms --env-file ./env.prod.local somniarooms:prod
```
+1
View File
@@ -21,6 +21,7 @@
"symfony/console": "7.0.*",
"symfony/dotenv": "7.0.*",
"symfony/flex": "^2",
"symfony/form": "7.0.*",
"symfony/framework-bundle": "7.0.*",
"symfony/property-access": "7.0.*",
"symfony/property-info": "7.0.*",
Generated
+7 -7
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0422b42b049fb3382babd77edbaac205",
"content-hash": "f177a4872c4ced09856f94d423de0b21",
"packages": [
{
"name": "doctrine/cache",
@@ -3141,16 +3141,16 @@
},
{
"name": "symfony/form",
"version": "v7.0.6",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
"reference": "d5db6599775a563792391d0045decc240e7ebd1e"
"reference": "b4df6a399a2b03782a0163807239db342659f54f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/d5db6599775a563792391d0045decc240e7ebd1e",
"reference": "d5db6599775a563792391d0045decc240e7ebd1e",
"url": "https://api.github.com/repos/symfony/form/zipball/b4df6a399a2b03782a0163807239db342659f54f",
"reference": "b4df6a399a2b03782a0163807239db342659f54f",
"shasum": ""
},
"require": {
@@ -3217,7 +3217,7 @@
"description": "Allows to easily create, process and reuse HTML forms",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/form/tree/v7.0.6"
"source": "https://github.com/symfony/form/tree/v7.0.7"
},
"funding": [
{
@@ -3233,7 +3233,7 @@
"type": "tidelift"
}
],
"time": "2024-03-28T09:20:36+00:00"
"time": "2024-04-28T11:44:19+00:00"
},
{
"name": "symfony/framework-bundle",
+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
```
@@ -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);
+46 -7
View File
@@ -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();
+45 -19
View File
@@ -2,15 +2,14 @@
namespace App\Controller;
use App\Entity\User;
use App\Entity\ConfigVariable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
use App\Repository\UserRepository;
class SecurityController extends AbstractController
{
@@ -35,24 +34,51 @@ class SecurityController extends AbstractController
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
#[Route(path: '/addAdmin', name: 'app_security_addAdmin')]
#[Route(path: '/setup', name: 'app_security_addAdmin')]
public function addAdmin(UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $entityManager): Response
{
$user = new User();
$user->setUsername("admin");
$user->setRoles(["ROLE_USER", "ROLE_ADMIN"]);
$plaintextPassword = "admin";
$setupStatus = $entityManager->find(ConfigVariable::class, "SETUP_STATUS");
// hash the password (based on the security.yaml config for the $user class)
$hashedPassword = $passwordHasher->hashPassword(
$user,
$plaintextPassword
);
$user->setPassword($hashedPassword);
if($setupStatus == null || $setupStatus == 1) {
return new Response("Setup was already done");
}
$entityManager->persist($user);
$entityManager->flush();
return new Response("Created user - Admin");
$entityManager->beginTransaction();
try {
//Create system variables.
$var_setup = new ConfigVariable();
$var_setup->setKey("SETUP_STATUS");
$var_setup->setValue("1");
$var_setup->setSection("SYSTEM");
$entityManager->persist($var_setup);
//Create user Admin.
$user = new User();
$user->setUsername("admin");
$user->setRoles(["ROLE_USER", "ROLE_ADMIN", "ROLE_SUPERADMIN"]);
$plaintextPassword = "admin";
// hash the password (based on the security.yaml config for the $user class)
$hashedPassword = $passwordHasher->hashPassword(
$user,
$plaintextPassword
);
$user->setPassword($hashedPassword);
$entityManager->persist($user);
$entityManager->flush();
$entityManager->commit();
} catch (\Throwable $th) {
$entityManager->rollback();
throw $th;
}
return new Response("Setup completed :)");
}
}
+5 -1
View File
@@ -29,7 +29,7 @@ class RoomAvailability
public function setDay(\DateTimeInterface $day): static
{
$this->day = $day->format("Ymd");
$this->day = $day->format("Y-m-d");
return $this;
}
@@ -57,4 +57,8 @@ class RoomAvailability
return $this;
}
public function modNumAvailable(int $diffAmount):void {
$this->numAvailable -= $diffAmount;
}
}
+108 -5
View File
@@ -3,6 +3,7 @@
namespace App\Repository;
use App\Entity\RoomAvailability;
use App\Entity\RoomCategory;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -34,7 +35,6 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->getResult();
}
/**
* @param \DateTimeInterface Period start (included in range).
* @param \DateTimeInterface Period end (not included in range).
@@ -53,8 +53,30 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getArrayResult()
;
->getArrayResult();
}
/**
* @param \DateTimeInterface Period start (included in range).
* @param \DateTimeInterface Period end (not included in range).
* @return int Returns number of available room of that category on selected period.
*/
public function getAvailabilityForCategoryInPeriod(RoomCategory $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate): int | null
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
$roomCatId = $roomCategory->getId();
return $this->createQueryBuilder('ra')
->select('MIN(ra.numAvailable) as availability')
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->andWhere('ra.roomCategory = :roomCategory')
->setParameter('roomCategory', $roomCatId)
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getSingleScalarResult();
}
public function getAvailabilityForPeriodDetails(\DateTimeInterface $startDate, \DateTimeInterface $endDate): array
@@ -68,7 +90,88 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getArrayResult()
;
->getArrayResult();
}
/**
* Updates All Availability of selected roomCategory during period. It can operate in 2 modes, normal and diff mode.
* - Normal mode -> Sets value.
* - Diff mode -> apply value as sum ( currentAvailability += diff )
* NOTE: A Flush is needed after this operation.
*/
public function updateAvailabilityForRoomCategoryOnPeriod(RoomCategory $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate, int $amount, bool $isDiff = false): void
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
$roomCatId = $roomCategory->getId();
$availabilityItemList = $this->createQueryBuilder('ra')
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->andWhere('ra.roomCategory = :roomCategory')
->setParameter('roomCategory', $roomCatId)
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->getResult();
$em = $this->getEntityManager();
foreach ($availabilityItemList as $item) {
if ($isDiff) {
$item->modNumAvailable($amount);
} else {
$item->setNumAvailable($amount);
}
}
}
/**
* For a given RoomCategory, sets its availability for all days on a period with the given amount.
*/
public function bulkEditRoomAvailabilityForCategoryOnPeriod(RoomCategory $roomCategory, \DateTimeInterface $startDate, \DateTimeInterface $endDate, int $amount): void
{
$strStartDate = $startDate->format('Y-m-d');
$strEndDate = $endDate->format('Y-m-d');
$roomCatId = $roomCategory->getId();
$entityManager = $this->getEntityManager();
$entityManager->beginTransaction();
try {
//First, delete old data.
$this->createQueryBuilder('ra')
->delete()
->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate')
->andWhere('ra.roomCategory = :roomCategory')
->setParameter('roomCategory', $roomCatId)
->setParameter('startDate', $strStartDate)
->setParameter('endDate', $strEndDate)
->getQuery()
->execute();
$entityManager->flush();
//For each day, create item and persist it.
$daterange = new \DatePeriod($startDate, new \DateInterval('P1D'), $endDate);
foreach ($daterange as $date) {
$item = new RoomAvailability();
$item->setRoomCategory($roomCategory);
$item->setDay($date);
$item->setNumAvailable($amount);
$entityManager->persist($item);
}
$entityManager->flush();
$entityManager->commit();
} catch (\Throwable $th) {
$entityManager->rollback();
throw $th;
}
}
}
@@ -0,0 +1,10 @@
{# templates/admin/room_availability/availability_day_edit.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Category - Room Availability - Edit' %}
{% block main %}
<div>
<h3>{{categoryId}}</h3>
</div>
{{ form(form) }}
{% endblock %}
@@ -0,0 +1,10 @@
{# templates/admin/room_availability/availability_day_edit.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Room Availability - Edit' %}
{% block main %}
<div>
<h3>{{availabilityData.day |date('d-m-Y')}}</h3>
</div>
{{ form(form) }}
{% endblock %}
@@ -1,4 +1,4 @@
{# templates/admin/room_availability/index.html.twig #}
{# templates/admin/room_availability/category_view.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title 'Room Availability' %}
@@ -18,6 +18,11 @@
<tr>
<td>{{availability.day |date('d-m-Y')}}</td>
<td>{{availability.numAvailable}}</td>
<td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category_update', { day: availability.day |date('Y-m-d'), categoryId: categoryData.id }) }}">
Edit
</a>
</td>
</tr>
{% endfor %}
</tbody>
@@ -6,19 +6,27 @@
<table class="datagrid">
<thead>
<tr>
<td>ID</td>
<td>Category</td>
<td>Details</td>
<td>Period update</td>
</tr>
</thead>
<tbody>
{% for category in roomCategoryList %}
<tr>
<td>{{category.id}}</td>
<td>{{category.name}}</td>
<td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category', { categoryId: category.id }) }}">
View Details
</a>
</td>
<td>
<a href="{{ ea_url().setRoute('admin_roomAvailability_category_bulkupdate', { categoryId: category.id }) }}">
Update Period
</a>
</td>
</tr>
{% endfor %}
</tbody>