3 Commits
Author SHA1 Message Date
Daniel-Garmig 3bb042462b Booking - Small improvements and fixes
CI / Docker Lint (push) Successful in 29s
2026-01-13 15:01:30 +01:00
Daniel-Garmig 52a4997497 Updated DateTime types. 2026-01-13 10:48:04 +01:00
Daniel-Garmig 708948fa15 Config Vars - General fixes and improvements. 2026-01-07 22:29:07 +01:00
16 changed files with 147 additions and 196 deletions
+52 -63
View File
@@ -5,9 +5,8 @@
- [Config implementation status](#config-implementation-status) - [Config implementation status](#config-implementation-status)
- [Company Information](#company-information) - [Company Information](#company-information)
- [Front-end Config](#front-end-config) - [Front-end Config](#front-end-config)
- [Var `NightAmountForContactDialog`](#var-nightamountforcontactdialog) - [Var `NightCountForContactDialog`](#var-nightcountforcontactdialog)
- [Var `GuestNumberForContactDialog`](#var-guestnumberforcontactdialog) - [Var `GuestNumberForContactDialog`](#var-guestnumberforcontactdialog)
- [Var `ContactDialogShowBehaviour`](#var-contactdialogshowbehaviour)
- [Var `ShowNonAvailableRooms`](#var-shownonavailablerooms) - [Var `ShowNonAvailableRooms`](#var-shownonavailablerooms)
- [Var `SomniaAttributionNotice`](#var-somniaattributionnotice) - [Var `SomniaAttributionNotice`](#var-somniaattributionnotice)
- [Var `PhotoAspectRatio`](#var-photoaspectratio) - [Var `PhotoAspectRatio`](#var-photoaspectratio)
@@ -31,31 +30,32 @@ Variables are divided in sections. Some sections are ony accesible from SomniaRo
| [Company Information](#company-information) | COMPANY | Configure company information | Public | | [Company Information](#company-information) | COMPANY | Configure company information | Public |
| [Front-end config](#front-end-config) | FRONTEND_CONFIG | Configure front-end behaviour | Public | | [Front-end config](#front-end-config) | FRONTEND_CONFIG | Configure front-end behaviour | Public |
| [Price API Management](#price-management) | API_PRICES | Configure how prices are calculated for a stay | Private | | [Price API Management](#price-management) | API_PRICES | Configure how prices are calculated for a stay | Private |
| [API Config](#api-config) | API_CONFIG | Configure API | Private | | [API Config](#api-config) | API_CONFIG | Configure API | Public |
### Config implementation status ### Config implementation status
Not all variables are fully implemented, some are experimental or have options that are. Not all variables are fully implemented, some are experimental or have options that are.
Current status for a variable and it's options: Current status for a variable and it's options:
- New!: :sparkles: - New/Experimental: :test_tube:
- Fully available: :heavy_check_mark: - Fully available: :heavy_check_mark:
- Partially available: :construction: - Partially available: :construction:
- Experimental: :bulb:
- Not yet: :x: - Not yet: :x:
## Company Information ## Company Information
Variables in this section contains information about the Company. Those are used to identify the business and to customize frontend. Variables in this section contains information about the Company. Those are used to identify the business and to customize frontend.
| Variable | Type | Description | Constraints | Default Value | Status | | Variable | Type | Description | Constraints | Default Value | Status |
| :------------- | :----- | :-------------------- | :----------------------------------------------------------- | :------------ | :------------: | | :-------------------- | :----- | :----------------------- | :----------------------------------------------------------- | :------------ | :----------------: |
| CompanyName | string | Company name | - | SomniaRooms | :construction: | | CompanyName | string | Company name | - | SomniaRooms | :heavy_check_mark: |
| CompanyWebsite | string | Company website URL | Valid URL to company website. | somnia.dev | :construction: | | CompanyWebsite | string | Company website URL | Valid URL to company website. | somnia.dev | :heavy_check_mark: |
| CompanyLogo | string | Company Logo URL | Valid URL to company logo. Somnia Backend system can be used | "" | :construction: | | CompanyContactFormUrl | string | Company Contact form URL | A empty string or a URL to a page with a contact form. | "" | :heavy_check_mark: |
| CompanyAddress | string | Company Address | - | "" | :construction: | | CompanyLogo | string | Company Logo URL | Valid URL to company logo. Somnia Backend system can be used | "" | :heavy_check_mark: |
| CompanyEmail | string | Company email address | - | "" | :construction: | | CompanyLogoSize | string | Company Logo Size | A valid CSS size for the company logo. e.g. "96px" | "" | :heavy_check_mark: |
| CompanyPhone | string | Company phone number | - | "" | :construction: | | CompanyAddress | string | Company Address | - | "" | :heavy_check_mark: |
| CompanyEmail | string | Company email address | - | "" | :heavy_check_mark: |
| CompanyPhone | string | Company phone number | - | "" | :heavy_check_mark: |
## Front-end Config ## Front-end Config
@@ -63,24 +63,23 @@ This variable set the behaviour of front-end app.
| Variable | Type | Description | Constraints | Default Value | Status | | Variable | Type | Description | Constraints | Default Value | Status |
| :-------------------------- | :------ | :------------------------------------------------- | :---------------------------------------------- | :------------ | :------------: | | :-------------------------- | :------ | :------------------------------------------------- | :---------------------------------------------- | :------------ | :------------: |
| NightAmountForContactDialog | int | Set the amount of nights to show a contact dialog. | See [details](#var-nightamountforcontactdialog) | 10 | :construction: | | NightCountForContactDialog | int | Set the amount of nights to show a contact dialog. | See [details](#var-NightCountForContactDialog) | 10 | :test_tube: |
| GuestNumberForContactDialog | int | Set the guest number to show a contact dialog. | See [details](#var-guestnumberforcontactdialog) | 6 | :construction: | | GuestNumberForContactDialog | int | Set the guest number to show a contact dialog. | See [details](#var-guestnumberforcontactdialog) | 6 | :construction: |
| ContactDialogShowBehaviour | options | Set how the contact dialog is shown | See [details](#var-contactdialogshowbehaviour) | show | :construction: |
| ShowNonAvailableRooms | bool | Show rooms without availability on search | See [details](#var-shownonavailablerooms) | true | :construction: | | ShowNonAvailableRooms | bool | Show rooms without availability on search | See [details](#var-shownonavailablerooms) | true | :construction: |
| SomniaAttributionNotice | options | How SomniaRooms notice is shown. | See [details](#var-somniaattributionnotice) | full | :construction: | | SomniaAttributionNotice | options | How SomniaRooms notice is shown. | See [details](#var-somniaattributionnotice) | full | :construction: |
| PhotoAspectRatio | string | Set the aspect ratio for photos | See [details](#var-photoaspectratio) | 4:3 | :construction: | | PhotoAspectRatio | string | Set the aspect ratio for photos | See [details](#var-photoaspectratio) | 4:3 | :construction: |
### Var `NightAmountForContactDialog` ### Var `NightCountForContactDialog`
Set the amount of nights to show a contact dialog. This dialog contains company contact information and may be used to get a custom price for long stays. Set the amount of nights to show a contact dialog. This dialog contains company contact information and may be used to get a custom price for long stays.
The contact dialog will be shown when night amount is equal or greater than configured amount. The contact dialog will be shown when night amount is equal or greater than configured amount.
**Options:** **Options:**
| Option | Value | Usage | Status | | Option | Value | Usage | Status |
| :------------ | :---- | :-------------------------------------------------------- | :----: | | :------------ | :---- | :-------------------------------------------------------- | :---------: |
| Disabled | -1 | Disable this funcionality. Contact dialog won't be shown. | :x: | | Disabled | -1 | Disable this funcionality. Contact dialog won't be shown. | :test_tube: |
| Always | 0 | Contact dialog is always shown. | :x: | | Always | 0 | Contact dialog is always shown. | :test_tube: |
| Custom Amount | - | Set the minimun number of nights to show the dialog. | :x: | | Custom Amount | - | Set the minimun number of nights to show the dialog. | :test_tube: |
### Var `GuestNumberForContactDialog` ### Var `GuestNumberForContactDialog`
@@ -96,16 +95,6 @@ The contact dialog will be shown when guest number is equal or greater than conf
| Custom Amount | - | Set the minimun guest number to show the dialog. | :x: | | Custom Amount | - | Set the minimun guest number to show the dialog. | :x: |
### Var `ContactDialogShowBehaviour`
How the contact dialog is shown to the user (when conditions are meet).
**Options:**
| Option | Value | Usage | Status |
| :----- | :---- | :------------------------------- | :----: |
| Show | SHOW | Show dialog on screen | :x: |
| Alert | ALERT | Show an alert to open the dialog | :x: |
### Var `ShowNonAvailableRooms` ### Var `ShowNonAvailableRooms`
Show rooms without availability on search. Show rooms without availability on search.
Non-available rooms will be shown at the end of the list. Non-available rooms will be shown at the end of the list.
@@ -120,11 +109,11 @@ Non-available rooms will be shown at the end of the list.
Configure how SomniaRooms attribution note is display. Configure how SomniaRooms attribution note is display.
**Options:** **Options:**
| Option | Value | Usage | Status | | Option | Value | Usage | Status |
| :----- | :---- | :------------------------------------ | :----: | | :----- | :---- | :------------------------------------ | :---------: |
| Full | FULL | Show the full message. Thanks :heart: | :x: | | Full | FULL | Show the full message. Thanks :heart: | :test_tube: |
| Small | SMALL | Show a small notice :+1: | :x: | | Small | SMALL | Show a small notice :+1: | :x: |
| Hide | HIDE | SomniaRooms notice won't be shown. | :x: | | Hide | HIDE | SomniaRooms notice won't be shown. | :x: |
### Var `PhotoAspectRatio` ### Var `PhotoAspectRatio`
@@ -137,10 +126,10 @@ It's recommended that all photos have the same aspect-ratio to avoid scaling.
## Price Management ## Price Management
How SomniaRooms API services calculates prices for bookings. How SomniaRooms API services calculates prices for bookings.
| Variable | Type | Description | Constraints | Default Value | Status | | Variable | Type | Description | Constraints | Default Value | Status |
| :------------------ | :----- | :---------------------------------------------------- | :-------------------------------------- | :------------ | :----: | | :------------------ | :----- | :---------------------------------------------------- | :-------------------------------------- | :------------ | :---------: |
| TotalPriceMethod | option | How total price is calculated for a stay | See [details](#var-totalpricemethod) | SUM | :x: | | TotalPriceMethod | option | How total price is calculated for a stay | See [details](#var-totalpricemethod) | SUM | :test_tube: |
| FareSelectionMethod | option | What fare to select when multiple fares are available | See [details](#var-fareselectionmethod) | HIGHER | :x: | | FareSelectionMethod | option | What fare to select when multiple fares are available | See [details](#var-fareselectionmethod) | HIGHER | :test_tube: |
### Var `TotalPriceMethod`. ### Var `TotalPriceMethod`.
@@ -149,12 +138,12 @@ Establish the method used to calculate the total price for a stay.
The total price for a stay will be calculated from the price of the days that make the stay. A operation must be selected to get total price from daily prices. The total price for a stay will be calculated from the price of the days that make the stay. A operation must be selected to get total price from daily prices.
**Options:** **Options:**
| Option | Value | Usage | Status | | Option | Value | Usage | Status |
| :------------ | :------ | :----------------------------------------------------------------- | :----: | | :------------ | :------ | :----------------------------------------------------------------- | :---------: |
| Lower Price | LOWER | The lower price will be apply for each day. | :x: | | Lower Price | LOWER | The lower price will be apply for each day. | :test_tube: |
| High Price | HIGHER | The highest price will be apply for each day. | :x: | | High Price | HIGHER | The highest price will be apply for each day. | :test_tube: |
| Total Sum | SUM | Total price will be the sum of the price for each day. | :x: | | Total Sum | SUM | Total price will be the sum of the price for each day. | :test_tube: |
| Average Price | AVERAGE | The average for all days will be calculated and apply to each day. | :x: | | Average Price | AVERAGE | The average for all days will be calculated and apply to each day. | :test_tube: |
### Var `FareSelectionMethod` ### Var `FareSelectionMethod`
@@ -164,20 +153,20 @@ A faretable may contain multiple fares for a room and guest number given some co
Note: RoomPrices have more priority than fares. So if a RoomPrice is available, it will always be the prefered option. Note: RoomPrices have more priority than fares. So if a RoomPrice is available, it will always be the prefered option.
**Options:** **Options:**
| Option | Value | Usage | Status | | Option | Value | Usage | Status |
| :------------ | :------ | :---------------------------------------------------------------- | :----: | | :------------ | :------ | :---------------------------------------------------------------- | :---------: |
| Lower Price | LOWER | Lower price available will be used. | :x: | | Lower Price | LOWER | Lower price available will be used. | :test_tube: |
| High Price | HIGHER | Higher available price will be used. | :x: | | High Price | HIGHER | Higher available price will be used. | :test_tube: |
| Average price | AVERAGE | Selected price will be the aritmetic average of available prices. | :x: | | Average price | AVERAGE | Selected price will be the aritmetic average of available prices. | :test_tube: |
## API Config. ## API Config.
Configuration for some API endpoints. Configuration for some API endpoints.
| Variable | Type | Description | Constraints | Default Value | Status | | Variable | Type | Description | Constraints | Default Value | Status |
| :-------------------- | :--- | :----------------------------------- | :---------------------------------------- | :------------ | :------------: | | :-------------------- | :--- | :----------------------------------- | :---------------------------------------- | :------------ | :---------: |
| MaxAllowedNightAmount | int | Max night amount that can be booked. | See [details](#var-maxallowednightamount) | 20 | :construction: | | MaxAllowedNightAmount | int | Max night amount that can be booked. | See [details](#var-maxallowednightamount) | 20 | :test_tube: |
| MaxAllowedGuestNumber | int | Max guest number that can be booked. | See [details](#var-maxallowedguestnumber) | 20 | :construction: | | MaxAllowedGuestNumber | int | Max guest number that can be booked. | See [details](#var-maxallowedguestnumber) | 20 | :test_tube: |
@@ -185,16 +174,16 @@ Configuration for some API endpoints.
Max number of nights that can be booked using the app. For longer stays the company should be contacted. Max number of nights that can be booked using the app. For longer stays the company should be contacted.
**Options:** **Options:**
| Option | Value | Usage | Status | | Option | Value | Usage | Status |
| :------------ | :---- | :---------------------------------------------------------- | :----: | | :------------ | :---- | :---------------------------------------------------------- | :---------: |
| Disabled | 0 | Disable this funcionality. No limit on booking will be set. | :x: | | Disabled | 0 | Disable this funcionality. No limit on booking will be set. | :test_tube: |
| Custom Amount | - | Set the max number of nights allowed to book. | :x: | | Custom Amount | - | Set the max number of nights allowed to book. | :test_tube: |
### Var `MaxAllowedGuestNumber` ### Var `MaxAllowedGuestNumber`
Max guest number that can be booked using the app. For bigger groups the company should be contacted. Max guest number that can be booked using the app. For bigger groups the company should be contacted.
**Options:** **Options:**
| Option | Value | Usage | Status | | Option | Value | Usage | Status |
| :------------ | :---- | :---------------------------------------------------------- | :----: | | :------------ | :---- | :---------------------------------------------------------- | :---------: |
| Disabled | 0 | Disable this funcionality. No limit on booking will be set. | :x: | | Disabled | 0 | Disable this funcionality. No limit on booking will be set. | :test_tube: |
| Custom Amount | - | Set the max guest number allowed to book. | :x: | | Custom Amount | - | Set the max guest number allowed to book. | :test_tube: |
+18 -3
View File
@@ -9,17 +9,23 @@ use App\Enum\ConfigVariableSectionEnum;
// --> This constants contains all defined Config Variables. For each one a array is stored as [varKey => [varSection, varDefaultValue]]. // --> This constants contains all defined Config Variables. For each one a array is stored as [varKey => [varSection, varDefaultValue]].
class ConfigVariableConsts class ConfigVariableConsts
{ {
public const PUBLIC_SECTION_LIST = [
ConfigVariableSectionEnum::Company->value,
ConfigVariableSectionEnum::Frontend->value,
ConfigVariableSectionEnum::API_CONFIG->value,
];
public const CONFIG_COMPANY_NAME = ["CompanyName" => [ConfigVariableSectionEnum::Company, "SomniaRooms"]]; public const CONFIG_COMPANY_NAME = ["CompanyName" => [ConfigVariableSectionEnum::Company, "SomniaRooms"]];
public const CONFIG_COMPANY_WEBSITE = ["CompanyWebsite" => [ConfigVariableSectionEnum::Company, "somnia.dev"]]; public const CONFIG_COMPANY_WEBSITE = ["CompanyWebsite" => [ConfigVariableSectionEnum::Company, "somnia.dev"]];
public const CONFIG_COMPANY_CONTACT_FORMURL = ["CompanyContactFormUrl" => [ConfigVariableSectionEnum::Company, "somnia.dev"]];
public const CONFIG_COMPANY_LOGO = ["CompanyLogo" => [ConfigVariableSectionEnum::Company, ""]]; public const CONFIG_COMPANY_LOGO = ["CompanyLogo" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_COMPANY_LOGOSIZE = ["CompanyLogoSize" => [ConfigVariableSectionEnum::Company, ""]]; public const CONFIG_COMPANY_LOGOSIZE = ["CompanyLogoSize" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_COMPANY_ADDRESS = ["CompanyAddress" => [ConfigVariableSectionEnum::Company, ""]]; public const CONFIG_COMPANY_ADDRESS = ["CompanyAddress" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_COMPANY_EMAIL = ["CompanyEmail" => [ConfigVariableSectionEnum::Company, ""]]; public const CONFIG_COMPANY_EMAIL = ["CompanyEmail" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_COMPANY_PHONE = ["CompanyPhone" => [ConfigVariableSectionEnum::Company, ""]]; public const CONFIG_COMPANY_PHONE = ["CompanyPhone" => [ConfigVariableSectionEnum::Company, ""]];
public const CONFIG_FRONTEND_NIGHTSFORCONTACT = ["NightAmountForContactDialog" => [ConfigVariableSectionEnum::Frontend, "10"]]; public const CONFIG_FRONTEND_NIGHTSFORCONTACT = ["NightCountForContactDialog" => [ConfigVariableSectionEnum::Frontend, "10"]];
public const CONFIG_FRONTEND_GUESTSFORCONTACT = ["GuestNumberForContactDialog" => [ConfigVariableSectionEnum::Frontend, "6"]]; public const CONFIG_FRONTEND_GUESTSFORCONTACT = ["GuestNumberForContactDialog" => [ConfigVariableSectionEnum::Frontend, "6"]];
public const CONFIG_FRONTEND_CONTACTDIALOGBEHAVIOUR = ["ContactDialogShowBehaviour" => [ConfigVariableSectionEnum::Frontend, "show"]];
public const CONFIG_FRONTEND_SHOWNONAVAILABLEROOMS = ["ShowNonAvailableRooms" => [ConfigVariableSectionEnum::Frontend, true]]; public const CONFIG_FRONTEND_SHOWNONAVAILABLEROOMS = ["ShowNonAvailableRooms" => [ConfigVariableSectionEnum::Frontend, true]];
public const CONFIG_FRONTEND_SOMNIAATTRIBUTION = ["SomniaAttributionNotice" => [ConfigVariableSectionEnum::Frontend, "full"]]; public const CONFIG_FRONTEND_SOMNIAATTRIBUTION = ["SomniaAttributionNotice" => [ConfigVariableSectionEnum::Frontend, "full"]];
public const CONFIG_FRONTEND_PHOTOASPECTRATIO = ["PhotoAspectRatio" => [ConfigVariableSectionEnum::Frontend, "4:3"]]; public const CONFIG_FRONTEND_PHOTOASPECTRATIO = ["PhotoAspectRatio" => [ConfigVariableSectionEnum::Frontend, "4:3"]];
@@ -42,6 +48,7 @@ class ConfigVariableConsts
return array_merge( return array_merge(
ConfigVariableConsts::CONFIG_COMPANY_NAME, ConfigVariableConsts::CONFIG_COMPANY_NAME,
ConfigVariableConsts::CONFIG_COMPANY_WEBSITE, ConfigVariableConsts::CONFIG_COMPANY_WEBSITE,
ConfigVariableConsts::CONFIG_COMPANY_CONTACT_FORMURL,
ConfigVariableConsts::CONFIG_COMPANY_LOGO, ConfigVariableConsts::CONFIG_COMPANY_LOGO,
ConfigVariableConsts::CONFIG_COMPANY_LOGOSIZE, ConfigVariableConsts::CONFIG_COMPANY_LOGOSIZE,
ConfigVariableConsts::CONFIG_COMPANY_ADDRESS, ConfigVariableConsts::CONFIG_COMPANY_ADDRESS,
@@ -49,7 +56,6 @@ class ConfigVariableConsts
ConfigVariableConsts::CONFIG_COMPANY_PHONE, ConfigVariableConsts::CONFIG_COMPANY_PHONE,
ConfigVariableConsts::CONFIG_FRONTEND_NIGHTSFORCONTACT, ConfigVariableConsts::CONFIG_FRONTEND_NIGHTSFORCONTACT,
ConfigVariableConsts::CONFIG_FRONTEND_GUESTSFORCONTACT, ConfigVariableConsts::CONFIG_FRONTEND_GUESTSFORCONTACT,
ConfigVariableConsts::CONFIG_FRONTEND_CONTACTDIALOGBEHAVIOUR,
ConfigVariableConsts::CONFIG_FRONTEND_SHOWNONAVAILABLEROOMS, ConfigVariableConsts::CONFIG_FRONTEND_SHOWNONAVAILABLEROOMS,
ConfigVariableConsts::CONFIG_FRONTEND_SOMNIAATTRIBUTION, ConfigVariableConsts::CONFIG_FRONTEND_SOMNIAATTRIBUTION,
ConfigVariableConsts::CONFIG_FRONTEND_PHOTOASPECTRATIO, ConfigVariableConsts::CONFIG_FRONTEND_PHOTOASPECTRATIO,
@@ -60,4 +66,13 @@ class ConfigVariableConsts
ConfigVariableConsts::CONFIG_INTERNAL_SETUP, ConfigVariableConsts::CONFIG_INTERNAL_SETUP,
); );
} }
public static function IsSectionValuePublicForApi(string $sectionValue)
{
return in_array($sectionValue, ConfigVariableConsts::PUBLIC_SECTION_LIST);
}
public static function IsSectionPublicForApi(ConfigVariableSectionEnum $section)
{
return ConfigVariableConsts::IsSectionValuePublicForApi($section->value);
}
} }
+39 -19
View File
@@ -3,6 +3,7 @@
namespace App\Controller; namespace App\Controller;
use App\ApiErrorException; use App\ApiErrorException;
use App\Consts\ConfigVariableConsts;
use App\DTO\ApiErrorMessage; use App\DTO\ApiErrorMessage;
use App\Entity\Booking; use App\Entity\Booking;
use App\Entity\BookingRoom; use App\Entity\BookingRoom;
@@ -12,7 +13,9 @@ use App\Repository\RoomAvailabilityRepository;
use App\Request\BookingCreateRequest; use App\Request\BookingCreateRequest;
use App\Request\BookingDetailsRequest; use App\Request\BookingDetailsRequest;
use App\Response\BookingCreateResponse; use App\Response\BookingCreateResponse;
use App\Service\ConfigVariableService;
use App\Service\PricesService; use App\Service\PricesService;
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Nelmio\ApiDocBundle\Attribute\Model; use Nelmio\ApiDocBundle\Attribute\Model;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -84,21 +87,43 @@ class BookingController extends AbstractController
RoomCategoryRepository $repoCat, RoomCategoryRepository $repoCat,
RoomAvailabilityRepository $repoAvailability, RoomAvailabilityRepository $repoAvailability,
PricesService $priceSrv, PricesService $priceSrv,
ConfigVariableService $config,
#[MapRequestPayload()] BookingCreateRequest $createRequest #[MapRequestPayload()] BookingCreateRequest $createRequest
): JsonResponse { ): JsonResponse {
$booking = new Booking(); if ($createRequest->arrivalDate >= $createRequest->departureDate) {
try { throw new ApiErrorException(
if ($createRequest->arrivalDate >= $createRequest->departureDate) { new ApiErrorMessage(422, ApiErrorMessage::TYPE_INVALID_DATE),
'Arrival cant be before departure',
);
}
if (\count($createRequest->rooms) == 0) {
throw new ApiErrorException(
new ApiErrorMessage(422, ApiErrorMessage::TYPE_VALIDATION_ERROR),
'Booking must contain rooms',
);
}
$maxAllowedGuestNumber = $config->getVariableFromConst(ConfigVariableConsts::CONFIG_API_NAXGUESTS)?->getValue() ?? 0;
if ($maxAllowedGuestNumber > 0) {
$totalGuestNumber = array_reduce($createRequest->rooms, function ($carry, $item) {
return $carry += $item->guestNumber;
}, 0);
if ($totalGuestNumber > $maxAllowedGuestNumber) {
throw new ApiErrorException( throw new ApiErrorException(
new ApiErrorMessage(422, ApiErrorMessage::TYPE_INVALID_DATE), new ApiErrorMessage(422, ApiErrorMessage::TYPE_VALIDATION_ERROR),
'Arrival cant be before departure', 'Max allowed guest number exceeded. Contact the company to complete the booking',
); );
} }
}
$entityManager->beginTransaction(); $currentTime = new \DateTimeImmutable("now");
$booking = new Booking();
try {
$currentTime = new \DateTime("now"); $entityManager->beginTransaction(TransactionIsolationLevel::REPEATABLE_READ);
// Create booking from request data. // Create booking from request data.
$booking->setBookingTime($currentTime); $booking->setBookingTime($currentTime);
@@ -117,13 +142,6 @@ class BookingController extends AbstractController
$entityManager->persist($booking); $entityManager->persist($booking);
if (\count($createRequest->rooms) == 0) {
throw new ApiErrorException(
new ApiErrorMessage(422, ApiErrorMessage::TYPE_VALIDATION_ERROR),
'Booking must contain rooms',
);
}
// Create each room. // Create each room.
foreach ($createRequest->rooms as $roomItem) { foreach ($createRequest->rooms as $roomItem) {
$room = new BookingRoom(); $room = new BookingRoom();
@@ -157,9 +175,9 @@ class BookingController extends AbstractController
//Get Price for period. //Get Price for period.
$priceList = $priceSrv->getFinalPricesForSearch($roomCategory, $createRequest->arrivalDate, $createRequest->departureDate); $priceList = $priceSrv->getFinalPricesForSearch($roomCategory, $createRequest->arrivalDate, $createRequest->departureDate);
$priceFound = false; $priceFound = false;
foreach ($priceList as $key => $value) { foreach ($priceList as $priceItem) {
if ($key == $selectedGuestAmount) { if ($priceItem->guestNumber == $selectedGuestAmount) {
$room->setRoomPrice($value); $room->setRoomPrice($priceItem->price);
$priceFound = true; $priceFound = true;
break; break;
} }
@@ -178,10 +196,12 @@ class BookingController extends AbstractController
$repoAvailability->updateAvailabilityForRoomCategoryOnPeriod($roomCategory, $createRequest->arrivalDate, $createRequest->departureDate, $selectedAmount, true); //Update using "diff" mode. $repoAvailability->updateAvailabilityForRoomCategoryOnPeriod($roomCategory, $createRequest->arrivalDate, $createRequest->departureDate, $selectedAmount, true); //Update using "diff" mode.
} }
$entityManager->commit();
$entityManager->flush(); $entityManager->flush();
$entityManager->commit();
} catch (\Throwable $th) { } catch (\Throwable $th) {
$entityManager->rollback(); if ($entityManager->getConnection()->isTransactionActive()) {
$entityManager->rollback();
}
throw $th; throw $th;
} }
+10 -65
View File
@@ -3,6 +3,7 @@
namespace App\Controller; namespace App\Controller;
use App\ApiErrorException; use App\ApiErrorException;
use App\Consts\ConfigVariableConsts;
use App\DTO\ApiErrorMessage; use App\DTO\ApiErrorMessage;
use App\Entity\ConfigVariable; use App\Entity\ConfigVariable;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -11,6 +12,7 @@ use Symfony\Component\Routing\Attribute\Route;
use App\Repository\ConfigVariableRepository; use App\Repository\ConfigVariableRepository;
use Nelmio\ApiDocBundle\Attribute\Model; use Nelmio\ApiDocBundle\Attribute\Model;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ConfigVariableController extends AbstractController class ConfigVariableController extends AbstractController
{ {
@@ -36,71 +38,11 @@ class ConfigVariableController extends AbstractController
public function getAllVariables(ConfigVariableRepository $repo): Response public function getAllVariables(ConfigVariableRepository $repo): Response
{ {
$varList = $repo->findAllVariables(); $varList = $repo->findAllVariables();
$filteredResults = array_filter($varList, function ($varItem) {
return ConfigVariableConsts::IsSectionPublicForApi($varItem->getSection());
});
return $this->json(['results' => $varList]); return $this->json(['results' => array_values($filteredResults)]);
}
#[Route('/api/config/section', name: 'app_config_sections', methods: ["GET"])]
#[OA\Tag(name: "Config")]
#[OA\Response(
response: 200,
description: 'Returns all public config sections.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(
property: "results",
type: "array",
items: new OA\Items(type: "string")
)
]
)
)]
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', methods: ["GET"])]
#[OA\Tag(name: "Config")]
#[OA\Response(
response: 200,
description: 'Returns all public config variables for given section.',
content: new OA\JsonContent(
type: 'object',
properties: [
new OA\Property(
property: "results",
type: "array",
items: new OA\Items(ref: new Model(type: ConfigVariable::class))
)
]
)
)]
#[OA\Parameter(
name: 'section',
in: 'path',
description: 'Config section.',
schema: new OA\Schema(type: 'string')
)]
public function getVariablesBySection(ConfigVariableRepository $repo, string $section): Response
{
if(\is_null($section) || $section == '') {
throw new ApiErrorException(
new ApiErrorMessage(400, ApiErrorMessage::TYPE_INVALID_REQUEST_BODY_FORMAT)
);
}
$varList = $repo->findAllBySection($section);
return $this->json(['results' => $varList]);
} }
#[Route('/api/config/var/{key}', name: 'app_config_variable', methods: ["GET"])] #[Route('/api/config/var/{key}', name: 'app_config_variable', methods: ["GET"])]
@@ -116,12 +58,15 @@ class ConfigVariableController extends AbstractController
#[OA\Parameter( #[OA\Parameter(
name: 'key', name: 'key',
in: 'path', in: 'path',
description: 'Variable key.', description: 'Variable key. (Capital letters must match)',
schema: new OA\Schema(type: 'string') schema: new OA\Schema(type: 'string')
)] )]
public function getByKey(ConfigVariableRepository $repo, string $key): Response public function getByKey(ConfigVariableRepository $repo, string $key): Response
{ {
$var = $repo->findVariableByKey($key); $var = $repo->findVariableByKey($key);
if (is_null($var)) {
throw new NotFoundHttpException('Config Variable not found!');
}
return $this->json($var); return $this->json($var);
} }
+3 -3
View File
@@ -21,13 +21,13 @@ class Booking
#[ORM\Column(length: 20)] #[ORM\Column(length: 20)]
private ?string $refNumber = null; private ?string $refNumber = null;
#[ORM\Column(type: Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_IMMUTABLE)]
private ?\DateTimeInterface $arrivalDate = null; private ?\DateTimeInterface $arrivalDate = null;
#[ORM\Column(type : Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_IMMUTABLE)]
private ?\DateTimeInterface $departureDate = null; private ?\DateTimeInterface $departureDate = null;
#[ORM\Column(type : Types::DATETIMETZ_MUTABLE)] #[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)]
private ?\DateTimeInterface $bookingTime = null; private ?\DateTimeInterface $bookingTime = null;
#[ORM\OneToOne(mappedBy: 'booking', cascade: ['persist', 'remove'])] #[ORM\OneToOne(mappedBy: 'booking', cascade: ['persist', 'remove'])]
+2 -2
View File
@@ -20,10 +20,10 @@ class FareTable
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")] #[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
private ?RoomCategory $roomCategory = null; private ?RoomCategory $roomCategory = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)] #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: false)]
private ?\DateTimeInterface $startDate = null; private ?\DateTimeInterface $startDate = null;
#[ORM\Column(type : Types::DATETIME_MUTABLE, nullable: true)] #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeInterface $endDate = null; private ?\DateTimeInterface $endDate = null;
#[ORM\Column(length : 255, nullable: true)] #[ORM\Column(length : 255, nullable: true)]
+2 -3
View File
@@ -3,7 +3,6 @@
namespace App\Entity; namespace App\Entity;
use App\Repository\RoomAvailabilityRepository; use App\Repository\RoomAvailabilityRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: RoomAvailabilityRepository::class)] #[ORM\Entity(repositoryClass: RoomAvailabilityRepository::class)]
@@ -22,9 +21,9 @@ class RoomAvailability
#[ORM\Column(nullable: false)] #[ORM\Column(nullable: false)]
private ?int $numAvailable = null; private ?int $numAvailable = null;
public function getDay(): ?\DateTimeInterface public function getDay(): ?\DateTimeImmutable
{ {
return new \DateTime($this->day); return new \DateTimeImmutable($this->day);
} }
public function setDay(\DateTimeInterface $day): static public function setDay(\DateTimeInterface $day): static
+3 -3
View File
@@ -38,9 +38,9 @@ class RoomPrice
return $this; return $this;
} }
public function getDate(): ?\DateTime public function getDate(): ?\DateTimeImmutable
{ {
return new \DateTime($this->date); return new \DateTimeImmutable($this->date);
} }
public function getDateString(): ?string public function getDateString(): ?string
@@ -48,7 +48,7 @@ class RoomPrice
return $this->date; return $this->date;
} }
public function setDate(\DateTime $date): static public function setDate(\DateTimeInterface $date): static
{ {
$this->date = $date->format('Y-m-d'); $this->date = $date->format('Y-m-d');
+4 -9
View File
@@ -24,11 +24,11 @@ class ConfigVariableRepository extends ServiceEntityRepository
/** /**
* Get all config variables for API. * Get all config variables for API.
* @return ConfigVariable[]
*/ */
public function findAllVariables(): array public function findAllVariables(): array
{ {
return $this->createQueryBuilder('c') return $this->createQueryBuilder('c')
->select('c.key, c.value, c.section')
->getQuery() ->getQuery()
->getResult(); ->getResult();
} }
@@ -55,21 +55,16 @@ class ConfigVariableRepository extends ServiceEntityRepository
public function findVariableByKey($varKey): ConfigVariable|null public function findVariableByKey($varKey): ConfigVariable|null
{ {
$rs = $this->createQueryBuilder('c') $rs = $this->createQueryBuilder('c')
->select('c.key, c.value, c.section')
->andWhere('c.key = :val') ->andWhere('c.key = :val')
->setParameter('val', $varKey) ->setParameter('val', $varKey)
->setMaxResults(1) ->setMaxResults(1)
->getQuery() ->getQuery()
->getResult(); ->getResult();
// FIXME!!!
if (!$rs) { if (count($rs) == 0) {
return null; return null;
} }
$var = new ConfigVariable(); return $rs[0];
$var->setKey($rs[0]["key"]);
$var->setValue($rs[0]["value"]);
$var->setSection($rs[0]["section"]);
return $var;
} }
/** /**
@@ -106,6 +106,7 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
$strEndDate = $endDate->format('Y-m-d'); $strEndDate = $endDate->format('Y-m-d');
$roomCatId = $roomCategory->getId(); $roomCatId = $roomCategory->getId();
/** @var RoomAvailability[] */
$availabilityItemList = $this->createQueryBuilder('ra') $availabilityItemList = $this->createQueryBuilder('ra')
->andWhere('ra.day >= :startDate') ->andWhere('ra.day >= :startDate')
->andWhere('ra.day < :endDate') ->andWhere('ra.day < :endDate')
@@ -116,8 +117,6 @@ class RoomAvailabilityRepository extends ServiceEntityRepository
->getQuery() ->getQuery()
->getResult(); ->getResult();
$em = $this->getEntityManager();
foreach ($availabilityItemList as $item) { foreach ($availabilityItemList as $item) {
if ($isDiff) { if ($isDiff) {
$item->modNumAvailable($amount); $item->modNumAvailable($amount);
+3 -3
View File
@@ -3,7 +3,7 @@
namespace App\Request; namespace App\Request;
use App\Entity\BookingCustomer; use App\Entity\BookingCustomer;
use DateTimeInterface; use DateTimeImmutable;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@@ -12,11 +12,11 @@ class BookingCreateRequest
#[OA\Property(description: 'Booking Arrival date')] #[OA\Property(description: 'Booking Arrival date')]
#[Assert\NotBlank] #[Assert\NotBlank]
#[Assert\GreaterThanOrEqual(value: 'today')] #[Assert\GreaterThanOrEqual(value: 'today')]
public DateTimeInterface $arrivalDate; public DateTimeImmutable $arrivalDate;
#[OA\Property(description: 'Booking departure date')] #[OA\Property(description: 'Booking departure date')]
#[Assert\NotBlank] #[Assert\NotBlank]
public DateTimeInterface $departureDate; public DateTimeImmutable $departureDate;
#[OA\Property( #[OA\Property(
description: 'Booking customer details', description: 'Booking customer details',
+3 -3
View File
@@ -2,7 +2,7 @@
namespace App\Request; namespace App\Request;
use DateTimeInterface; use DateTimeImmutable;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@@ -10,9 +10,9 @@ class SearchPeriodRequest
{ {
#[OA\Property(description: 'Period start date')] #[OA\Property(description: 'Period start date')]
#[Assert\NotBlank] #[Assert\NotBlank]
public DateTimeInterface $startDate; public DateTimeImmutable $startDate;
#[OA\Property(description: 'Period end date')] #[OA\Property(description: 'Period end date')]
#[Assert\NotBlank] #[Assert\NotBlank]
public DateTimeInterface $endDate; public DateTimeImmutable $endDate;
} }
@@ -2,7 +2,7 @@
namespace App\Request; namespace App\Request;
use DateTimeInterface; use DateTimeImmutable;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@@ -14,9 +14,9 @@ class SearchPricesForCategoryRequest
#[OA\Property(description: 'Period start date')] #[OA\Property(description: 'Period start date')]
#[Assert\NotBlank] #[Assert\NotBlank]
public DateTimeInterface $startDate; public DateTimeImmutable $startDate;
#[OA\Property(description: 'Period end date')] #[OA\Property(description: 'Period end date')]
#[Assert\NotBlank] #[Assert\NotBlank]
public DateTimeInterface $endDate; public DateTimeImmutable $endDate;
} }
+1 -1
View File
@@ -65,7 +65,7 @@ class ConfigVariableService
} }
} }
public function AddConfigVariable(string $key, string $value, ConfigVariableSectionEnum $section): void private function AddConfigVariable(string $key, string $value, ConfigVariableSectionEnum $section): void
{ {
$var = new ConfigVariable(); $var = new ConfigVariable();
$var->setKey($key); $var->setKey($key);
+2 -13
View File
@@ -2,36 +2,25 @@
namespace App\Service; namespace App\Service;
use App\DTO\AppSetupConfig;
use App\DTO\ExampleDataConfig; use App\DTO\ExampleDataConfig;
use App\Entity\BedType; use App\Entity\BedType;
use App\Entity\ConfigVariable;
use App\Entity\FareTable; use App\Entity\FareTable;
use App\Entity\RoomCategory; use App\Entity\RoomCategory;
use App\Entity\RoomCategoryBedType; use App\Entity\RoomCategoryBedType;
use App\Entity\RoomFare; use App\Entity\RoomFare;
use App\Entity\User;
use App\Enum\UserRoleEnum;
use App\Repository\ConfigVariableRepository;
use App\Repository\FareTableRepository; use App\Repository\FareTableRepository;
use App\Repository\RoomAvailabilityRepository; use App\Repository\RoomAvailabilityRepository;
use App\Repository\RoomCategoryRepository; use App\Repository\RoomCategoryRepository;
use App\Repository\RoomFareRepository; use App\Repository\RoomFareRepository;
use App\Serializer\Normalizer\EntityByIdDenormalizer; use DateTimeImmutable;
use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use stdClass;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
/** /**
* Service for setup proccess. * Service for setup proccess.
@@ -163,7 +152,7 @@ class ExampleDataService
private function GenerateFares(ExampleDataConfig $config): bool private function GenerateFares(ExampleDataConfig $config): bool
{ {
$generationTime = new DateTime(); $generationTime = new DateTimeImmutable();
$strGenTime = $generationTime->format('d-m-Y H:i:s'); $strGenTime = $generationTime->format('d-m-Y H:i:s');
$roomCategoryList = $this->roomCategoryRepository->findAll(); $roomCategoryList = $this->roomCategoryRepository->findAll();
+1 -1
View File
@@ -43,7 +43,7 @@ class PricesService
* *
* e. g. --> [ 2 => 120€, 3 => 180€ ] * e. g. --> [ 2 => 120€, 3 => 180€ ]
* *
* @return array Contiene los precios en función del número de personas. * @return SearchPricesResponsePriceItem[] Contiene los precios en función del número de personas.
*/ */
public function getFinalPricesForSearch(RoomCategory $category, \DateTimeInterface $startDate, \DateTimeInterface $endDate): array public function getFinalPricesForSearch(RoomCategory $category, \DateTimeInterface $startDate, \DateTimeInterface $endDate): array
{ {