wip: booking process

This commit is contained in:
Björn Fromme
2025-07-14 17:15:15 +02:00
parent a55e30e12d
commit bef18971c3
45 changed files with 688 additions and 270 deletions
+22 -5
View File
@@ -1,25 +1,42 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Represents a physical address with street, postal code, city, and country information.
*
* This class handles address data for users and participants in the booking system.
* It provides validation constraints for required fields and methods to convert
* address data to API payload format.
*/
class Address
{
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])]
public ?string $street = '';
public string $street = '';
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])]
public ?string $postCode = '';
public string $postCode = '';
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])]
public ?string $city = '';
public string $city = '';
public ?string $district = '';
public string $district = '';
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])]
public ?string $country = '';
public string $country = '';
/**
* Converts the address to API payload format.
*
* Transforms the address object into an array structure suitable
* for API communication with the BusProNet system.
*
* @return array<string, string> The payload array for API transmission
*/
public function toPayload(): array
{
return [
+17
View File
@@ -1,7 +1,16 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents service availability with status and pricing information.
*
* This class handles availability data including service identification,
* status, available quantity, and pricing. It implements JsonSerializable
* for API response formatting.
*/
class Availability implements \JsonSerializable
{
public ?int $serviceId = null;
@@ -9,6 +18,14 @@ class Availability implements \JsonSerializable
public ?int $available = null;
public ?float $price = null;
/**
* Serializes the availability data to JSON format.
*
* Converts the availability object to an array structure suitable
* for JSON serialization in API responses.
*
* @return array<string, mixed> The serialized availability data
*/
public function jsonSerialize(): array
{
return [
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a bank account with payment information.
*
* This class handles bank account data including IBAN, BIC, bank name,
* and account holder information for payment processing.
*/
class BankAccount
{
public ?string $iban = null;
+20
View File
@@ -1,18 +1,38 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents base data with item collection and retrieval functionality.
*
* This class provides a container for generic data items with methods
* to access the entire collection or individual items by key.
*/
class BaseData
{
public function __construct(private readonly array $items)
{
}
/**
* Retrieves all items in the base data collection.
*
* @return array The complete items array
*/
public function getItems(): array
{
return $this->items;
}
/**
* Retrieves a specific item by its key.
*
* @param string $key The key to search for
*
* @return mixed The item value or null if not found
*/
public function getItemByKey(string $key): mixed
{
return $this->items[$key] ?? null;
+104 -1
View File
@@ -1,7 +1,16 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a booking with participants, services, and pricing information.
*
* This class handles complete booking data including participant information,
* selected services, transportation details, accommodation, and pricing calculations.
* It provides methods to retrieve participant-specific data and calculate costs.
*/
class Booking
{
public ?int $id = null;
@@ -12,7 +21,7 @@ class Booking
public ?int $participantCount = null;
public ?float $price = null;
public ?\DateTimeImmutable $bookingDate = null;
public ?string $travel = null;
public ?string $travelName = null;
public ?int $travelId = null;
public ?string $travelCode = null;
public ?\DateTimeImmutable $travelDate = null;
@@ -37,6 +46,14 @@ class Booking
public ?float $totalPrice = null;
public ?string $travelInfoUrl = null;
/**
* Calculates the remaining balance for the booking.
*
* Returns the difference between the total price and any payments made.
* If no payment has been made, returns the full price.
*
* @return float The remaining balance amount
*/
public function getBalance(): float
{
if (null === $this->payment) {
@@ -46,6 +63,16 @@ class Booking
return $this->price - $this->payment;
}
/**
* Retrieves additional services filtered by group.
*
* Filters the additional services array based on the provided group(s).
* Supports both single group strings and arrays of groups.
*
* @param mixed $group The service group(s) to filter by
*
* @return array<Service> The filtered services array
*/
public function getAdditionalServicesByGroup(mixed $group): array
{
$group = (array) $group;
@@ -55,6 +82,17 @@ class Booking
});
}
/**
* Retrieves additional services for a specific participant by group.
*
* Filters additional services by group and participant index mapping.
* Returns services that are assigned to the specified participant.
*
* @param int $participantIndex The participant index to filter by
* @param mixed $group The service group(s) to filter by
*
* @return array<Service> The filtered services for the participant
*/
public function getAdditionalServicesForParticipantByGroup(int $participantIndex, mixed $group): array
{
$selectedServices = $this->getAdditionalServicesByGroup($group);
@@ -64,6 +102,17 @@ class Booking
});
}
/**
* Retrieves transportation service for a specific participant and direction.
*
* Finds the transportation service that matches the participant index
* and travel direction (e.g., 'H' for outbound, 'R' for return).
*
* @param int $participantIndex The participant index to search for
* @param string $direction The travel direction ('H' or 'R')
*
* @return Service|null The matching transportation service or null if not found
*/
public function getTransportationServiceForParticipantAndDirection(
int $participantIndex,
string $direction
@@ -80,6 +129,16 @@ class Booking
return null;
}
/**
* Retrieves pickup service for a specific participant.
*
* Finds the pickup service assigned to the specified participant
* from the outbound pickup services.
*
* @param int $participantIndex The participant index to search for
*
* @return Pickup|null The matching pickup service or null if not found
*/
public function getPickupForParticipant(int $participantIndex): ?Pickup
{
foreach ($this->pickupsTo as $pickup) {
@@ -91,6 +150,16 @@ class Booking
return null;
}
/**
* Retrieves room assignment for a specific participant.
*
* Finds the room assigned to the specified participant
* based on the room mapping configuration.
*
* @param int $participantIndex The participant index to search for
*
* @return Room|null The matching room or null if not found
*/
public function getRoomForParticipant(int $participantIndex): ?Room
{
foreach ($this->rooms as $room) {
@@ -102,6 +171,16 @@ class Booking
return null;
}
/**
* Calculates the total price for a specific participant.
*
* Sums up all services, surcharges, and room costs assigned to the participant.
* Includes mandatory services that apply to all participants.
*
* @param int $participantIndex The participant index to calculate for
*
* @return float The total price for the participant
*/
public function getPriceForParticipant(int $participantIndex): float
{
$price = 0.0;
@@ -126,6 +205,15 @@ class Booking
return $price;
}
/**
* Retrieves surcharges for a specific participant.
*
* Filters surcharges based on participant index mapping.
*
* @param int $participantIndex The participant index to filter by
*
* @return array<Surcharge> The surcharges assigned to the participant
*/
public function getSurchargesForParticipant(int $participantIndex): array
{
return array_filter($this->surcharges, function (Surcharge $surcharge) use ($participantIndex) {
@@ -133,6 +221,13 @@ class Booking
});
}
/**
* Calculates the total price for all participants.
*
* Sums up the individual prices for all participants in the booking.
*
* @return float The calculated total price for all participants
*/
public function getCalculatedTotalPrice(): float
{
$totalPrice = 0.0;
@@ -144,6 +239,14 @@ class Booking
return $totalPrice;
}
/**
* Determines if the booking is editable.
*
* A booking is editable if the travel date is in the future and
* the status is not cancelled ('S') or unknown ('U').
*
* @return bool True if the booking can be edited, false otherwise
*/
public function isEditable(): bool
{
return $this->travelDate > new \DateTimeImmutable() && false === in_array($this->status, ['S', 'U']);
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a booking update response with validation and status information.
*
* This class handles booking update results including validation status,
* success indication, and status messages for API response handling.
*/
class BookingUpdate
{
public bool $valid = false;
+20 -3
View File
@@ -1,22 +1,39 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Represents communication information including email, phone, and newsletter preferences.
*
* This class handles contact information for users and participants in the booking system.
* It provides validation constraints for required fields and methods to convert
* communication data to API payload format.
*/
class Communication
{
public ?string $phone = '';
public string $phone = '';
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])]
public ?string $mobile = '';
public string $mobile = '';
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data'])]
#[Assert\Email(message: 'Bitte eine gültige Adresse angeben', mode: 'strict', groups: ['personal_data'])]
public ?string $email = '';
public string $email = '';
public bool $newsletter = false;
/**
* Converts the communication data to API payload format.
*
* Transforms the communication object into an array structure suitable
* for API communication with the BusProNet system.
*
* @return array<string, string> The payload array for API transmission
*/
public function toPayload(): array
{
return [
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a country with identification and nationality information.
*
* This class handles country data including unique identification,
* name, token, and associated nationality information.
*/
class Country
{
public ?int $id = null;
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a CRM action with configuration and state information.
*
* This class handles CRM action data including identification, labeling,
* mutability status, and selection state for user interface management.
*/
class CrmAction
{
public ?int $id = null;
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents CRM attributes with role and permission information.
*
* This class handles CRM system attributes including selection groups,
* actions, user roles, and hotel assignments for access control.
*/
class CrmAttributes
{
public ?array $selectionGroups = null;
+9
View File
@@ -1,9 +1,18 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Groups;
/**
* Represents a CRM selection with configuration and state information.
*
* This class handles CRM selection data including identification, labeling,
* mutability status, and selection state. It provides serialization groups
* for API responses.
*/
class CrmSelection
{
#[Groups(['api:single'])]
+26 -1
View File
@@ -1,16 +1,25 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Groups;
/**
* Represents a CRM selection group with included services mapping.
*
* This class handles CRM selection group data including identification,
* labeling, and selections. It provides methods to filter mutable selections
* and determine visibility, along with a static mapping for included services.
*/
class CrmSelectionGroup
{
/**
* Maps BusPro IDs of CRM selection groups representing
* included services.
*
* @var array|string[]
* @var array<string, string>
*/
public static array $includedServicesMapping = [
7 => 'Skipass',
@@ -32,6 +41,14 @@ class CrmSelectionGroup
#[Groups(['api:single'])]
public ?array $selections = null;
/**
* Retrieves only the mutable selections from the group.
*
* Filters the selections array to return only items that are marked
* as mutable for user interface management.
*
* @return array<CrmSelection> The mutable selections array
*/
public function getMutableSelections(): array
{
return array_filter($this->selections, function (CrmSelection $selection) {
@@ -39,6 +56,14 @@ class CrmSelectionGroup
});
}
/**
* Determines if the selection group should be visible.
*
* A group is considered visible if it contains at least one
* mutable selection for user interaction.
*
* @return bool True if the group has mutable selections, false otherwise
*/
#[Groups(['api:single'])]
public function isVisible(): bool
{
+12
View File
@@ -1,9 +1,21 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents error codes and messages for the BusProNet API.
*
* This class provides a static mapping of error codes to their corresponding
* German error messages for consistent error handling across the application.
* Error codes range from 100-999 and cover various API scenarios.
*/
class Error
{
/**
* @var array<int, string>
*/
private static array $errors = [
100 => 'Anfrageknoten fehlt',
101 => 'Satzknoten fehlt in Anfrageknoten',
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a file with content and metadata information.
*
* This class handles file data including filename, content, and MIME type
* for document storage and retrieval operations.
*/
class File
{
public function __construct(string $filename, string $content, string $mimeType)
+8
View File
@@ -1,9 +1,17 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Groups;
/**
* Represents a travel guide with contact information.
*
* This class handles guide data including name and contact details.
* It provides serialization groups for API responses in travel contexts.
*/
class Guide
{
#[Groups(['api:single'])]
+9
View File
@@ -1,9 +1,18 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Groups;
/**
* Represents a hotel with location and contact information.
*
* This class handles hotel data including identification, location details,
* and contact information. It provides serialization groups for API responses
* in travel and booking contexts.
*/
class Hotel
{
#[Groups(['api:list', 'api:single'])]
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents mutable data configuration with category and timing information.
*
* This class handles mutable data settings including category identification,
* mutability status, and timing constraints for booking modifications.
*/
class MutableData
{
public const CATEGORY_PARTICIPANT_COUNT = 'participant_count';
+17
View File
@@ -1,7 +1,16 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a notification with code and message information.
*
* This class handles notification data including error codes and messages
* for API response handling. It provides methods to determine if the
* notification represents an error condition.
*/
class Notification
{
public function __construct(int $code, string $message)
@@ -13,6 +22,14 @@ class Notification
public ?int $code;
public ?string $message;
/**
* Determines if the notification represents an error.
*
* Returns true if the notification code is not 650 (success code).
* Code 650 is used for successful operations in the BusProNet API.
*
* @return bool True if the notification is an error, false otherwise
*/
public function isError(): bool
{
return 650 !== $this->code;
+42 -14
View File
@@ -1,31 +1,43 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Represents personal data for a user including address and communication information.
*
* This class handles the storage and transformation of personal information
* including name, address, communication details, and physical attributes.
* It provides methods to convert data to API payload format and extract claims
* for authentication purposes.
*/
class PersonalData
{
private const DEFAULT_AGE_YEARS = 18;
public ?int $addressId = null;
public ?int $personId = null;
public bool $mutable = false;
public ?string $status = '';
public ?string $status = null;
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['Default', 'personal_data'])]
public ?string $name = '';
public string $name = '';
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['Default', 'personal_data'])]
public ?string $firstName = '';
public string $firstName = '';
public ?string $salutation = '';
public ?string $title = '';
public ?string $gender = '';
public ?string $nationality = '';
public ?string $height = '';
public ?string $shoeSize = '';
public ?string $weight = '';
public string $salutation = '';
public string $title = '';
public string $gender = '';
public string $nationality = '';
public string $height = '';
public string $shoeSize = '';
public string $weight = '';
public ?\DateTimeImmutable $dateOfBirth = null;
public ?string $remarks = '';
public string $remarks = '';
#[Assert\Valid(groups: ['personal_data'])]
public Address $address;
@@ -39,11 +51,19 @@ class PersonalData
$this->communication = new Communication();
}
/**
* Converts the personal data to API payload format.
*
* Transforms the personal data object into an array structure suitable
* for API communication with the BusProNet system.
*
* @return array<string, mixed> The payload array for API transmission
*/
public function toPayload(): array
{
// Ensure date of birth is populated
if (null === $dob = $this->dateOfBirth) {
$dob = new \DateTimeImmutable('18 years ago');
$dob = new \DateTimeImmutable(self::DEFAULT_AGE_YEARS . ' years ago');
}
return [
@@ -56,8 +76,8 @@ class PersonalData
'titel' => $this->title,
'vorname' => $this->firstName,
'name' => $this->name,
'anschrift' => $this->address?->toPayload(),
'kommunikation' => $this->communication?->toPayload(),
'anschrift' => $this->address->toPayload(),
'kommunikation' => $this->communication->toPayload(),
'sonstiges1' => $this->height,
'sonstiges2' => $this->weight,
'sonstiges3' => $this->shoeSize,
@@ -65,6 +85,14 @@ class PersonalData
];
}
/**
* Extracts user claims for authentication and profile information.
*
* Creates a structured array containing user profile information
* suitable for JWT claims or user session data.
*
* @return array<string, mixed> The claims array with user profile data
*/
public function getClaims(): array
{
return [
+9
View File
@@ -1,11 +1,20 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
/**
* Represents a pickup location with timing and pricing information.
*
* This class handles pickup point data including location details, scheduled times,
* pricing, and participant mapping. It provides serialization groups for API
* responses and booking management.
*/
class Pickup
{
#[Groups(['api:list'])]
+9
View File
@@ -1,11 +1,20 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
/**
* Represents a room configuration with capacity, pricing, and availability information.
*
* This class handles room data including category, board options, capacity limits,
* pricing structure, and participant mapping. It provides serialization groups
* for different API contexts and booking management.
*/
class Room
{
#[Groups(['api:list', 'api:single'])]
+9 -16
View File
@@ -1,29 +1,22 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
/**
* Represents a service offering with pricing, availability, and configuration options.
*
* This class handles various types of services including transportation, additional
* services, courses, and rentals. It provides constants for service categorization
* and status management, along with serialization groups for API responses.
*/
class Service
{
public const SOURCE_TRAVEL = 'travel';
public const SOURCE_BOOKING = 'booking';
public const CATEGORY_ADDITIONAL = 'additional';
public const CATEGORY_TRANSPORTATION = 'transportation';
public const TOKEN_COURSES = 'KUR';
public const TOKEN_SKI_PASS = 'SPA';
public const TOKEN_ADDITIONAL = 'SON';
public const TOKEN_BOARD = 'VPF';
public const TOKEN_RENTALS = ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8'];
public const STATUS_AVAILABLE = 'Frei';
public const STATUS_BLOCKED = 'Buchungsstop';
public const STATUS_ON_REQUEST = 'Anfrage';
#[Groups(['api:single', 'api:list'])]
public ?int $id = null;
+8
View File
@@ -1,7 +1,15 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Represents a surcharge with pricing and participant mapping.
*
* This class handles surcharge data including label, pricing structure,
* and participant mapping for booking calculations.
*/
class Surcharge
{
public ?string $label = null;
+62
View File
@@ -1,11 +1,20 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
/**
* Represents a travel package with services, accommodation, and pricing information.
*
* This class handles travel data including dates, hotel information, available services,
* transportation options, and participant configuration. It provides methods to filter
* and retrieve services by various criteria and manage included services.
*/
class Travel
{
#[Groups(['api:list', 'api:single'])]
@@ -76,6 +85,17 @@ class Travel
#[Groups(['api:single'])]
public ?Guide $guide = null;
/**
* Retrieves additional services filtered by group and availability.
*
* Filters additional services based on the provided group(s) and optionally
* by availability. Services are sorted alphabetically by label.
*
* @param mixed $group The service group(s) to filter by
* @param bool $availableOnly Whether to include only available services
*
* @return array<Service> The filtered and sorted services array
*/
public function getAdditionalServicesByGroup(mixed $group, bool $availableOnly = true): array
{
$group = (array) $group;
@@ -91,6 +111,17 @@ class Travel
return $services;
}
/**
* Retrieves transportation services filtered by direction and availability.
*
* Filters transportation services based on travel direction and optionally
* by availability. Services are sorted by subtype.
*
* @param string $direction The travel direction to filter by
* @param bool $availableOnly Whether to include only available services
*
* @return array<Service> The filtered and sorted transportation services
*/
public function getTransportationServicesByDirection(string $direction, bool $availableOnly = true): array
{
$services = array_filter($this->transportationServices, function (Service $service) use ($direction, $availableOnly) {
@@ -106,6 +137,14 @@ class Travel
return $services;
}
/**
* Retrieves included services from selection groups.
*
* Maps selection group IDs to their corresponding services based on
* the predefined included services mapping.
*
* @return array The included services from selection groups
*/
public function getIncludedServices(): array
{
$services = [];
@@ -118,4 +157,27 @@ class Travel
return $services;
}
/**
* Retrieves rooms filtered by their IDs.
*
* Filters the rooms array to return only rooms whose IDs match
* the provided array of IDs. Returns an empty array if no matching
* rooms are found.
*
* @param array<int> $ids The array of room IDs to filter by
*
* @return array<Room> The filtered rooms array
*/
public function getRoomsByIds(array $ids): array
{
if (empty($ids)) {
return [];
}
return array_filter($this->rooms, function (Room $room) use ($ids) {
return in_array($room->id, $ids, true);
});
}
}