Add support for motive descendants in filters and improve Motive entity

- Added `getDescendants` method to `Motive` entity to retrieve all descendants recursively.
- Updated `TicketACLAwareRepository` to include descendants when filtering by motives.
- Updated API specification to clarify that motive descendants are included in query filters.
- Added unit tests for `Motive` entity to validate descendant logic.
This commit is contained in:
2025-09-24 21:48:15 +02:00
parent f50b7ee1cd
commit 914c741578
4 changed files with 88 additions and 3 deletions

View File

@@ -112,7 +112,7 @@ paths:
- no
- name: byMotives
in: query
description: the motives of the ticket
description: the motives of the ticket. All the descendants of the motive are taken into account.
required: false
style: form
explode: false

View File

@@ -195,4 +195,25 @@ class Motive
{
return $this->parent;
}
/**
* Get the descendants of the current entity.
*
* This method collects all descendant entities recursively, starting from the current entity
* and including all of its children and their descendants.
*
* @return ReadableCollection&Selectable A collection containing the current entity and all its descendants
*/
public function getDescendants(): ReadableCollection&Selectable
{
$collection = new ArrayCollection([$this]);
foreach ($this->getChildren() as $child) {
foreach ($child->getDescendants() as $descendant) {
$collection->add($descendant);
}
}
return $collection;
}
}

View File

@@ -113,10 +113,11 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor
if (array_key_exists('byMotives', $params)) {
$byMotives = $qb->expr()->orX();
foreach ($params['byMotives'] as $motive) {
$motivesWithDescendants = $motive->getDescendants()->toArray();
$byMotives->add(
$qb->expr()->exists(sprintf(
'SELECT 1 FROM %s tp_motive_%d WHERE tp_motive_%d.ticket = t
AND tp_motive_%d.motive = :motive_%d AND tp_motive_%d.endDate IS NULL
AND tp_motive_%d.motive IN (:motives_%d) AND tp_motive_%d.endDate IS NULL
',
MotiveHistory::class,
++$i,
@@ -126,7 +127,7 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor
$i,
))
);
$qb->setParameter(sprintf('motive_%d', $i), $motive);
$qb->setParameter(sprintf('motives_%d', $i), $motivesWithDescendants);
}
$qb->andWhere($byMotives);
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TicketBundle\Tests\Entity;
use Chill\TicketBundle\Entity\Motive;
use PHPUnit\Framework\TestCase;
/**
* @internal
*
* @covers \Chill\TicketBundle\Entity\Motive::getWithDescendants
*/
final class MotiveTest extends TestCase
{
public function testGetDescendantsOnLeafReturnsSelfOnly(): void
{
$leaf = new Motive();
$leaf->setLabel(['fr' => 'Feuille']);
$collection = $leaf->getDescendants();
self::assertCount(1, $collection);
self::assertSame($leaf, $collection->first());
self::assertContains($leaf, $collection->toArray());
}
public function testGetWithDescendantsReturnsSelfAndAllDescendants(): void
{
$parent = new Motive();
$parent->setLabel(['fr' => 'Parent']);
$childA = new Motive();
$childA->setLabel(['fr' => 'Enfant A']);
$childA->setParent($parent);
$childB = new Motive();
$childB->setLabel(['fr' => 'Enfant B']);
$childB->setParent($parent);
$grandChildA1 = new Motive();
$grandChildA1->setLabel(['fr' => 'Petit-enfant A1']);
$grandChildA1->setParent($childA);
$descendants = $parent->getDescendants();
$asArray = $descendants->toArray();
// It should contain the parent itself, both children and the grand child
self::assertCount(4, $descendants, 'Expected parent + 2 children + 1 grandchild');
self::assertContains($parent, $asArray);
self::assertContains($childA, $asArray);
self::assertContains($childB, $asArray);
self::assertContains($grandChildA1, $asArray);
}
}