wip: initial commit

This commit is contained in:
Björn Fromme
2024-11-20 18:32:09 +01:00
parent ae964198e9
commit d4c4e69d09
91 changed files with 32685 additions and 144 deletions
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Form;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingData;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class BookingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('participants', CollectionType::class, [
'entry_type' => ParticipantType::class,
'entry_options' => [
'selectable_courses' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_COURSES),
'selectable_ski_passes' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_SKI_PASS),
'selectable_services' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_ADDITIONAL),
'selectable_board' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_BOARD),
'selectable_rentals' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_RENTALS),
'selectable_transportation_services_to' => $options['travel']
->getTransportationServicesByDirection('HIN'),
'selectable_transportation_services_fro' => $options['travel']
->getTransportationServicesByDirection('RUECK'),
],
'allow_add' => false,
'allow_delete' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->setDefaults([
'data_class' => BookingData::class,
'mutable_fields' => [],
])
->setRequired(['travel'])
->setAllowedTypes('travel', Travel::class)
;
}
}
@@ -0,0 +1,50 @@
<?php
namespace App\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class HtmxSubmitExtension extends AbstractTypeExtension
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'hx_post' => null,
'hx_target' => '#htmx-modal',
'hx_swap' => 'outerHTML',
'hx_indicator' => '#loading-indicator',
'hx_trigger' => null,
'hx_select' => null,
]);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
if (null !== $options['hx_post']) {
$attr = [
'hx-post' => $options['hx_post'],
'hx-target' => $options['hx_target'],
'hx-swap' => $options['hx_swap'],
];
if (null !== $options['hx_indicator']) {
$attr['hx-indicator'] = $options['hx_indicator'];
}
if (null !== $options['hx_trigger']) {
$attr['hx-trigger'] = $options['hx_trigger'];
}
if (null !== $options['hx_select']) {
$attr['hx-select'] = $options['hx_select'];
}
$view->vars['attr'] = array_merge($view->vars['attr'], $attr);
}
}
public static function getExtendedTypes(): iterable
{
return [FormType::class];
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Form\Model;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Service;
use Symfony\Component\Validator\Constraints as Assert;
class BookingData
{
public ?Booking $booking = null;
#[Assert\Valid]
public array $participants = [];
public static function fromBooking(Booking $booking): static
{
$instance = new static();
$instance->booking = $booking;
foreach ($booking->participants as $index => $participant) {
/** @var PersonalData $participant */
$participantData = ParticipantData::fromPersonalData($participant);
$participantData->index = $index;
$participantData->courses = $booking
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_COURSES);
$participantData->skiPass = $booking
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_SKI_PASS);
$participantData->additionalServices = $booking
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_ADDITIONAL);
$participantData->board = $booking
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_BOARD);
$participantData->rentals = $booking
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_RENTALS);
// Different keys for direction used in booking data (H <=> HIN, R <=> RUECK)!
$participantData->transportationServiceTo = $booking
->getTransportationServiceForParticipantAndDirection($index, 'H');
$participantData->transportationServiceFro = $booking
->getTransportationServiceForParticipantAndDirection($index, 'R');
$instance->participants[$index] = $participantData;
}
return $instance;
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
namespace App\Form\Model;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Service;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class ParticipantData
{
public ?int $index = null;
public ?int $addressId = null;
public ?int $personId = null;
public ?string $status = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $firstName = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $lastName = null;
public ?string $title = null;
public ?string $gender = null;
public ?string $nationality = null;
#[Assert\Range(notInRangeMessage: 'Bitte einen Wert im Bereich {{ min }}-{{ max }}cm angeben', min: 140, max: 210)]
public ?string $height = null;
#[Assert\Range(notInRangeMessage: 'Bitte einen Wert im Bereich {{ min }}-{{ max }} angeben', min: 35, max: 49)]
public ?string $shoeSize = null;
#[Assert\Range(notInRangeMessage: 'Bitte einen Wert im Bereich {{ min }}-{{ max }}kg angeben', min: 40, max: 130)]
public ?string $weight = null;
#[Assert\NotNull(message: 'Bitte angeben')]
public ?\DateTimeImmutable $dateOfBirth = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $email = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $mobile = null;
public array $courses = [];
public array $additionalServices = [];
public array $skiPass = [];
public array $board = [];
public array $rentals = [];
public ?Service $transportationServiceTo = null;
public ?Service $transportationServiceFro = null;
#[Assert\Callback]
public function assertBodyMeasurements(ExecutionContextInterface $context): void
{
if (0 === count($this->rentals)) {
return;
}
if (empty($this->height)) {
$context->buildViolation('Bitte angeben wegen Leihmaterial')
->atPath('height')
->addViolation()
;
}
if (empty($this->shoeSize)) {
$context->buildViolation('Bitte angeben Leihmaterial')
->atPath('shoeSize')
->addViolation()
;
}
if (empty($this->weight)) {
$context->buildViolation('Bitte angeben Leihmaterial')
->atPath('weight')
->addViolation()
;
}
}
public static function fromPersonalData(PersonalData $personalData): static
{
$instance = new static();
$instance->addressId = $personalData->addressId;
$instance->personId = $personalData->personId;
$instance->firstName = $personalData->firstName;
$instance->lastName = $personalData->name;
$instance->gender = $personalData->gender;
$instance->nationality = $personalData->nationality;
$instance->email = $personalData->communication->email;
$instance->mobile = $personalData->communication->mobile;
$instance->dateOfBirth = $personalData->dateOfBirth;
$instance->height = $personalData->height;
$instance->weight = $personalData->weight;
$instance->shoeSize = $personalData->shoeSize;
return $instance;
}
}
+160
View File
@@ -0,0 +1,160 @@
<?php
namespace App\Form;
use App\BusProNet\Form\CountryType;
use App\BusProNet\Model\Service;
use App\Form\Model\ParticipantData;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ParticipantType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('firstName', TextType::class, [
'label' => 'Vorname',
])
->add('lastName', TextType::class, [
'label' => 'Nachname',
])
->add('dateOfBirth', BirthdayType::class, [
'label' => 'Geburtsdatum',
'html5' => true,
'widget' => 'single_text',
'input' => 'datetime_immutable',
])
->add('gender', ChoiceType::class, [
'label' => 'Geschlecht',
'choices' => [
'männlich' => 'M',
'weiblich' => 'W',
'divers' => 'D',
],
])
->add('nationality', CountryType::class, [
'label' => 'Nationalität',
'property' => 'nationality',
'preferred_choices' => ['D', 'A', 'CH'],
])
->add('email', EmailType::class, [
'label' => 'Email',
])
->add('mobile', TextType::class, [
'label' => 'Telefon (mobil)',
])
->add('height', IntegerType::class, [
'label' => 'Körpergröße [cm]',
'required' => false,
])
->add('shoeSize', IntegerType::class, [
'label' => 'Schuhgröße',
'required' => false,
])
->add('weight', IntegerType::class, [
'label' => 'Gewicht [kg]',
'required' => false,
])
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) {
/** @var ParticipantData $participant */
$participant = $event->getData();
$participantIndex = $participant->index;
$form = $event->getForm();
$commonChoiceFieldOptions = [
'multiple' => true,
'expanded' => true,
'choice_value' => 'id',
'choice_label' => function (?Service $service) use ($participantIndex) {
if (null === $service) {
return null;
}
$price = $service->individualPrice[$participantIndex] ?? $service->price;
if (null === $price || 0.0 === $price) {
return $service->label;
}
return sprintf('%s (%s€)',
$service->label,
number_format($price, 2, ',', '.')
);
},
'choice_attr' => function (?Service $service) {
if (true === $service->mandatory) {
return [
'checked' => true,
'disabled' => true,
];
}
return [];
},
];
$form
->add('courses', ChoiceType::class, [
...$commonChoiceFieldOptions,
'label' => 'Kurse',
'choices' => $options['selectable_courses'],
])
->add('additionalServices', ChoiceType::class, [
...$commonChoiceFieldOptions,
'label' => 'Zusatzleistungen',
'choices' => $options['selectable_services'],
])
->add('skiPass', ChoiceType::class, [
...$commonChoiceFieldOptions,
'label' => 'Skipass',
'choices' => $options['selectable_ski_passes'],
])
->add('board', ChoiceType::class, [
...$commonChoiceFieldOptions,
'label' => 'Verpflegung',
'choices' => $options['selectable_board'],
])
->add('rentals', ChoiceType::class, [
...$commonChoiceFieldOptions,
'label' => 'Verleih',
'choices' => $options['selectable_rentals'],
])
->add('transportationServiceTo', ChoiceType::class, [
...$commonChoiceFieldOptions,
'label' => 'Anreise',
'multiple' => false,
'choices' => $options['selectable_transportation_services_to'],
])
->add('transportationServiceFro', ChoiceType::class, [
...$commonChoiceFieldOptions,
'label' => 'Rückreise',
'multiple' => false,
'choices' => $options['selectable_transportation_services_fro'],
])
;
})
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ParticipantData::class,
'selectable_courses' => [],
'selectable_ski_passes' => [],
'selectable_services' => [],
'selectable_board' => [],
'selectable_rentals' => [],
'selectable_transportation_services_to' => [],
'selectable_transportation_services_fro' => [],
]);
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace App\Form;
use App\BusProNet\Form\CountryType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class PersonalDataType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('gender', ChoiceType::class, [
'label' => 'Gender',
'choices' => [
'M' => 'M',
'W' => 'W',
'D' => 'D',
],
])
->add('firstName', TextType::class, [
'label' => 'Name',
])
->add('name', TextType::class, [
'label' => 'Nachname',
])
->add('dateOfBirth', BirthdayType::class, [
'label' => 'Geburtsdatum',
'html5' => true,
'widget' => 'single_text',
'input' => 'datetime_immutable',
])
->add('street', TextType::class, [
'label' => 'Straße',
'property_path' => 'address.street',
])
->add('postCode', TextType::class, [
'label' => 'PLZ',
'property_path' => 'address.postCode',
])
->add('city', TextType::class, [
'label' => 'Stadt',
'property_path' => 'address.city',
])
->add('country', CountryType::class, [
'label' => 'Land',
'property_path' => 'address.country',
])
->add('email', EmailType::class, [
'label' => 'E-Mail',
'property_path' => 'communication.email',
])
->add('phone', TextType::class, [
'label' => 'Telefon',
'property_path' => 'communication.phone',
])
->add('mobile', TextType::class, [
'label' => 'Mobil',
'property_path' => 'communication.mobile',
])
;
}
}