From ed41d40059d24082fdcc9aa01474fc5303caab88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 25 Feb 2026 19:22:49 +0100 Subject: [PATCH] feat: filter assignments and applications by teamer name closes #869b9vgq7 --- src/Form/ApplicationFilterType.php | 6 ++++ src/Form/AssignmentFilterType.php | 6 ++++ src/Model/ApplicationFilterDto.php | 13 +++++++++ src/Model/AssignmentFilterDto.php | 13 +++++++++ src/Repository/ApplicationRepository.php | 14 +++++++++ src/Repository/AssignmentRepository.php | 29 +++++++++++++++++++ .../Common/ApplicationFilterHandler.php | 4 +++ .../Common/AssignmentFilterHandler.php | 4 +++ .../common/modal_application_filter.html.twig | 11 +++++-- .../common/modal_assignment_filter.html.twig | 11 +++++-- 10 files changed, 105 insertions(+), 6 deletions(-) diff --git a/src/Form/ApplicationFilterType.php b/src/Form/ApplicationFilterType.php index 915752b..048a2a5 100644 --- a/src/Form/ApplicationFilterType.php +++ b/src/Form/ApplicationFilterType.php @@ -11,6 +11,7 @@ use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; 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; @@ -88,6 +89,11 @@ class ApplicationFilterType extends AbstractType 'label' => 'ID', 'required' => false, ]) + ->add('teamerName', TextType::class, [ + 'label' => 'Teamer:in', + 'required' => false, + 'empty_data' => null, + ]) ->add('status', MultiselectType::class, [ 'label' => 'Status', 'required' => false, diff --git a/src/Form/AssignmentFilterType.php b/src/Form/AssignmentFilterType.php index d24370a..4b730f1 100644 --- a/src/Form/AssignmentFilterType.php +++ b/src/Form/AssignmentFilterType.php @@ -11,6 +11,7 @@ use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; 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; @@ -92,6 +93,11 @@ class AssignmentFilterType extends AbstractType 'label' => 'ID', 'required' => false, ]) + ->add('teamerName', TextType::class, [ + 'label' => 'Teamer:in', + 'required' => false, + 'empty_data' => null, + ]) ->add('status', MultiselectType::class, [ 'label' => 'Status', 'required' => false, diff --git a/src/Model/ApplicationFilterDto.php b/src/Model/ApplicationFilterDto.php index bc681bd..0e96b07 100644 --- a/src/Model/ApplicationFilterDto.php +++ b/src/Model/ApplicationFilterDto.php @@ -18,6 +18,7 @@ class ApplicationFilterDto extends AbstractFilterDto protected array $status = []; protected bool $includePast = false; protected ?string $country = null; + protected ?string $teamerName = null; public function getId(): ?int { @@ -126,4 +127,16 @@ class ApplicationFilterDto extends AbstractFilterDto return $this; } + + public function getTeamerName(): ?string + { + return $this->teamerName; + } + + public function setTeamerName(?string $teamerName): static + { + $this->teamerName = $teamerName; + + return $this; + } } diff --git a/src/Model/AssignmentFilterDto.php b/src/Model/AssignmentFilterDto.php index 44d19fd..2117924 100644 --- a/src/Model/AssignmentFilterDto.php +++ b/src/Model/AssignmentFilterDto.php @@ -18,6 +18,7 @@ class AssignmentFilterDto extends AbstractFilterDto protected array $status = []; protected bool $includePast = false; protected ?string $country = null; + protected ?string $teamerName = null; protected bool $notSyncedWithBusProNet = false; public function getId(): ?int @@ -128,6 +129,18 @@ class AssignmentFilterDto extends AbstractFilterDto return $this; } + public function getTeamerName(): ?string + { + return $this->teamerName; + } + + public function setTeamerName(?string $teamerName): static + { + $this->teamerName = $teamerName; + + return $this; + } + public function isNotSyncedWithBusProNet(): bool { return $this->notSyncedWithBusProNet; diff --git a/src/Repository/ApplicationRepository.php b/src/Repository/ApplicationRepository.php index 83e7878..f857c1f 100644 --- a/src/Repository/ApplicationRepository.php +++ b/src/Repository/ApplicationRepository.php @@ -7,6 +7,7 @@ use App\Entity\Assignment; use App\Entity\Teamer; use App\Model\ApplicationFilterDto; use App\Model\AssignmentFilterDto; +use App\Repository\Traits\QueryHelperTrait; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; @@ -21,6 +22,8 @@ use Doctrine\Persistence\ManagerRegistry; */ class ApplicationRepository extends ServiceEntityRepository { + use QueryHelperTrait; + public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Application::class); @@ -109,6 +112,17 @@ class ApplicationRepository extends ServiceEntityRepository ; } + $teamerName = $filterDto->getTeamerName(); + if (null !== $teamerName && '' !== trim($teamerName)) { + $qb + ->andWhere($qb->expr()->orX( + $qb->expr()->like('user.firstName', ':teamerName'), + $qb->expr()->like('user.lastName', ':teamerName') + )) + ->setParameter('teamerName', '%'.$this->escapeLikeWildcards($teamerName).'%') + ; + } + return $qb->getQuery(); } diff --git a/src/Repository/AssignmentRepository.php b/src/Repository/AssignmentRepository.php index a81f8af..8aa5270 100644 --- a/src/Repository/AssignmentRepository.php +++ b/src/Repository/AssignmentRepository.php @@ -4,9 +4,11 @@ namespace App\Repository; use App\Entity\Application; use App\Entity\Assignment; +use App\Entity\Disposition; use App\Entity\JobProfile; use App\Entity\Teamer; use App\Model\AssignmentFilterDto; +use App\Repository\Traits\QueryHelperTrait; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\Query; use Doctrine\ORM\Query\Expr\Join; @@ -23,6 +25,8 @@ use Doctrine\Persistence\ManagerRegistry; */ class AssignmentRepository extends ServiceEntityRepository { + use QueryHelperTrait; + public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Assignment::class); @@ -194,6 +198,31 @@ class AssignmentRepository extends ServiceEntityRepository ; } + $teamerName = $filterDto->getTeamerName(); + if (null !== $teamerName && '' !== trim($teamerName)) { + $subQb = $this->getEntityManager()->createQueryBuilder(); + $subQb + ->select('1') + ->from(Disposition::class, 'disposition_filter') + ->innerJoin('disposition_filter.teamer', 'teamer_filter') + ->innerJoin('teamer_filter.user', 'user_filter') + ->where($subQb->expr()->andX( + $subQb->expr()->eq('disposition_filter.assignment', 'assignment'), + $subQb->expr()->neq('disposition_filter.status', ':dispositionCalledOffStatus'), + $subQb->expr()->orX( + $subQb->expr()->like('user_filter.firstName', ':teamerName'), + $subQb->expr()->like('user_filter.lastName', ':teamerName') + ) + )) + ; + + $qb + ->andWhere($qb->expr()->exists($subQb->getDQL())) + ->setParameter('dispositionCalledOffStatus', Disposition::STATUS_CALLED_OFF) + ->setParameter('teamerName', '%'.$this->escapeLikeWildcards($teamerName).'%') + ; + } + if (true === $filterDto->isNotSyncedWithBusProNet()) { $qb ->andWhere($qb->expr()->eq('disposition.syncedWithBusProNet', ':syncedWithBusProNet')) diff --git a/src/Service/Common/ApplicationFilterHandler.php b/src/Service/Common/ApplicationFilterHandler.php index e586d4f..e9900bb 100644 --- a/src/Service/Common/ApplicationFilterHandler.php +++ b/src/Service/Common/ApplicationFilterHandler.php @@ -51,6 +51,9 @@ class ApplicationFilterHandler extends AbstractFilterHandler if (isset($data['country'])) { $filterDto->setCountry($data['country']); } + if (isset($data['teamer_name'])) { + $filterDto->setTeamerName($data['teamer_name']); + } return $filterDto; } @@ -70,6 +73,7 @@ class ApplicationFilterHandler extends AbstractFilterHandler 'status' => $filterDto->getStatus(), 'include_past' => $filterDto->isIncludePast(), 'country' => $filterDto->getCountry(), + 'teamer_name' => $filterDto->getTeamerName(), ]); } } diff --git a/src/Service/Common/AssignmentFilterHandler.php b/src/Service/Common/AssignmentFilterHandler.php index add9b7f..4c5d9c2 100644 --- a/src/Service/Common/AssignmentFilterHandler.php +++ b/src/Service/Common/AssignmentFilterHandler.php @@ -51,6 +51,9 @@ class AssignmentFilterHandler extends AbstractFilterHandler if (isset($data['country'])) { $filterDto->setCountry($data['country']); } + if (isset($data['teamer_name'])) { + $filterDto->setTeamerName($data['teamer_name']); + } if (isset($data['not_synced_with_bus_pro_net'])) { $filterDto->setNotSyncedWithBusProNet((bool) $data['not_synced_with_bus_pro_net']); } @@ -73,6 +76,7 @@ class AssignmentFilterHandler extends AbstractFilterHandler 'status' => $filterDto->getStatus(), 'include_past' => $filterDto->isIncludePast(), 'country' => $filterDto->getCountry(), + 'teamer_name' => $filterDto->getTeamerName(), 'not_synced_with_bus_pro_net' => $filterDto->isNotSyncedWithBusProNet(), ]); } diff --git a/templates/common/modal_application_filter.html.twig b/templates/common/modal_application_filter.html.twig index 9cb2d4b..1b97e3b 100644 --- a/templates/common/modal_application_filter.html.twig +++ b/templates/common/modal_application_filter.html.twig @@ -5,9 +5,14 @@ {% block content %} {{ form_start(filterForm, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }}
- {% if filterForm.id is defined %} - {{ form_row(filterForm.id) }} - {% endif %} +
+ {% if filterForm.id is defined %} + {{ form_row(filterForm.id) }} + {% endif %} + {% if filterForm.teamerName is defined %} + {{ form_row(filterForm.teamerName) }} + {% endif %} +
{% if filterForm.status is defined %} {{ form_row(filterForm.status) }} {% endif %} diff --git a/templates/common/modal_assignment_filter.html.twig b/templates/common/modal_assignment_filter.html.twig index abf6b70..95ba8cc 100644 --- a/templates/common/modal_assignment_filter.html.twig +++ b/templates/common/modal_assignment_filter.html.twig @@ -5,9 +5,14 @@ {% block content %} {{ form_start(filterForm, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }}
- {% if filterForm.id is defined %} - {{ form_row(filterForm.id) }} - {% endif %} +
+ {% if filterForm.id is defined %} + {{ form_row(filterForm.id) }} + {% endif %} + {% if filterForm.teamerName is defined %} + {{ form_row(filterForm.teamerName) }} + {% endif %} +
{% if filterForm.status is defined %} {{ form_row(filterForm.status) }} {% endif %}