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),
10,
[
'wrap-queries' => true, // Required for query with 'having' clause
'defaultSortFieldName' => 'destination.dateFrom',
'defaultSortDirection' => 'desc',
]
@@ -34,6 +34,7 @@ class AssignmentFilterController extends AbstractController
'max_date' => $filterOptions['maxDate'],
'job_profiles' => $filterOptions['jobProfiles'],
'hotels' => $filterOptions['hotels'],
'user' => $this->getUser(),
];
$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_CLOSED = 'closed';
public const STATUS_STAFFED = 'staffed';
public const STATUS_PARTLY_STAFFED = 'partly_staffed';
public const STATUS_STAFFING = 'staffing';
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
@@ -475,9 +479,8 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
&& 0 === $this->applications->count();
}
public function isInProgress(): bool
public function isStaffing(): bool
{
return $this->availableDispositions > $this->dispositions->count()
&& 0 < $this->applications->count();
return 0 === $this->dispositions->count() && 0 < $this->applications->count();
}
}
+27 -9
View File
@@ -5,6 +5,7 @@ namespace App\Form;
use App\Entity\Assignment;
use App\Entity\Destination;
use App\Entity\JobProfile;
use App\Entity\User;
use App\Model\AssignmentFilterDto;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
@@ -22,15 +23,22 @@ class AssignmentFilterType extends AbstractType
'label' => 'Zeitraum von',
'min_date' => $options['min_date'],
'max_date' => $options['max_date'],
'attr' => [
'placeholder' => 'nicht filtern',
],
])
->add('dateTo', DatepickerType::class, [
'label' => 'Zeitraum bis',
'min_date' => $options['min_date'],
'max_date' => $options['max_date'],
'attr' => [
'placeholder' => 'nicht filtern',
],
])
->add('duration', ChoiceType::class, [
'label' => 'Einsatzdauer',
'required' => false,
'placeholder' => 'nicht filtern',
'choices' => [
'Wochenende (2-4 Tage)' => AssignmentFilterDto::DURATION_WEEKEND,
'Midweek (5-6 Tage)' => AssignmentFilterDto::DURATION_MID_WEEK,
@@ -38,15 +46,6 @@ class AssignmentFilterType extends AbstractType
'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, [
'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'])) {
$builder
->add('jobProfile', EntityType::class, [
'label' => 'Job-Profil',
'class' => JobProfile::class,
'required' => false,
'placeholder' => 'nicht filtern',
'choice_label' => 'name',
'choices' => $options['job_profiles'],
])
@@ -75,6 +90,7 @@ class AssignmentFilterType extends AbstractType
->add('hotel', ChoiceType::class, [
'label' => 'Haus',
'required' => false,
'placeholder' => 'nicht filtern',
'choices' => $choices,
])
;
@@ -91,8 +107,10 @@ class AssignmentFilterType extends AbstractType
'job_profiles' => [],
'hotels' => [],
])
->setRequired(['user'])
->setAllowedTypes('min_date', [\DateTimeImmutable::class, 'null'])
->setAllowedTypes('max_date', [\DateTimeImmutable::class, 'null'])
->setAllowedTypes('user', User::class)
;
}
}
+17 -2
View File
@@ -40,6 +40,7 @@ class AssignmentRepository extends ServiceEntityRepository
->leftJoin('assignment.applications', 'application')
->leftJoin('assignment.dispositions', 'disposition')
->leftJoin('disposition.teamer', 'teamer')
->groupBy('assignment')
;
$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.dispositions', 'disposition', Join::WITH, $qb->expr()->eq('disposition.teamer', ':teamer'))
->groupBy('assignment')
->setParameter('teamer', $teamer)
->setParameter('now', new \DateTimeImmutable())
;
@@ -79,10 +81,23 @@ class AssignmentRepository extends ServiceEntityRepository
private function applyFilterSettings(AssignmentFilterDto $filterDto, QueryBuilder $qb): void
{
if (null !== $status = $filterDto->getStatus()) {
switch ($status) {
case Assignment::STATUS_STAFFED:
$qb->having('assignment.availableDispositions = COUNT(disposition.id)');
break;
case Assignment::STATUS_PARTLY_STAFFED:
$qb
->andWhere($qb->expr()->eq('assignment.status', ':status'))
->setParameter('status', $status)
->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()) {
@@ -76,7 +76,7 @@
<svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-yellow-500">
<circle cx="3" cy="3" r="2" />
</svg>
{% elseif assignment.inProgress %}
{% elseif assignment.staffing %}
<svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-blue-500">
<circle cx="3" cy="3" r="2" />
</svg>
@@ -4,8 +4,10 @@
{% block content %}
{{ 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">
{% if form.status is defined %}
{{ form_row(filterForm.status) }}
{% endif %}
{{ form_row(filterForm.dateFrom) }}
{{ form_row(filterForm.dateTo) }}
{{ form_row(filterForm.jobProfile) }}
+3 -4
View File
@@ -166,17 +166,16 @@
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder:red-500 focus:ring-red-500' }) -%}
{% 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 -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
<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') }}
{{ stimulus_target('datepicker', 'field') }} />
<input type="text" name="{{ form.vars.full_name }}" value="{{ value }}" {{ block('widget_attributes') }} {{ stimulus_target('datepicker', 'field') }} />
</div>
{%- 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 -%}
{%- endblock datepicker_widget %}