feat: split nights off an accommodation booking as separate periods
addresses #869f6z36v
This commit is contained in:
1 parent
972839286c
commit
7001bc0cf2
35 files changed
+2221
-25
No files matched your search
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Admin\Groups;
|
||||
|
||||
use App\Form\Model\AccommodationBookingPeriodLineDto;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/** @extends AbstractType<AccommodationBookingPeriodLineDto> */
|
||||
class AccommodationBookingPeriodLineType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('label', TextType::class, [
|
||||
'label' => 'Bezeichnung',
|
||||
])
|
||||
->add('amount', MoneyType::class, [
|
||||
'label' => 'Betrag',
|
||||
'currency' => $options['currency'],
|
||||
'divisor' => 100,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AccommodationBookingPeriodLineDto::class,
|
||||
'empty_data' => static fn () => new AccommodationBookingPeriodLineDto(),
|
||||
'currency' => 'EUR',
|
||||
]);
|
||||
$resolver->setAllowedTypes('currency', 'string');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Admin\Groups;
|
||||
|
||||
use App\Form\Model\AccommodationBookingPeriodDto;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
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<AccommodationBookingPeriodDto> */
|
||||
class AccommodationBookingPeriodType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('label', TextType::class, [
|
||||
'label' => 'Bezeichnung',
|
||||
'required' => false,
|
||||
'help' => 'Überschrift in der Preisaufstellung, z. B. „Vorausteam“. Ohne Bezeichnung steht dort nur der Zeitraum.',
|
||||
])
|
||||
->add('nights', IntegerType::class, [
|
||||
'label' => 'Nächte',
|
||||
'attr' => ['min' => 1],
|
||||
])
|
||||
->add('lines', CollectionType::class, [
|
||||
'label' => 'Positionen',
|
||||
'entry_type' => AccommodationBookingPeriodLineType::class,
|
||||
'entry_options' => ['label' => false, 'currency' => $options['currency']],
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'by_reference' => false,
|
||||
'prototype' => true,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => AccommodationBookingPeriodDto::class,
|
||||
'currency' => 'EUR',
|
||||
]);
|
||||
$resolver->setAllowedTypes('currency', 'string');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Entity\Groups\AccommodationBookingPeriod;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* What the split and edit modals submit for a split-off period. Whether the group keeps a
|
||||
* night for itself depends on the booking, so that is left to the booking's own validation.
|
||||
*/
|
||||
class AccommodationBookingPeriodDto
|
||||
{
|
||||
/**
|
||||
* @param array<int, AccommodationBookingPeriodLineDto> $lines keyed as the form submits them, so not necessarily a list
|
||||
*/
|
||||
public function __construct(
|
||||
#[Assert\NotNull(message: 'Bitte die Anzahl der Nächte angeben.')]
|
||||
#[Assert\Range(min: 1, max: AccommodationBooking::MAX_NIGHTS)]
|
||||
public ?int $nights = 1,
|
||||
#[Assert\Valid]
|
||||
public array $lines = [],
|
||||
#[Assert\Length(max: 255)]
|
||||
public ?string $label = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromPeriod(AccommodationBookingPeriod $period): self
|
||||
{
|
||||
return new self(
|
||||
$period->getNights(),
|
||||
array_map(
|
||||
fn (array $line) => new AccommodationBookingPeriodLineDto($line['label'], $line['amount']),
|
||||
$period->getLines(),
|
||||
),
|
||||
$period->getLabel(),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return list<array{label: string, amount: int}> */
|
||||
public function linesSnapshot(): array
|
||||
{
|
||||
return array_values(array_map(
|
||||
fn (AccommodationBookingPeriodLineDto $line) => ['label' => (string) $line->label, 'amount' => (int) $line->amount],
|
||||
$this->lines,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/** One free-form price line of a split-off period, as the office keys it in. */
|
||||
class AccommodationBookingPeriodLineDto
|
||||
{
|
||||
public function __construct(
|
||||
#[Assert\NotBlank(message: 'Bitte eine Bezeichnung angeben.')]
|
||||
#[Assert\Length(max: 255)]
|
||||
public ?string $label = null,
|
||||
/** In minor units of the booking's pricing currency. */
|
||||
#[Assert\NotNull(message: 'Bitte einen Betrag angeben.')]
|
||||
#[Assert\PositiveOrZero]
|
||||
public ?int $amount = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user