feat: filter assignments by assignment status

This commit is contained in:
Björn Fromme
2024-03-01 12:02:48 +01:00
parent f722afe7ff
commit a6436f1513
8 changed files with 62 additions and 23 deletions
@@ -36,6 +36,7 @@ class IndexController extends AbstractController
$request->query->getInt('page', 1), $request->query->getInt('page', 1),
10, 10,
[ [
'wrap-queries' => true, // Required for query with 'having' clause
'defaultSortFieldName' => 'destination.dateFrom', 'defaultSortFieldName' => 'destination.dateFrom',
'defaultSortDirection' => 'desc', 'defaultSortDirection' => 'desc',
] ]
@@ -34,6 +34,7 @@ class AssignmentFilterController extends AbstractController
'max_date' => $filterOptions['maxDate'], 'max_date' => $filterOptions['maxDate'],
'job_profiles' => $filterOptions['jobProfiles'], 'job_profiles' => $filterOptions['jobProfiles'],
'hotels' => $filterOptions['hotels'], 'hotels' => $filterOptions['hotels'],
'user' => $this->getUser(),
]; ];
$form = $this->createForm(AssignmentFilterType::class, $formData, $formOptions); $form = $this->createForm(AssignmentFilterType::class, $formData, $formOptions);
+6 -3
View File
@@ -25,6 +25,10 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
public const STATUS_OPEN = 'open'; public const STATUS_OPEN = 'open';
public const STATUS_CLOSED = 'closed'; public const STATUS_CLOSED = 'closed';
public const STATUS_STAFFED = 'staffed';
public const STATUS_PARTLY_STAFFED = 'partly_staffed';
public const STATUS_STAFFING = 'staffing';
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
@@ -475,9 +479,8 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
&& 0 === $this->applications->count(); && 0 === $this->applications->count();
} }
public function isInProgress(): bool public function isStaffing(): bool
{ {
return $this->availableDispositions > $this->dispositions->count() return 0 === $this->dispositions->count() && 0 < $this->applications->count();
&& 0 < $this->applications->count();
} }
} }
+27 -9
View File
@@ -5,6 +5,7 @@ namespace App\Form;
use App\Entity\Assignment; use App\Entity\Assignment;
use App\Entity\Destination; use App\Entity\Destination;
use App\Entity\JobProfile; use App\Entity\JobProfile;
use App\Entity\User;
use App\Model\AssignmentFilterDto; use App\Model\AssignmentFilterDto;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@@ -22,15 +23,22 @@ class AssignmentFilterType extends AbstractType
'label' => 'Zeitraum von', 'label' => 'Zeitraum von',
'min_date' => $options['min_date'], 'min_date' => $options['min_date'],
'max_date' => $options['max_date'], 'max_date' => $options['max_date'],
'attr' => [
'placeholder' => 'nicht filtern',
],
]) ])
->add('dateTo', DatepickerType::class, [ ->add('dateTo', DatepickerType::class, [
'label' => 'Zeitraum bis', 'label' => 'Zeitraum bis',
'min_date' => $options['min_date'], 'min_date' => $options['min_date'],
'max_date' => $options['max_date'], 'max_date' => $options['max_date'],
'attr' => [
'placeholder' => 'nicht filtern',
],
]) ])
->add('duration', ChoiceType::class, [ ->add('duration', ChoiceType::class, [
'label' => 'Einsatzdauer', 'label' => 'Einsatzdauer',
'required' => false, 'required' => false,
'placeholder' => 'nicht filtern',
'choices' => [ 'choices' => [
'Wochenende (2-4 Tage)' => AssignmentFilterDto::DURATION_WEEKEND, 'Wochenende (2-4 Tage)' => AssignmentFilterDto::DURATION_WEEKEND,
'Midweek (5-6 Tage)' => AssignmentFilterDto::DURATION_MID_WEEK, 'Midweek (5-6 Tage)' => AssignmentFilterDto::DURATION_MID_WEEK,
@@ -38,15 +46,6 @@ class AssignmentFilterType extends AbstractType
'Mehr als einen Woche' => AssignmentFilterDto::DURATION_MORE, 'Mehr als einen Woche' => AssignmentFilterDto::DURATION_MORE,
], ],
]) ])
->add('status', ChoiceType::class, [
'label' => 'Status',
'required' => false,
'choices' => [
'Entwurf' => Assignment::STATUS_DRAFT,
'offen' => Assignment::STATUS_OPEN,
'geschlossen' => Assignment::STATUS_CLOSED,
],
])
->add('apply', SubmitType::class, [ ->add('apply', SubmitType::class, [
'label' => 'filtern', 'label' => 'filtern',
]) ])
@@ -55,12 +54,28 @@ class AssignmentFilterType extends AbstractType
]) ])
; ;
/** @var User $user */
$user = $options['user'];
if ($user->hasRole('ROLE_ADMINISTRATIVE')) {
$builder->add('status', ChoiceType::class, [
'label' => 'Status',
'required' => false,
'placeholder' => 'nicht filtern',
'choices' => [
'voll besetzt' => Assignment::STATUS_STAFFED,
'teilweise besetzt' => Assignment::STATUS_PARTLY_STAFFED,
'mit Bewerbungen' => Assignment::STATUS_STAFFING,
],
]);
}
if (0 < count($options['job_profiles'])) { if (0 < count($options['job_profiles'])) {
$builder $builder
->add('jobProfile', EntityType::class, [ ->add('jobProfile', EntityType::class, [
'label' => 'Job-Profil', 'label' => 'Job-Profil',
'class' => JobProfile::class, 'class' => JobProfile::class,
'required' => false, 'required' => false,
'placeholder' => 'nicht filtern',
'choice_label' => 'name', 'choice_label' => 'name',
'choices' => $options['job_profiles'], 'choices' => $options['job_profiles'],
]) ])
@@ -75,6 +90,7 @@ class AssignmentFilterType extends AbstractType
->add('hotel', ChoiceType::class, [ ->add('hotel', ChoiceType::class, [
'label' => 'Haus', 'label' => 'Haus',
'required' => false, 'required' => false,
'placeholder' => 'nicht filtern',
'choices' => $choices, 'choices' => $choices,
]) ])
; ;
@@ -91,8 +107,10 @@ class AssignmentFilterType extends AbstractType
'job_profiles' => [], 'job_profiles' => [],
'hotels' => [], 'hotels' => [],
]) ])
->setRequired(['user'])
->setAllowedTypes('min_date', [\DateTimeImmutable::class, 'null']) ->setAllowedTypes('min_date', [\DateTimeImmutable::class, 'null'])
->setAllowedTypes('max_date', [\DateTimeImmutable::class, 'null']) ->setAllowedTypes('max_date', [\DateTimeImmutable::class, 'null'])
->setAllowedTypes('user', User::class)
; ;
} }
} }
+19 -4
View File
@@ -40,6 +40,7 @@ class AssignmentRepository extends ServiceEntityRepository
->leftJoin('assignment.applications', 'application') ->leftJoin('assignment.applications', 'application')
->leftJoin('assignment.dispositions', 'disposition') ->leftJoin('assignment.dispositions', 'disposition')
->leftJoin('disposition.teamer', 'teamer') ->leftJoin('disposition.teamer', 'teamer')
->groupBy('assignment')
; ;
$this->applyFilterSettings($filterDto, $qb); $this->applyFilterSettings($filterDto, $qb);
@@ -67,6 +68,7 @@ class AssignmentRepository extends ServiceEntityRepository
)) ))
->leftJoin('assignment.applications', 'application', Join::WITH, $qb->expr()->eq('application.teamer', ':teamer')) ->leftJoin('assignment.applications', 'application', Join::WITH, $qb->expr()->eq('application.teamer', ':teamer'))
->leftJoin('assignment.dispositions', 'disposition', Join::WITH, $qb->expr()->eq('disposition.teamer', ':teamer')) ->leftJoin('assignment.dispositions', 'disposition', Join::WITH, $qb->expr()->eq('disposition.teamer', ':teamer'))
->groupBy('assignment')
->setParameter('teamer', $teamer) ->setParameter('teamer', $teamer)
->setParameter('now', new \DateTimeImmutable()) ->setParameter('now', new \DateTimeImmutable())
; ;
@@ -79,10 +81,23 @@ class AssignmentRepository extends ServiceEntityRepository
private function applyFilterSettings(AssignmentFilterDto $filterDto, QueryBuilder $qb): void private function applyFilterSettings(AssignmentFilterDto $filterDto, QueryBuilder $qb): void
{ {
if (null !== $status = $filterDto->getStatus()) { if (null !== $status = $filterDto->getStatus()) {
$qb switch ($status) {
->andWhere($qb->expr()->eq('assignment.status', ':status')) case Assignment::STATUS_STAFFED:
->setParameter('status', $status) $qb->having('assignment.availableDispositions = COUNT(disposition.id)');
; break;
case Assignment::STATUS_PARTLY_STAFFED:
$qb
->having('assignment.availableDispositions > COUNT(disposition.id)')
->andHaving('COUNT(application.id) = 0')
;
break;
case Assignment::STATUS_STAFFING:
$qb
->having('COUNT(disposition.id) = 0')
->andHaving('COUNT(application.id) > 0')
;
break;
}
} }
if (null !== $dateFrom = $filterDto->getDateFrom()) { if (null !== $dateFrom = $filterDto->getDateFrom()) {
@@ -76,7 +76,7 @@
<svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-yellow-500"> <svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-yellow-500">
<circle cx="3" cy="3" r="2" /> <circle cx="3" cy="3" r="2" />
</svg> </svg>
{% elseif assignment.inProgress %} {% elseif assignment.staffing %}
<svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-blue-500"> <svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-blue-500">
<circle cx="3" cy="3" r="2" /> <circle cx="3" cy="3" r="2" />
</svg> </svg>
@@ -4,8 +4,10 @@
{% block content %} {% block content %}
{{ form_start(filterForm, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }} {{ form_start(filterForm, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }}
<div class="flex flex-col space-y-2 pb-2"> <div class="flex flex-col space-y-2 pb-4">
{{ form_row(filterForm.status) }} {% if form.status is defined %}
{{ form_row(filterForm.status) }}
{% endif %}
{{ form_row(filterForm.dateFrom) }} {{ form_row(filterForm.dateFrom) }}
{{ form_row(filterForm.dateTo) }} {{ form_row(filterForm.dateTo) }}
{{ form_row(filterForm.jobProfile) }} {{ form_row(filterForm.jobProfile) }}
+3 -4
View File
@@ -166,17 +166,16 @@
{%- if errors|length -%} {%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder:red-500 focus:ring-red-500' }) -%} {%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder:red-500 focus:ring-red-500' }) -%}
{% else %} {% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-400 focus:ring-primary' }) -%} {%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-gray-900 focus:ring-primary' }) -%}
{%- endif -%} {%- endif -%}
{%- if disabled is defined and disabled == true -%} {%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%} {%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%} {%- endif -%}
<div {{ stimulus_controller('datepicker', { 'minDate': minDate, 'maxDate': maxDate, 'disableWeekends': form.vars.disable_weekends }) }}> <div {{ stimulus_controller('datepicker', { 'minDate': minDate, 'maxDate': maxDate, 'disableWeekends': form.vars.disable_weekends }) }}>
<input type="text" name="{{ form.vars.full_name }}" value="{{ value }}" {{ block('widget_attributes') }} <input type="text" name="{{ form.vars.full_name }}" value="{{ value }}" {{ block('widget_attributes') }} {{ stimulus_target('datepicker', 'field') }} />
{{ stimulus_target('datepicker', 'field') }} />
</div> </div>
{%- if disabled is defined and disabled == true -%} {%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}"> <input type="hidden" name="{{ form.vars.full_name }}" value="{{ value }}">
{%- endif -%} {%- endif -%}
{%- endblock datepicker_widget %} {%- endblock datepicker_widget %}