feat: filter assignments and applications by teamer name

closes #869b9vgq7
This commit is contained in:
Björn Fromme
2026-03-15 12:42:28 +01:00
parent e45f6458d4
commit ed41d40059
10 changed files with 105 additions and 6 deletions
+6
View File
@@ -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\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -88,6 +89,11 @@ class ApplicationFilterType extends AbstractType
'label' => 'ID', 'label' => 'ID',
'required' => false, 'required' => false,
]) ])
->add('teamerName', TextType::class, [
'label' => 'Teamer:in',
'required' => false,
'empty_data' => null,
])
->add('status', MultiselectType::class, [ ->add('status', MultiselectType::class, [
'label' => 'Status', 'label' => 'Status',
'required' => false, 'required' => false,
+6
View File
@@ -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\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -92,6 +93,11 @@ class AssignmentFilterType extends AbstractType
'label' => 'ID', 'label' => 'ID',
'required' => false, 'required' => false,
]) ])
->add('teamerName', TextType::class, [
'label' => 'Teamer:in',
'required' => false,
'empty_data' => null,
])
->add('status', MultiselectType::class, [ ->add('status', MultiselectType::class, [
'label' => 'Status', 'label' => 'Status',
'required' => false, 'required' => false,
+13
View File
@@ -18,6 +18,7 @@ class ApplicationFilterDto extends AbstractFilterDto
protected array $status = []; protected array $status = [];
protected bool $includePast = false; protected bool $includePast = false;
protected ?string $country = null; protected ?string $country = null;
protected ?string $teamerName = null;
public function getId(): ?int public function getId(): ?int
{ {
@@ -126,4 +127,16 @@ class ApplicationFilterDto extends AbstractFilterDto
return $this; return $this;
} }
public function getTeamerName(): ?string
{
return $this->teamerName;
}
public function setTeamerName(?string $teamerName): static
{
$this->teamerName = $teamerName;
return $this;
}
} }
+13
View File
@@ -18,6 +18,7 @@ class AssignmentFilterDto extends AbstractFilterDto
protected array $status = []; protected array $status = [];
protected bool $includePast = false; protected bool $includePast = false;
protected ?string $country = null; protected ?string $country = null;
protected ?string $teamerName = null;
protected bool $notSyncedWithBusProNet = false; protected bool $notSyncedWithBusProNet = false;
public function getId(): ?int public function getId(): ?int
@@ -128,6 +129,18 @@ class AssignmentFilterDto extends AbstractFilterDto
return $this; return $this;
} }
public function getTeamerName(): ?string
{
return $this->teamerName;
}
public function setTeamerName(?string $teamerName): static
{
$this->teamerName = $teamerName;
return $this;
}
public function isNotSyncedWithBusProNet(): bool public function isNotSyncedWithBusProNet(): bool
{ {
return $this->notSyncedWithBusProNet; return $this->notSyncedWithBusProNet;
+14
View File
@@ -7,6 +7,7 @@ use App\Entity\Assignment;
use App\Entity\Teamer; use App\Entity\Teamer;
use App\Model\ApplicationFilterDto; use App\Model\ApplicationFilterDto;
use App\Model\AssignmentFilterDto; use App\Model\AssignmentFilterDto;
use App\Repository\Traits\QueryHelperTrait;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
@@ -21,6 +22,8 @@ use Doctrine\Persistence\ManagerRegistry;
*/ */
class ApplicationRepository extends ServiceEntityRepository class ApplicationRepository extends ServiceEntityRepository
{ {
use QueryHelperTrait;
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry)
{ {
parent::__construct($registry, Application::class); 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(); return $qb->getQuery();
} }
+29
View File
@@ -4,9 +4,11 @@ namespace App\Repository;
use App\Entity\Application; use App\Entity\Application;
use App\Entity\Assignment; use App\Entity\Assignment;
use App\Entity\Disposition;
use App\Entity\JobProfile; use App\Entity\JobProfile;
use App\Entity\Teamer; use App\Entity\Teamer;
use App\Model\AssignmentFilterDto; use App\Model\AssignmentFilterDto;
use App\Repository\Traits\QueryHelperTrait;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\Query\Expr\Join;
@@ -23,6 +25,8 @@ use Doctrine\Persistence\ManagerRegistry;
*/ */
class AssignmentRepository extends ServiceEntityRepository class AssignmentRepository extends ServiceEntityRepository
{ {
use QueryHelperTrait;
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry)
{ {
parent::__construct($registry, Assignment::class); 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()) { if (true === $filterDto->isNotSyncedWithBusProNet()) {
$qb $qb
->andWhere($qb->expr()->eq('disposition.syncedWithBusProNet', ':syncedWithBusProNet')) ->andWhere($qb->expr()->eq('disposition.syncedWithBusProNet', ':syncedWithBusProNet'))
@@ -51,6 +51,9 @@ class ApplicationFilterHandler extends AbstractFilterHandler
if (isset($data['country'])) { if (isset($data['country'])) {
$filterDto->setCountry($data['country']); $filterDto->setCountry($data['country']);
} }
if (isset($data['teamer_name'])) {
$filterDto->setTeamerName($data['teamer_name']);
}
return $filterDto; return $filterDto;
} }
@@ -70,6 +73,7 @@ class ApplicationFilterHandler extends AbstractFilterHandler
'status' => $filterDto->getStatus(), 'status' => $filterDto->getStatus(),
'include_past' => $filterDto->isIncludePast(), 'include_past' => $filterDto->isIncludePast(),
'country' => $filterDto->getCountry(), 'country' => $filterDto->getCountry(),
'teamer_name' => $filterDto->getTeamerName(),
]); ]);
} }
} }
@@ -51,6 +51,9 @@ class AssignmentFilterHandler extends AbstractFilterHandler
if (isset($data['country'])) { if (isset($data['country'])) {
$filterDto->setCountry($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'])) { if (isset($data['not_synced_with_bus_pro_net'])) {
$filterDto->setNotSyncedWithBusProNet((bool) $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(), 'status' => $filterDto->getStatus(),
'include_past' => $filterDto->isIncludePast(), 'include_past' => $filterDto->isIncludePast(),
'country' => $filterDto->getCountry(), 'country' => $filterDto->getCountry(),
'teamer_name' => $filterDto->getTeamerName(),
'not_synced_with_bus_pro_net' => $filterDto->isNotSyncedWithBusProNet(), 'not_synced_with_bus_pro_net' => $filterDto->isNotSyncedWithBusProNet(),
]); ]);
} }
@@ -5,9 +5,14 @@
{% 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-4"> <div class="flex flex-col space-y-2 pb-4">
{% if filterForm.id is defined %} <div class="grid grid-cols-2 gap-x-4 gap-y-2">
{{ form_row(filterForm.id) }} {% if filterForm.id is defined %}
{% endif %} {{ form_row(filterForm.id) }}
{% endif %}
{% if filterForm.teamerName is defined %}
{{ form_row(filterForm.teamerName) }}
{% endif %}
</div>
{% if filterForm.status is defined %} {% if filterForm.status is defined %}
{{ form_row(filterForm.status) }} {{ form_row(filterForm.status) }}
{% endif %} {% endif %}
@@ -5,9 +5,14 @@
{% 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-4"> <div class="flex flex-col space-y-2 pb-4">
{% if filterForm.id is defined %} <div class="grid grid-cols-2 gap-x-4 gap-y-2">
{{ form_row(filterForm.id) }} {% if filterForm.id is defined %}
{% endif %} {{ form_row(filterForm.id) }}
{% endif %}
{% if filterForm.teamerName is defined %}
{{ form_row(filterForm.teamerName) }}
{% endif %}
</div>
{% if filterForm.status is defined %} {% if filterForm.status is defined %}
{{ form_row(filterForm.status) }} {{ form_row(filterForm.status) }}
{% endif %} {% endif %}