feat: groups price calculator admin crud, booking/offer flow and api
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Model\AccommodationBookingDto;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<AccommodationBookingDto>
|
||||
*/
|
||||
class AccommodationBookingConfirmationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$termsLink = sprintf(
|
||||
'<a href="%s" target="_blank" rel="noopener" class="underline">allgemeinen Geschäftsbedingungen (AGB)</a>',
|
||||
htmlspecialchars($options['terms_url'], ENT_QUOTES, 'UTF-8')
|
||||
);
|
||||
|
||||
$builder->add('termsAccepted', CheckboxType::class, [
|
||||
'label' => sprintf('Ich habe die %s gelesen und bin damit einverstanden, dass eine kostenpflichtige Buchung zustande kommt.', $termsLink),
|
||||
'label_html' => true,
|
||||
'mapped' => false,
|
||||
'required' => true,
|
||||
'constraints' => [
|
||||
new IsTrue(message: 'Bitte akzeptiere die AGB, um die Buchung abzuschließen.'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AccommodationBookingDto::class,
|
||||
]);
|
||||
$resolver->setRequired('terms_url');
|
||||
$resolver->setAllowedTypes('terms_url', 'string');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<null>
|
||||
*/
|
||||
class AccommodationInquiryConfirmationType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_token_id' => 'groups_booking_step4_inquiry',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Model\AccommodationBookingDto;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<AccommodationBookingDto>
|
||||
*/
|
||||
class AccommodationStep2Type extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('paxCount', IntegerType::class, [
|
||||
'label' => 'Anzahl Personen',
|
||||
'attr' => ['min' => 1],
|
||||
])
|
||||
->add('minorsCount', IntegerType::class, [
|
||||
'label' => 'davon Kinder (0–3 Jahre)',
|
||||
'required' => false,
|
||||
'attr' => ['min' => 0],
|
||||
])
|
||||
->add('childrenCount', IntegerType::class, [
|
||||
'label' => sprintf('davon Kinder (4–%d Jahre)', $options['max_adolescent_age']),
|
||||
'required' => false,
|
||||
'attr' => ['min' => 0],
|
||||
])
|
||||
->add('selectedBoardServiceId', ChoiceType::class, [
|
||||
'label' => false,
|
||||
'choices' => $options['board_service_choices'],
|
||||
'expanded' => true,
|
||||
'multiple' => false,
|
||||
'required' => false,
|
||||
'placeholder' => 'Selbstversorgung',
|
||||
])
|
||||
->add('selectedAdditionalServiceIds', ChoiceType::class, [
|
||||
'label' => false,
|
||||
'choices' => $options['additional_service_choices'],
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event): void {
|
||||
$data = $event->getData();
|
||||
|
||||
if (!is_array($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (['minorsCount', 'childrenCount'] as $field) {
|
||||
if (!isset($data[$field]) || '' === $data[$field]) {
|
||||
$data[$field] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Board service rendered manually: empty string from the "Selbstversorgung" radio → null
|
||||
if (isset($data['selectedBoardServiceId']) && '' === $data['selectedBoardServiceId']) {
|
||||
unset($data['selectedBoardServiceId']);
|
||||
}
|
||||
|
||||
$event->setData($data);
|
||||
});
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AccommodationBookingDto::class,
|
||||
'validation_groups' => ['step_2'],
|
||||
'board_service_choices' => [],
|
||||
'additional_service_choices' => [],
|
||||
'max_adolescent_age' => 0,
|
||||
]);
|
||||
$resolver->setAllowedTypes('max_adolescent_age', 'int');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Model\AccommodationBookingDto;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<AccommodationBookingDto>
|
||||
*/
|
||||
class AccommodationStep3Type extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('groupName', TextType::class, [
|
||||
'label' => 'Name der Gruppe',
|
||||
])
|
||||
->add('salutation', ChoiceType::class, [
|
||||
'label' => 'Anrede',
|
||||
'choices' => [
|
||||
'Herr' => 'Herr',
|
||||
'Frau' => 'Frau',
|
||||
'divers' => 'divers',
|
||||
],
|
||||
'expanded' => false,
|
||||
'multiple' => false,
|
||||
'placeholder' => 'Bitte wählen',
|
||||
])
|
||||
->add('firstName', TextType::class, [
|
||||
'label' => 'Vorname',
|
||||
])
|
||||
->add('lastName', TextType::class, [
|
||||
'label' => 'Nachname',
|
||||
])
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'E-Mail-Adresse',
|
||||
])
|
||||
->add('phone', TextType::class, [
|
||||
'label' => 'Telefon',
|
||||
])
|
||||
->add('street', TextType::class, [
|
||||
'label' => 'Straße und Hausnummer',
|
||||
])
|
||||
->add('zip', TextType::class, [
|
||||
'label' => 'Postleitzahl',
|
||||
])
|
||||
->add('city', TextType::class, [
|
||||
'label' => 'Ort',
|
||||
])
|
||||
->add('remarks', TextareaType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
'attr' => ['rows' => 4],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AccommodationBookingDto::class,
|
||||
'validation_groups' => ['step_3'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Admin\Groups;
|
||||
|
||||
use App\Entity\Groups\Accommodation;
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Entity\Groups\AdditionalService;
|
||||
use App\Entity\Groups\BoardService;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Range;
|
||||
|
||||
/** @extends AbstractType<AccommodationBooking> */
|
||||
class AccommodationBookingType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if ($options['with_accommodation']) {
|
||||
$builder->add('accommodation', EntityType::class, [
|
||||
'class' => Accommodation::class,
|
||||
'choice_label' => 'name',
|
||||
'label' => 'Gruppenhaus',
|
||||
]);
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('groupName', TextType::class, [
|
||||
'label' => 'Gruppenname',
|
||||
])
|
||||
->add('salutation', ChoiceType::class, [
|
||||
'label' => 'Anrede',
|
||||
'choices' => [
|
||||
'Herr' => 'Herr',
|
||||
'Frau' => 'Frau',
|
||||
'divers' => 'divers',
|
||||
],
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
])
|
||||
->add('firstName', TextType::class, [
|
||||
'label' => 'Vorname',
|
||||
])
|
||||
->add('lastName', TextType::class, [
|
||||
'label' => 'Nachname',
|
||||
])
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'E-Mail',
|
||||
])
|
||||
->add('phone', TextType::class, [
|
||||
'label' => 'Telefon',
|
||||
'required' => false,
|
||||
])
|
||||
->add('street', TextType::class, [
|
||||
'label' => 'Straße',
|
||||
'required' => false,
|
||||
])
|
||||
->add('zip', TextType::class, [
|
||||
'label' => 'PLZ',
|
||||
'required' => false,
|
||||
])
|
||||
->add('city', TextType::class, [
|
||||
'label' => 'Ort',
|
||||
'required' => false,
|
||||
])
|
||||
->add('dateFrom', DateType::class, [
|
||||
'label' => 'Anreise',
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('dateTo', DateType::class, [
|
||||
'label' => 'Abreise',
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('paxCount', IntegerType::class, [
|
||||
'label' => 'Anzahl Personen',
|
||||
])
|
||||
->add('minorsCount', IntegerType::class, [
|
||||
'label' => 'davon Kinder (0–3 Jahre)',
|
||||
])
|
||||
->add('childrenCount', IntegerType::class, [
|
||||
'label' => $options['max_adolescent_age'] > 0
|
||||
? sprintf('davon Kinder (4–%d Jahre)', $options['max_adolescent_age'])
|
||||
: 'davon Kinder',
|
||||
])
|
||||
;
|
||||
|
||||
if (!empty($options['board_services'])) {
|
||||
$builder->add('boardService', EntityType::class, [
|
||||
'class' => BoardService::class,
|
||||
'mapped' => false,
|
||||
'required' => false,
|
||||
'choices' => $options['board_services'],
|
||||
'choice_label' => 'label',
|
||||
'placeholder' => 'keine',
|
||||
'label' => 'Verpflegung',
|
||||
'data' => $options['current_board_service'],
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($options['additional_services'])) {
|
||||
$builder->add('selectedAdditionalServices', EntityType::class, [
|
||||
'class' => AdditionalService::class,
|
||||
'mapped' => false,
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'choices' => $options['additional_services'],
|
||||
'choice_label' => 'label',
|
||||
'label' => 'Zusatzleistungen',
|
||||
'data' => $options['current_additional_services'],
|
||||
]);
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('isInquiry', CheckboxType::class, [
|
||||
'label' => 'Anfrage (nicht bindend)',
|
||||
'required' => false,
|
||||
])
|
||||
->add('accommodationDiscount', IntegerType::class, [
|
||||
'label' => 'Rabatt Unterkunft (%)',
|
||||
'required' => false,
|
||||
'constraints' => [
|
||||
new Range(min: 1, max: 100),
|
||||
],
|
||||
])
|
||||
->add('boardServiceDiscount', IntegerType::class, [
|
||||
'label' => 'Rabatt Verpflegung (%)',
|
||||
'required' => false,
|
||||
'constraints' => [
|
||||
new Range(min: 1, max: 100),
|
||||
],
|
||||
])
|
||||
->add('additionalServicesDiscount', IntegerType::class, [
|
||||
'label' => 'Rabatt Zusatzleistungen (%)',
|
||||
'required' => false,
|
||||
'constraints' => [
|
||||
new Range(min: 1, max: 100),
|
||||
],
|
||||
])
|
||||
->add('remarks', TextareaType::class, [
|
||||
'label' => 'Bemerkungen',
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AccommodationBooking::class,
|
||||
'with_accommodation' => false,
|
||||
'max_adolescent_age' => 0,
|
||||
'board_services' => [],
|
||||
'additional_services' => [],
|
||||
'current_board_service' => null,
|
||||
'current_additional_services' => [],
|
||||
]);
|
||||
$resolver->setAllowedTypes('with_accommodation', 'bool');
|
||||
$resolver->setAllowedTypes('max_adolescent_age', 'int');
|
||||
$resolver->setAllowedTypes('board_services', 'array');
|
||||
$resolver->setAllowedTypes('additional_services', 'array');
|
||||
$resolver->setAllowedTypes('current_board_service', ['null', BoardService::class]);
|
||||
$resolver->setAllowedTypes('current_additional_services', 'array');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Admin\Groups;
|
||||
|
||||
use App\Entity\Groups\AccommodationPrice;
|
||||
use App\Enum\Groups\PriceType;
|
||||
use App\Enum\Groups\Season;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/** @extends AbstractType<AccommodationPrice> */
|
||||
class AccommodationPriceType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('dateFrom', DateType::class, [
|
||||
'label' => 'Datum von',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('dateTo', DateType::class, [
|
||||
'label' => 'Datum bis',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('type', EnumType::class, [
|
||||
'label' => 'Typ',
|
||||
'class' => PriceType::class,
|
||||
'required' => false,
|
||||
'placeholder' => 'Standard',
|
||||
'choice_label' => fn($item) => $item->label(),
|
||||
])
|
||||
->add('season', EnumType::class, [
|
||||
'label' => 'Saison',
|
||||
'class' => Season::class,
|
||||
'choice_label' => fn($item) => $item->token(),
|
||||
])
|
||||
->add('includedPax', IntegerType::class, [
|
||||
'label' => 'Inklusiv-Personen',
|
||||
])
|
||||
->add('minNights', IntegerType::class, [
|
||||
'label' => 'Mindestbelegung (Nächte)',
|
||||
])
|
||||
->add('pricePerNight', MoneyType::class, [
|
||||
'label' => 'Preis pro Nacht',
|
||||
'currency' => $options['currency'],
|
||||
'divisor' => 100,
|
||||
])
|
||||
->add('priceAdditionalPerson', MoneyType::class, [
|
||||
'label' => 'Preis weitere Person',
|
||||
'currency' => $options['currency'],
|
||||
'divisor' => 100,
|
||||
])
|
||||
->add('acceptUndersubscription', CheckboxType::class, [
|
||||
'label' => 'Unterbelegung akzeptieren',
|
||||
'required' => false,
|
||||
])
|
||||
->add('acceptShortTerm', CheckboxType::class, [
|
||||
'label' => 'Kurzzeit-Belegung akzeptieren',
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AccommodationPrice::class,
|
||||
'currency' => 'EUR',
|
||||
]);
|
||||
$resolver->setAllowedValues('currency', ['EUR', 'CHF']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Admin\Groups;
|
||||
|
||||
use App\Entity\Groups\Accommodation;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/** @extends AbstractType<Accommodation> */
|
||||
class AccommodationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'Name',
|
||||
])
|
||||
->add('calendarCode', TextType::class, [
|
||||
'label' => 'Code für Kalender',
|
||||
])
|
||||
->add('cmsCode', TextType::class, [
|
||||
'label' => 'Code für CMS-Daten',
|
||||
'required' => false,
|
||||
])
|
||||
->add('maxAdolescentAge', IntegerType::class, [
|
||||
'label' => 'Altersgrenze Kinder',
|
||||
])
|
||||
->add('currency', ChoiceType::class, [
|
||||
'label' => 'Währung',
|
||||
'choices' => [
|
||||
'EUR' => 'EUR',
|
||||
'CHF' => 'CHF',
|
||||
],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Accommodation::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Admin\Groups;
|
||||
|
||||
use App\Entity\Groups\AdditionalService;
|
||||
use App\Enum\Groups\AdditionalServiceType as AdditionalServiceTypeEnum;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/** @extends AbstractType<AdditionalService> */
|
||||
class AdditionalServiceType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('label', TextType::class, [
|
||||
'label' => 'Bezeichnung',
|
||||
])
|
||||
->add('description', TextareaType::class, [
|
||||
'label' => 'Beschreibung',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'rows' => 2,
|
||||
],
|
||||
])
|
||||
->add('type', EnumType::class, [
|
||||
'label' => 'Preistyp',
|
||||
'class' => AdditionalServiceTypeEnum::class,
|
||||
'choice_label' => fn($item) => $item->label(),
|
||||
])
|
||||
->add('price', MoneyType::class, [
|
||||
'label' => 'Preis',
|
||||
'currency' => $options['currency'],
|
||||
'divisor' => 100,
|
||||
])
|
||||
->add('selectionGroup', TextType::class, [
|
||||
'label' => 'Auswahlgruppe',
|
||||
'required' => false,
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('dateFrom', DateType::class, [
|
||||
'label' => 'Datum von',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('dateTo', DateType::class, [
|
||||
'label' => 'Datum bis',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AdditionalService::class,
|
||||
'currency' => 'EUR',
|
||||
]);
|
||||
$resolver->setAllowedValues('currency', ['EUR', 'CHF']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Admin\Groups;
|
||||
|
||||
use App\Entity\Groups\BoardService;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/** @extends AbstractType<BoardService> */
|
||||
class BoardServiceType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('label', TextType::class, [
|
||||
'label' => 'Bezeichnung',
|
||||
])
|
||||
->add('description', TextareaType::class, [
|
||||
'label' => 'Beschreibung',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'rows' => 2,
|
||||
],
|
||||
])
|
||||
->add('price', MoneyType::class, [
|
||||
'label' => 'Preis pro Person/Nacht',
|
||||
'currency' => $options['currency'],
|
||||
'divisor' => 100,
|
||||
])
|
||||
->add('dateFrom', DateType::class, [
|
||||
'label' => 'Datum von',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('dateTo', DateType::class, [
|
||||
'label' => 'Datum bis',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => BoardService::class,
|
||||
'currency' => 'EUR',
|
||||
]);
|
||||
$resolver->setAllowedValues('currency', ['EUR', 'CHF']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/** @extends AbstractType<mixed> */
|
||||
class DatepickerType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'html5' => false,
|
||||
'widget' => 'single_text',
|
||||
'input' => 'datetime_immutable',
|
||||
'min_date' => null,
|
||||
'max_date' => null,
|
||||
'disable_weekends' => false,
|
||||
]);
|
||||
$resolver->setAllowedTypes('min_date', ['null', \DateTimeImmutable::class]);
|
||||
$resolver->setAllowedTypes('max_date', ['null', \DateTimeImmutable::class]);
|
||||
$resolver->setAllowedTypes('disable_weekends', 'bool');
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['min_date'] = $options['min_date'];
|
||||
$view->vars['max_date'] = $options['max_date'];
|
||||
$view->vars['disable_weekends'] = $options['disable_weekends'];
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return DateType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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 ModalSubmitExtension extends AbstractTypeExtension
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'hx_post' => null,
|
||||
'hx_target' => '#htmx-modal',
|
||||
'hx_swap' => 'outerHTML',
|
||||
]);
|
||||
}
|
||||
|
||||
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'],
|
||||
'hx-indicator' => '#htmx-modal-indicator',
|
||||
];
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], $attr);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [FormType::class];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class AccommodationBookingDto
|
||||
{
|
||||
public int $currentStep = 1;
|
||||
|
||||
public ?int $accommodationId = null;
|
||||
|
||||
public ?\DateTimeImmutable $dateFrom = null;
|
||||
|
||||
public ?\DateTimeImmutable $dateTo = null;
|
||||
|
||||
#[Assert\Positive(message: 'invalid', groups: ['step_3'])]
|
||||
public int $paxCount = 1;
|
||||
|
||||
#[Assert\PositiveOrZero(message: 'invalid', groups: ['step_3'])]
|
||||
public int $minorsCount = 0;
|
||||
|
||||
#[Assert\PositiveOrZero(message: 'invalid', groups: ['step_3'])]
|
||||
public int $childrenCount = 0;
|
||||
|
||||
public ?int $selectedBoardServiceId = null;
|
||||
|
||||
/** @var list<int>*/
|
||||
public array $selectedAdditionalServiceIds = [];
|
||||
|
||||
public bool $isInquiry = false;
|
||||
|
||||
/** @var string[] */
|
||||
public array $inquiryReasons = [];
|
||||
|
||||
public bool $forceInquiry = false;
|
||||
|
||||
public ?int $totalPrice = null;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $priceBreakdown = [];
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
public ?string $groupName = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
#[Assert\Choice(choices: ['Herr', 'Frau', 'divers'], groups: ['step_3'])]
|
||||
public ?string $salutation = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
public ?string $firstName = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
public ?string $lastName = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
#[Assert\Email(message: 'invalid', groups: ['step_3'])]
|
||||
public ?string $email = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
public ?string $phone = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
public ?string $street = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
public ?string $zip = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'required', groups: ['step_3'])]
|
||||
public ?string $city = null;
|
||||
|
||||
public ?string $remarks = null;
|
||||
|
||||
public function getNights(): int
|
||||
{
|
||||
if (null === $this->dateFrom || null === $this->dateTo) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->dateFrom->diff($this->dateTo)->days;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use App\Model\BookingSummaryCmsHotelData;
|
||||
use App\Model\CmsHotelData;
|
||||
|
||||
/**
|
||||
* DTO containing all booking summary data for sidebar display.
|
||||
@@ -19,7 +19,7 @@ class BookingSummaryDto
|
||||
public readonly int $participantCount,
|
||||
public readonly BookingSummaryPricingDto $pricing,
|
||||
public readonly BookingSummaryVoucherDto $vouchers,
|
||||
public readonly ?BookingSummaryCmsHotelData $cmsData,
|
||||
public readonly ?CmsHotelData $cmsData,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<null>
|
||||
*/
|
||||
class OfferAcceptConfirmationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$termsLink = sprintf(
|
||||
'<a href="%s" target="_blank" rel="noopener" class="underline">allgemeinen Geschäftsbedingungen (AGB)</a>',
|
||||
htmlspecialchars($options['terms_url'], ENT_QUOTES, 'UTF-8')
|
||||
);
|
||||
|
||||
$builder->add('termsAccepted', CheckboxType::class, [
|
||||
'label' => sprintf('Ich habe die %s gelesen und bin damit einverstanden, dass eine kostenpflichtige Buchung zustande kommt.', $termsLink),
|
||||
'label_html' => true,
|
||||
'mapped' => false,
|
||||
'required' => true,
|
||||
'constraints' => [
|
||||
new IsTrue(message: 'Bitte akzeptiere die AGB, um die Buchung abzuschließen.'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired('terms_url');
|
||||
$resolver->setAllowedTypes('terms_url', 'string');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user