feat: enable entering additional remarks in offer confirmation

This commit is contained in:
Björn Fromme
2026-08-17 15:30:17 +02:00
parent 582e54ddc4
commit a5d97f1206
15 changed files with 387 additions and 20 deletions
@@ -10,9 +10,11 @@ use App\Enum\Groups\AccommodationBookingStatus;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\EnumType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -57,6 +59,13 @@ class AccommodationBookingCreateType extends AbstractType
],
'choice_label' => fn (AccommodationBookingStatus $status) => $status->label(),
])
// Not marked required in HTML: a draft may be saved without it, and the
// validation group below enforces it for everything else.
->add('email', EmailType::class, [
'label' => 'E-Mail',
'required' => false,
'help' => 'Für ein Angebot erforderlich — der Zugangslink und alle Benachrichtigungen gehen an diese Adresse.',
])
;
}
@@ -64,6 +73,15 @@ class AccommodationBookingCreateType extends AbstractType
{
$resolver->setDefaults([
'data_class' => AccommodationBooking::class,
// A draft is a scratch record, but anything the customer can be pointed at
// needs a reachable address — see the `offer` group on the entity. The rest of
// the contact data is collected on the edit page, which validates `edit`.
'validation_groups' => static function (FormInterface $form): array {
/** @var AccommodationBooking $booking */
$booking = $form->getData();
return $booking->isDraft() ? ['Default'] : ['Default', 'offer'];
},
]);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Carries what the offer acceptance modal submits: the AGB confirmation and an
* optional remark. `remarks` is prefilled from the booking, so whatever comes back
* is the full value the customer wants stored — an emptied textarea means "clear it".
*/
class OfferAcceptDto
{
public function __construct(
#[Assert\IsTrue(message: 'Bitte akzeptiere die AGB, um die Buchung abzuschließen.')]
public bool $termsAccepted = false,
#[Assert\Length(max: 2000, maxMessage: 'Die Anmerkungen dürfen maximal {{ limit }} Zeichen lang sein.')]
public ?string $remarks = null,
) {
}
}
+21 -11
View File
@@ -4,14 +4,15 @@ declare(strict_types=1);
namespace App\Form;
use App\Form\Model\OfferAcceptDto;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
/**
* @extends AbstractType<null>
* @extends AbstractType<OfferAcceptDto>
*/
class OfferAcceptConfirmationType extends AbstractType
{
@@ -22,19 +23,28 @@ class OfferAcceptConfirmationType extends AbstractType
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.'),
],
]);
$builder
->add('remarks', TextareaType::class, [
'label' => 'Anmerkungen (optional)',
'required' => false,
'attr' => [
'rows' => 4,
'maxlength' => 2000,
],
])
->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,
'required' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => OfferAcceptDto::class,
]);
$resolver->setRequired('terms_url');
$resolver->setAllowedTypes('terms_url', 'string');
}