feat: all-season assignments

This commit is contained in:
Björn Fromme authored and Björn Fromme committed 2026-09-26 12:13:16 +02:00
1 parent 675cec1b69
commit cf876ed224
119 files changed
+6194 -294

No files matched your search

+1 -5
View File
@@ -122,13 +122,9 @@ class AssignmentType extends AbstractType
;
},
])
->add('remarks', TextareaType::class, [
->add('remarks', AutosizeTextareaType::class, [
'label' => 'Anmerkungen intern',
'required' => false,
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
],
])
->add('pickup', ChoiceType::class, [
'label' => 'Busbegleitung ab',
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* A textarea that grows with its content, wiring up assets/controllers/textarea_autosize_controller.js.
*
* The controller overwrites the rows attribute on connect, so `rows` never had an effect on
* these fields - `min_rows` is the floor it actually reads.
*/
class AutosizeTextareaType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['min_rows' => 1]);
$resolver->setAllowedTypes('min_rows', 'int');
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
// Merged rather than set, and the caller's own attributes win: a field that needs a
// second Stimulus controller on the same element composes the data-controller list
// itself, which is the only case this can collide on.
$view->vars['attr'] = array_merge([
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
'data-textarea-autosize-min-rows-value' => $options['min_rows'],
], $view->vars['attr']);
}
public function getParent(): string
{
return TextareaType::class;
}
}
+2 -11
View File
@@ -4,7 +4,6 @@ namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
@@ -14,23 +13,15 @@ class DisableUserType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('disabledReason', TextareaType::class, [
->add('disabledReason', AutosizeTextareaType::class, [
'label' => 'Begründung',
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
],
'constraints' => [
new NotBlank(message: 'Bitte gib die Begründung ein'),
],
])
->add('disabledReasonInternal', TextareaType::class, [
->add('disabledReasonInternal', AutosizeTextareaType::class, [
'label' => 'Begründung intern',
'required' => false,
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
],
])
;
}
+1 -6
View File
@@ -6,7 +6,6 @@ use App\Entity\Disposition;
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\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
@@ -23,12 +22,8 @@ class DispositionCallOffType extends AbstractType
'E&P' => Disposition::CALLED_OFF_BY_OFFICE,
],
])
->add('calledOffReason', TextareaType::class, [
->add('calledOffReason', AutosizeTextareaType::class, [
'label' => 'Begründung',
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
],
'constraints' => [
new NotBlank(message: 'Bitte gib die Begründung ein'),
],
+2 -7
View File
@@ -6,7 +6,6 @@ use App\Config\EmailTextDefinition;
use App\Entity\EmailText;
use App\Validator\KnownPlaceholders;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
@@ -31,13 +30,9 @@ class EmailTextType extends AbstractType
'required' => false,
'constraints' => [$placeholders],
])
->add('body', TextareaType::class, [
->add('body', AutosizeTextareaType::class, [
'label' => 'Text',
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
'data-textarea-autosize-min-rows-value' => 12,
],
'min_rows' => 12,
'constraints' => [$placeholders],
])
;
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\Form;
use App\Config\HouseCatalog;
use App\Entity\JobProfile;
use App\Model\SeasonAssignmentFilterDto;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SeasonAssignmentFilterType extends AbstractType
{
public function __construct(private readonly HouseCatalog $houseCatalog)
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('teamerName', TextType::class, [
'label' => 'Name',
'required' => false,
])
// By code, not by name as the destination filters do: a season assignment stores
// the code itself, so this is an exact match rather than a substring guess.
->add('hotelCodes', MultiselectType::class, [
'label' => 'Haus',
'required' => false,
'empty_label' => 'nicht filtern',
'choices' => $this->houseCatalog->getCodeChoices(),
])
->add('jobProfiles', MultiselectEntityType::class, [
'label' => 'Job-Profile',
'class' => JobProfile::class,
'required' => false,
'choice_label' => 'name',
'empty_label' => 'nicht filtern',
'query_builder' => function (EntityRepository $repository) {
return $repository->createQueryBuilder('job_profile')
->orderBy('job_profile.name', \SortDirection::Ascending);
},
])
->add('includeDeleted', CheckboxType::class, [
'label' => 'gelöschte Saisoneinsätze anzeigen',
'required' => false,
])
// AbstractFilterHandler::handleRequest() branches on exactly these two names
->add('apply', SubmitType::class, [
'label' => 'filtern',
])
->add('reset', SubmitType::class, [
'label' => 'reset',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->setDefaults([
'data_class' => SeasonAssignmentFilterDto::class,
])
;
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Form;
use App\Config\HouseCatalog;
use App\Entity\JobProfile;
use App\Entity\SeasonAssignment;
use App\Entity\Teamer;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SeasonAssignmentType extends AbstractType
{
public function __construct(private readonly HouseCatalog $houseCatalog)
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// Optional: assignments are laid out first and staffed later. The endpoint only
// ever answers with all-season teamers, so a short term teamer cannot be picked
// here even by typing their full name.
->add('teamer', AutocompleteEntityType::class, [
'label' => 'Teamer:in',
'class' => Teamer::class,
'required' => false,
'placeholder' => 'noch nicht zugewiesen',
'label_property' => 'fullName',
'label_function' => fn (Teamer $teamer) => (string) $teamer,
'endpoint_route' => 'app_admin_autocomplete_season_teamer',
])
->add('dateFrom', DatepickerType::class, [
'label' => 'Datum von',
])
->add('dateTo', DatepickerType::class, [
'label' => 'Datum bis',
])
// The houses map is the whole list of places an all-season teamer can be placed, so
// a plain select over it needs neither an autocomplete endpoint nor a BusPro call.
->add('hotelCode', ChoiceType::class, [
'label' => 'Haus',
'placeholder' => 'Bitte auswählen',
'choices' => $this->houseCatalog->getCodeChoices(),
])
->add('jobProfile', EntityType::class, [
'label' => 'Jobprofil',
'class' => JobProfile::class,
'choice_label' => 'name',
'placeholder' => 'Bitte auswählen',
'query_builder' => function (EntityRepository $repository) {
return $repository->createQueryBuilder('job_profile')
->where('job_profile.deletedAt IS NULL')
->orderBy('job_profile.name', \SortDirection::Ascending);
},
])
->add('remarks', AutosizeTextareaType::class, [
'label' => 'Anmerkungen intern',
'required' => false,
'min_rows' => 4,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->setDefaults([
'data_class' => SeasonAssignment::class,
])
;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Form;
use App\Entity\SeasonAssignmentTask;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* The two deadline overrides of one checklist entry.
*
* Both are optional: an empty field means "not overridden", and the deadline falls back to what
* App\Service\Season\SeasonTaskDeadlineResolver computes from the destination's travel start.
*/
class SeasonTaskDeadlineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('deadlineOverride1', DatepickerType::class, [
'label' => 'Frist 1',
'required' => false,
])
->add('deadlineOverride2', DatepickerType::class, [
'label' => 'Frist 2',
'required' => false,
'help' => 'Ohne Angabe gilt Frist 1.',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => SeasonAssignmentTask::class,
]);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Form;
use App\Entity\SeasonTaskDocument;
use App\Entity\Upload;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SeasonTaskDocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('document', AutocompleteEntityType::class, [
'label' => 'Dokument',
'class' => Upload::class,
'label_property' => 'originalFilename',
'label_function' => fn (Upload $upload) => $upload->getDisplayName() ?? $upload->getOriginalFilename(),
'endpoint_route' => 'app_admin_autocomplete_document',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => SeasonTaskDocument::class,
]);
}
}
+1 -6
View File
@@ -4,7 +4,6 @@ namespace App\Form;
use App\Entity\Disposition;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -12,13 +11,9 @@ class SpecialAgreementsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('specialAgreements', TextareaType::class, [
$builder->add('specialAgreements', AutosizeTextareaType::class, [
'label' => 'zusätzliche Absprache',
'required' => false,
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
],
]);
}
+2 -7
View File
@@ -4,7 +4,6 @@ namespace App\Form;
use App\Model\TeamerMailingDto;
use Symfony\Component\Form\AbstractType;
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;
@@ -17,13 +16,9 @@ class TeamerMailingType extends AbstractType
->add('subject', TextType::class, [
'label' => 'Betreff',
])
->add('message', TextareaType::class, [
->add('message', AutosizeTextareaType::class, [
'label' => 'Nachricht',
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
'data-textarea-autosize-min-rows-value' => 12,
],
'min_rows' => 12,
])
;
}
+2 -11
View File
@@ -5,7 +5,6 @@ namespace App\Form;
use App\Entity\User;
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\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
@@ -30,23 +29,15 @@ class UserType extends AbstractType
'required' => false,
'help' => 'Gesperrte Benutzer:innen können sich nicht anmelden.',
])
->add('disabledReason', TextareaType::class, [
->add('disabledReason', AutosizeTextareaType::class, [
'label' => 'Begründung',
'required' => false,
'help' => 'Wird bei der Anmeldung angezeigt.',
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
],
])
->add('disabledReasonInternal', TextareaType::class, [
->add('disabledReasonInternal', AutosizeTextareaType::class, [
'label' => 'Begründung intern',
'required' => false,
'help' => 'Wird der Benutzer:in nicht angezeigt.',
'attr' => [
'data-controller' => 'textarea-autosize',
'data-action' => 'textarea-autosize#resize',
],
])
;