Merge branch '641_issues_with_children' into '111_exports_suite'

Issue641, improve query,  issues and actions with descendants

See merge request Chill-Projet/chill-bundles!460
This commit is contained in:
Mathieu Jaumotte 2022-10-18 12:50:35 +00:00
commit 93713f2ac2
23 changed files with 490 additions and 230 deletions

View File

@ -14,10 +14,9 @@ namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Form\Type\PickSocialActionType;
use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
@ -37,22 +36,17 @@ class BySocialActionFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$where = $qb->getDQLPart('where');
if (!in_array('actsocialaction', $qb->getAllAliases(), true)) { if (!in_array('actsocialaction', $qb->getAllAliases(), true)) {
$qb->join('activity.socialActions', 'actsocialaction'); $qb->join('activity.socialActions', 'actsocialaction');
} }
$clause = $qb->expr()->in('actsocialaction.id', ':socialactions'); $clause = $qb->expr()->in('actsocialaction.id', ':socialactions');
if ($where instanceof Andx) { $qb->andWhere($clause)
$where->add($clause); ->setParameter(
} else { 'socialactions',
$where = $qb->expr()->andX($clause); SocialAction::getDescendantsWithThisForActions($data['accepted_socialactions'])
} );
$qb->add('where', $where);
$qb->setParameter('socialactions', $data['accepted_socialactions']);
} }
public function applyOn(): string public function applyOn(): string
@ -62,13 +56,8 @@ class BySocialActionFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_socialactions', EntityType::class, [ $builder->add('accepted_socialactions', PickSocialActionType::class, [
'class' => SocialAction::class,
'choice_label' => function (SocialAction $sa) {
return $this->actionRender->renderString($sa, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
]); ]);
} }
@ -76,8 +65,10 @@ class BySocialActionFilter implements FilterInterface
{ {
$actions = []; $actions = [];
foreach ($data['accepted_socialactions'] as $sa) { foreach ($data['accepted_socialactions'] as $action) {
$actions[] = $this->actionRender->renderString($sa, []); $actions[] = $this->actionRender->renderString($action, [
'show_and_children' => true,
]);
} }
return ['Filtered activity by linked socialaction: only %actions%', [ return ['Filtered activity by linked socialaction: only %actions%', [

View File

@ -14,10 +14,9 @@ namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Form\Type\PickSocialIssueType;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
@ -37,22 +36,17 @@ class BySocialIssueFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$where = $qb->getDQLPart('where');
if (!in_array('actsocialissue', $qb->getAllAliases(), true)) { if (!in_array('actsocialissue', $qb->getAllAliases(), true)) {
$qb->join('activity.socialIssues', 'actsocialissue'); $qb->join('activity.socialIssues', 'actsocialissue');
} }
$clause = $qb->expr()->in('actsocialissue.id', ':socialissues'); $clause = $qb->expr()->in('actsocialissue.id', ':socialissues');
if ($where instanceof Andx) { $qb->andWhere($clause)
$where->add($clause); ->setParameter(
} else { 'socialissues',
$where = $qb->expr()->andX($clause); SocialIssue::getDescendantsWithThisForIssues($data['accepted_socialissues'])
} );
$qb->add('where', $where);
$qb->setParameter('socialissues', $data['accepted_socialissues']);
} }
public function applyOn(): string public function applyOn(): string
@ -62,13 +56,8 @@ class BySocialIssueFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_socialissues', EntityType::class, [ $builder->add('accepted_socialissues', PickSocialIssueType::class, [
'class' => SocialIssue::class,
'choice_label' => function (SocialIssue $si) {
return $this->issueRender->renderString($si, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
]); ]);
} }
@ -76,8 +65,10 @@ class BySocialIssueFilter implements FilterInterface
{ {
$issues = []; $issues = [];
foreach ($data['accepted_socialissues'] as $si) { foreach ($data['accepted_socialissues'] as $issue) {
$issues[] = $this->issueRender->renderString($si, []); $issues[] = $this->issueRender->renderString($issue, [
'show_and_children' => true,
]);
} }
return ['Filtered activity by linked socialissue: only %issues%', [ return ['Filtered activity by linked socialissue: only %issues%', [

View File

@ -13,7 +13,7 @@ namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Select2LocationTypeType; use Chill\MainBundle\Form\Type\PickLocationTypeType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -60,7 +60,7 @@ class LocationTypeFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_locationtype', Select2LocationTypeType::class, [ $builder->add('accepted_locationtype', PickLocationTypeType::class, [
'multiple' => true, 'multiple' => true,
//'label' => false, //'label' => false,
]); ]);

View File

@ -15,7 +15,7 @@ use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Entity\GroupCenter; use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ComposedGroupCenterType; use Chill\MainBundle\Form\Type\ComposedGroupCenterType;
use Chill\MainBundle\Form\Type\Select2UserLocationType; use Chill\MainBundle\Form\UserCurrentLocationType;
use Chill\MainBundle\Form\UserPasswordType; use Chill\MainBundle\Form\UserPasswordType;
use Chill\MainBundle\Form\UserType; use Chill\MainBundle\Form\UserType;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
@ -234,7 +234,7 @@ class UserController extends CRUDController
public function editCurrentLocationAction(Request $request) public function editCurrentLocationAction(Request $request)
{ {
$user = $this->getUser(); $user = $this->getUser();
$form = $this->createForm(Select2UserLocationType::class, $user) $form = $this->createForm(UserCurrentLocationType::class, $user)
->add('submit', SubmitType::class, ['label' => 'Save']) ->add('submit', SubmitType::class, ['label' => 'Save'])
->handleRequest($request); ->handleRequest($request);

View File

@ -0,0 +1,50 @@
<?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\MainBundle\Form\Type;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PickLocationTypeType extends AbstractType
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'class' => LocationType::class,
'choice_label' => function (LocationType $type) {
return $this->translatableStringHelper->localize($type->getTitle());
},
'placeholder' => 'Pick a location type',
'required' => false,
'attr' => ['class' => 'select2'],
'label' => 'Location type',
'multiple' => false,
])
->setAllowedTypes('multiple', ['bool']);
}
public function getParent(): string
{
return EntityType::class;
}
}

View File

@ -16,10 +16,9 @@ use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
class Select2UserLocationType extends AbstractType class PickUserLocationType extends AbstractType
{ {
private LocationRepository $locationRepository; private LocationRepository $locationRepository;
@ -31,10 +30,10 @@ class Select2UserLocationType extends AbstractType
$this->locationRepository = $locationRepository; $this->locationRepository = $locationRepository;
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function configureOptions(OptionsResolver $resolver)
{ {
$builder $resolver
->add('currentLocation', EntityType::class, [ ->setDefaults([
'class' => Location::class, 'class' => Location::class,
'choices' => $this->locationRepository->findByPublicLocations(), 'choices' => $this->locationRepository->findByPublicLocations(),
'choice_label' => function (Location $entity) { 'choice_label' => function (Location $entity) {
@ -44,24 +43,15 @@ class Select2UserLocationType extends AbstractType
}, },
'placeholder' => 'Pick a location', 'placeholder' => 'Pick a location',
'required' => false, 'required' => false,
'label' => $options['label'],
'label_attr' => $options['label_attr'],
'multiple' => $options['multiple'],
'attr' => ['class' => 'select2'], 'attr' => ['class' => 'select2'],
]); 'label' => 'Current location',
} 'multiple' => false,
])
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('label', 'Current location')
->setDefault('label_attr', [])
->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool']); ->setAllowedTypes('multiple', ['bool']);
} }
public function getBlockPrefix(): string public function getParent(): string
{ {
return 'select2_user_location_type'; return EntityType::class;
} }
} }

View File

@ -1,59 +0,0 @@
<?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\MainBundle\Form\Type;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class Select2LocationTypeType extends AbstractType
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('locationtype', EntityType::class, [
'class' => LocationType::class,
'choice_label' => function (LocationType $type) {
return $this->translatableStringHelper->localize($type->getTitle());
},
'placeholder' => 'Pick a location type',
'required' => false,
'label' => $options['label'],
'label_attr' => $options['label_attr'],
'multiple' => $options['multiple'],
'attr' => ['class' => 'select2'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('label', 'Location type')
->setDefault('label_attr', [])
->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool']);
}
public function getBlockPrefix(): string
{
return 'select2_location_type_type';
}
}

View File

@ -0,0 +1,24 @@
<?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\MainBundle\Form;
use Chill\MainBundle\Form\Type\PickUserLocationType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class UserCurrentLocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('currentLocation', PickUserLocationType::class);
}
}

View File

@ -130,14 +130,6 @@ services:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Chill\MainBundle\Form\Type\Select2UserLocationType:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\Type\Select2LocationTypeType:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\Type\LocationFormType: ~ Chill\MainBundle\Form\Type\LocationFormType: ~
Chill\MainBundle\Form\WorkflowStepType: ~ Chill\MainBundle\Form\WorkflowStepType: ~

View File

@ -228,6 +228,22 @@ class SocialAction
return $descendants; return $descendants;
} }
/**
* @param Collection|SocialAction[] $socialActions
*/
public static function getDescendantsWithThisForActions($socialActions): Collection
{
$unique = [];
foreach ($socialActions as $action) {
foreach ($action->getDescendantsWithThis() as $child) {
$unique[spl_object_hash($child)] = $child;
}
}
return new ArrayCollection(array_values($unique));
}
public function getEvaluations(): Collection public function getEvaluations(): Collection
{ {
return $this->evaluations; return $this->evaluations;
@ -274,6 +290,11 @@ class SocialAction
return $this->title; return $this->title;
} }
public function hasChildren(): bool
{
return 0 < $this->getChildren()->count();
}
public function hasParent(): bool public function hasParent(): bool
{ {
return $this->getParent() instanceof self; return $this->getParent() instanceof self;
@ -369,6 +390,8 @@ class SocialAction
{ {
$this->parent = $parent; $this->parent = $parent;
$parent->addChild($this);
return $this; return $this;
} }

View File

@ -71,11 +71,17 @@ class SocialIssue
$this->socialActions = new ArrayCollection(); $this->socialActions = new ArrayCollection();
} }
/**
* @internal use @see{SocialIssue::setParent} instead
*
* @param SocialIssue $child
*
* @return $this
*/
public function addChild(self $child): self public function addChild(self $child): self
{ {
if (!$this->children->contains($child)) { if (!$this->children->contains($child)) {
$this->children[] = $child; $this->children[] = $child;
$child->setParent($this);
} }
return $this; return $this;
@ -215,6 +221,22 @@ class SocialIssue
return $descendants; return $descendants;
} }
/**
* @param array|SocialIssue[] $socialIssues
*/
public static function getDescendantsWithThisForIssues(array $socialIssues): Collection
{
$unique = [];
foreach ($socialIssues as $issue) {
foreach ($issue->getDescendantsWithThis() as $child) {
$unique[spl_object_hash($child)] = $child;
}
}
return new ArrayCollection(array_values($unique));
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -262,6 +284,11 @@ class SocialIssue
return $this->title; return $this->title;
} }
public function hasChildren(): bool
{
return 0 < $this->getChildren()->count();
}
public function hasParent(): bool public function hasParent(): bool
{ {
return null !== $this->parent; return null !== $this->parent;
@ -329,6 +356,8 @@ class SocialIssue
{ {
$this->parent = $parent; $this->parent = $parent;
$parent->addChild($this);
return $this; return $this;
} }

View File

@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Select2UserLocationType; use Chill\MainBundle\Form\Type\PickUserLocationType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -48,11 +48,8 @@ class AdministrativeLocationFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_locations', Select2UserLocationType::class, [ $builder->add('accepted_locations', PickUserLocationType::class, [
'label' => 'Accepted locations', 'label' => 'Accepted locations',
'label_attr' => [
//'class' => 'd-none'
],
'multiple' => true, 'multiple' => true,
]); ]);
} }

View File

@ -15,10 +15,9 @@ use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Form\Type\PickSocialActionType;
use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
@ -51,17 +50,13 @@ class SocialActionFilter implements FilterInterface
$qb->join('acpw.socialAction', 'acpwsocialaction'); $qb->join('acpw.socialAction', 'acpwsocialaction');
} }
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('acpwsocialaction.id', ':socialactions'); $clause = $qb->expr()->in('acpwsocialaction.id', ':socialactions');
if ($where instanceof Andx) { $qb->andWhere($clause)
$where->add($clause); ->setParameter(
} else { 'socialactions',
$where = $qb->expr()->andX($clause); SocialAction::getDescendantsWithThisForActions($data['accepted_socialactions'])->toArray()
} );
$qb->add('where', $where);
$qb->setParameter('socialactions', $data['accepted_socialactions']);
} }
public function applyOn(): string public function applyOn(): string
@ -71,26 +66,25 @@ class SocialActionFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_socialactions', EntityType::class, [ $builder->add('accepted_socialactions', PickSocialActionType::class, [
'class' => SocialAction::class,
'choice_label' => function (SocialAction $sa) {
return $this->actionRender->renderString($sa, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
]); ]);
} }
public function describeAction($data, $format = 'string'): array public function describeAction($data, $format = 'string'): array
{ {
$socialactions = []; $actions = [];
foreach ($data['accepted_socialactions'] as $sa) { $socialactions = $data['accepted_socialactions'];
$socialactions[] = $this->actionRender->renderString($sa, []);
foreach ($socialactions as $action) {
$actions[] = $this->actionRender->renderString($action, [
'show_and_children' => true,
]);
} }
return ['Filtered by socialactions: only %socialactions%', [ return ['Filtered by socialactions: only %socialactions%', [
'%socialactions%' => implode(', ou ', $socialactions), '%socialactions%' => implode(', ou ', $actions),
]]; ]];
} }

View File

@ -15,10 +15,9 @@ use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Form\Type\PickSocialIssueType;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array; use function in_array;
@ -55,19 +54,12 @@ class SocialIssueFilter implements FilterInterface
$qb->join('acp.socialIssues', 'acpsocialissue'); $qb->join('acp.socialIssues', 'acpsocialissue');
} }
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('acpsocialissue.id', ':socialissues'); $clause = $qb->expr()->in('acpsocialissue.id', ':socialissues');
if ($where instanceof Andx) { $qb->andWhere($clause)
$where->add($clause); ->setParameter(
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter(
'socialissues', 'socialissues',
$this->addParentIssues($data['accepted_socialissues']) SocialIssue::getDescendantsWithThisForIssues($data['accepted_socialissues'])
); );
} }
@ -78,28 +70,21 @@ class SocialIssueFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_socialissues', EntityType::class, [ $builder->add('accepted_socialissues', PickSocialIssueType::class, [
'class' => SocialIssue::class,
'choice_label' => function ($socialIssue) {
return $this->socialIssueRender->renderString($socialIssue, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
]); ]);
} }
public function describeAction($data, $format = 'string') public function describeAction($data, $format = 'string'): array
{ {
$issues = []; $issues = [];
$socialissues = $this->addParentIssues($data['accepted_socialissues']); $socialissues = $data['accepted_socialissues'];
foreach ($socialissues as $i) { foreach ($socialissues as $issue) {
if ('null' === $i) { $issues[] = $this->socialIssueRender->renderString($issue, [
$issues[] = $this->translator->trans('Not given'); 'show_and_children' => true,
} else { ]);
$issues[] = $this->socialIssueRender->renderString($i, []);
}
} }
return [ return [
@ -108,44 +93,8 @@ class SocialIssueFilter implements FilterInterface
], ]; ], ];
} }
public function getTitle() public function getTitle(): string
{ {
return 'Filter by social issue'; return 'Filter by social issue';
} }
/**
* "Le filtre retiendra les parcours qui comportent cette problématique,
* ou une problématique parente à celles choisies.".
*
* Add parent of each socialissue selected, and remove duplicates
*
* @param $accepted_issues
*/
private function addParentIssues($accepted_issues): array
{
$array = [];
foreach ($accepted_issues as $i) {
/** @var SocialIssue $i */
if ($i->hasParent()) {
$array[] = $i->getParent();
}
$array[] = $i;
}
return $this->removeDuplicate($array);
}
private function removeDuplicate(array $array): array
{
$ids = array_map(static function ($item) {
return $item->getId();
}, $array);
$unique_ids = array_unique($ids);
return array_values(
array_intersect_key($array, $unique_ids)
);
}
} }

View File

@ -0,0 +1,57 @@
<?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\PersonBundle\Form\Type;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PickSocialActionType extends AbstractType
{
private SocialActionRender $actionRender;
private SocialActionRepository $actionRepository;
public function __construct(
SocialActionRender $actionRender,
SocialActionRepository $actionRepository
) {
$this->actionRender = $actionRender;
$this->actionRepository = $actionRepository;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'class' => SocialAction::class,
'choices' => $this->actionRepository->findAllActive(),
'choice_label' => function (SocialAction $sa) {
return $this->actionRender->renderString($sa, []);
},
'placeholder' => 'Pick a social action',
'required' => false,
'attr' => ['class' => 'select2'],
'label' => 'Social actions',
'multiple' => false,
])
->setAllowedTypes('multiple', ['bool']);
}
public function getParent(): string
{
return EntityType::class;
}
}

View File

@ -0,0 +1,57 @@
<?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\PersonBundle\Form\Type;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PickSocialIssueType extends AbstractType
{
private SocialIssueRender $issueRender;
private SocialIssueRepository $issueRepository;
public function __construct(
SocialIssueRender $issueRender,
SocialIssueRepository $issueRepository
) {
$this->issueRender = $issueRender;
$this->issueRepository = $issueRepository;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'class' => SocialIssue::class,
'choices' => $this->issueRepository->findAllActive(),
'choice_label' => function (SocialIssue $si) {
return $this->issueRender->renderString($si, []);
},
'placeholder' => 'Pick a social issue',
'required' => false,
'attr' => ['class' => 'select2'],
'label' => 'Social issues',
'multiple' => false,
])
->setAllowedTypes('multiple', ['bool']);
}
public function getParent(): string
{
return EntityType::class;
}
}

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Repository\SocialWork; namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -44,6 +45,14 @@ final class SocialActionRepository implements ObjectRepository
return $this->repository->findAll(); return $this->repository->findAll();
} }
/**
* @return array|SocialAction[]
*/
public function findAllActive(): array
{
return $this->buildQueryWithDesactivatedDateCriteria()->getQuery()->getResult();
}
/** /**
* @param mixed|null $limit * @param mixed|null $limit
* @param mixed|null $offset * @param mixed|null $offset
@ -67,4 +76,16 @@ final class SocialActionRepository implements ObjectRepository
{ {
return SocialAction::class; return SocialAction::class;
} }
private function buildQueryWithDesactivatedDateCriteria(): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('sa');
$qb->where('sa.desactivationDate is null')
->orWhere('sa.desactivationDate > :now')
->orderBy('sa.ordering', 'ASC')
->setParameter('now', new DateTime('now'));
return $qb;
}
} }

View File

@ -12,8 +12,10 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Repository\SocialWork; namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\ObjectRepository;
final class SocialIssueRepository implements ObjectRepository final class SocialIssueRepository implements ObjectRepository
@ -38,6 +40,14 @@ final class SocialIssueRepository implements ObjectRepository
return $this->repository->findAll(); return $this->repository->findAll();
} }
/**
* @return array|SocialIssue[]
*/
public function findAllActive(): array
{
return $this->buildQueryWithDesactivatedDateCriteria()->getQuery()->getResult();
}
/** /**
* @param mixed|null $limit * @param mixed|null $limit
* @param mixed|null $offset * @param mixed|null $offset
@ -61,4 +71,16 @@ final class SocialIssueRepository implements ObjectRepository
{ {
return SocialIssue::class; return SocialIssue::class;
} }
private function buildQueryWithDesactivatedDateCriteria(): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('si');
$qb->where('si.desactivationDate is null')
->orWhere('si.desactivationDate > :now')
->orderBy('si.ordering', 'ASC')
->setParameter('now', new DateTime('now'));
return $qb;
}
} }

View File

@ -15,16 +15,20 @@ use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Symfony\Component\Templating\EngineInterface; use Symfony\Component\Templating\EngineInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_merge; use function array_merge;
use function array_reverse; use function array_reverse;
use function implode; use function implode;
class SocialActionRender implements ChillEntityRenderInterface class SocialActionRender implements ChillEntityRenderInterface
{ {
public const AND_CHILDREN_MENTION = 'show_and_children_mention';
public const DEFAULT_ARGS = [ public const DEFAULT_ARGS = [
self::SEPARATOR_KEY => ' > ', self::SEPARATOR_KEY => ' > ',
self::NO_BADGE => false, self::NO_BADGE => false,
self::SHOW_AND_CHILDREN => false,
self::AND_CHILDREN_MENTION => 'social_action.and children',
]; ];
/** /**
@ -34,14 +38,26 @@ class SocialActionRender implements ChillEntityRenderInterface
public const SEPARATOR_KEY = 'default.separator'; public const SEPARATOR_KEY = 'default.separator';
/**
* Show a mention "and children" on each SocialAction, if the social action
* has at least one child.
*/
public const SHOW_AND_CHILDREN = 'show_and_children';
private EngineInterface $engine; private EngineInterface $engine;
private TranslatableStringHelper $translatableStringHelper; private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine) private TranslatorInterface $translator;
{
public function __construct(
TranslatableStringHelper $translatableStringHelper,
EngineInterface $engine,
TranslatorInterface $translator
) {
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->engine = $engine; $this->engine = $engine;
$this->translator = $translator;
} }
public function renderBox($socialAction, array $options): string public function renderBox($socialAction, array $options): string
@ -72,7 +88,13 @@ class SocialActionRender implements ChillEntityRenderInterface
$titles = array_reverse($titles); $titles = array_reverse($titles);
return implode($options[self::SEPARATOR_KEY], $titles); $title = implode($options[self::SEPARATOR_KEY], $titles);
if ($options[self::SHOW_AND_CHILDREN] && $socialAction->hasChildren()) {
$title .= ' (' . $this->translator->trans($options[self::AND_CHILDREN_MENTION]) . ')';
}
return $title;
} }
public function supports($entity, array $options): bool public function supports($entity, array $options): bool

View File

@ -15,26 +15,42 @@ use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Symfony\Component\Templating\EngineInterface; use Symfony\Component\Templating\EngineInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_reverse; use function array_reverse;
use function implode; use function implode;
final class SocialIssueRender implements ChillEntityRenderInterface final class SocialIssueRender implements ChillEntityRenderInterface
{ {
public const AND_CHILDREN_MENTION = 'show_and_children_mention';
public const DEFAULT_ARGS = [ public const DEFAULT_ARGS = [
self::SEPARATOR_KEY => ' > ', self::SEPARATOR_KEY => ' > ',
self::SHOW_AND_CHILDREN => false,
self::AND_CHILDREN_MENTION => 'social_issue.and children',
]; ];
public const SEPARATOR_KEY = 'default.separator'; public const SEPARATOR_KEY = 'default.separator';
/**
* Show a mention "and children" on each SocialIssue, if the social issue
* has at least one child.
*/
public const SHOW_AND_CHILDREN = 'show_and_children';
private EngineInterface $engine; private EngineInterface $engine;
private TranslatableStringHelper $translatableStringHelper; private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine) private TranslatorInterface $translator;
{
public function __construct(
TranslatableStringHelper $translatableStringHelper,
EngineInterface $engine,
TranslatorInterface $translator
) {
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->engine = $engine; $this->engine = $engine;
$this->translator = $translator;
} }
/** /**
@ -78,7 +94,13 @@ final class SocialIssueRender implements ChillEntityRenderInterface
$titles = array_reverse($titles); $titles = array_reverse($titles);
return implode($options[self::SEPARATOR_KEY], $titles); $title = implode($options[self::SEPARATOR_KEY], $titles);
if ($options[self::SHOW_AND_CHILDREN] && $socialIssue->hasChildren()) {
$title .= ' (' . $this->translator->trans($options[self::AND_CHILDREN_MENTION]) . ')';
}
return $title;
} }
public function supports($entity, array $options): bool public function supports($entity, array $options): bool

View File

@ -0,0 +1,51 @@
<?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\PersonBundle\Tests\Entity\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
final class SocialActionTest extends TestCase
{
public function testGetDescendantsWithThisForActions()
{
$parentA = new SocialAction();
$childA = (new SocialAction())->setParent($parentA);
$grandChildA = (new SocialAction())->setParent($childA);
$grandGrandChildA = (new SocialAction())->setParent($grandChildA);
$unrelatedA = new SocialAction();
$parentB = new SocialAction();
$childB = (new SocialAction())->setParent($parentB);
$grandChildB = (new SocialAction())->setParent($childB);
$grandGrandChildB = (new SocialAction())->setParent($grandChildB);
$unrelatedB = new SocialAction();
$actual = SocialAction::getDescendantsWithThisForActions([$parentA, $parentB]);
$this->assertContains($parentA, $actual);
$this->assertContains($parentB, $actual);
$this->assertContains($childA, $actual);
$this->assertContains($childB, $actual);
$this->assertContains($grandChildA, $actual);
$this->assertContains($grandChildB, $actual);
$this->assertContains($grandGrandChildA, $actual);
$this->assertContains($grandGrandChildB, $actual);
$this->assertCount(8, $actual);
$this->assertNotContains($unrelatedA, $actual);
$this->assertNotContains($unrelatedB, $actual);
}
}

View File

@ -55,6 +55,35 @@ final class SocialIssueTest extends TestCase
$this->assertCount(0, $unrelated->getAncestors(false)); $this->assertCount(0, $unrelated->getAncestors(false));
} }
public function testGetDescendantsWithThisForIssues()
{
$parentA = new SocialIssue();
$childA = (new SocialIssue())->setParent($parentA);
$grandChildA = (new SocialIssue())->setParent($childA);
$grandGrandChildA = (new SocialIssue())->setParent($grandChildA);
$unrelatedA = new SocialIssue();
$parentB = new SocialIssue();
$childB = (new SocialIssue())->setParent($parentB);
$grandChildB = (new SocialIssue())->setParent($childB);
$grandGrandChildB = (new SocialIssue())->setParent($grandChildB);
$unrelatedB = new SocialIssue();
$actual = SocialIssue::getDescendantsWithThisForIssues([$parentA, $parentB]);
$this->assertContains($parentA, $actual);
$this->assertContains($parentB, $actual);
$this->assertContains($childA, $actual);
$this->assertContains($childB, $actual);
$this->assertContains($grandChildA, $actual);
$this->assertContains($grandChildB, $actual);
$this->assertContains($grandGrandChildA, $actual);
$this->assertContains($grandGrandChildB, $actual);
$this->assertCount(8, $actual);
$this->assertNotContains($unrelatedA, $actual);
$this->assertNotContains($unrelatedB, $actual);
}
public function testIsDescendantOf() public function testIsDescendantOf()
{ {
$parent = new SocialIssue(); $parent = new SocialIssue();

View File

@ -205,9 +205,11 @@ Resources: Interlocuteurs privilégiés
Any requestor to this accompanying course: Aucun demandeur pour ce parcours Any requestor to this accompanying course: Aucun demandeur pour ce parcours
Social action: Action d'accompagnement Social action: Action d'accompagnement
Social actions: Actions d'accompagnement Social actions: Actions d'accompagnement
Pick a social action: Choisir une action d'accompagnement
Last social actions: Les dernières actions d'accompagnement Last social actions: Les dernières actions d'accompagnement
Social issue: Problématique sociale Social issue: Problématique sociale
Social issues: Problématiques sociales Social issues: Problématiques sociales
Pick a social issue: Choisir une problématique sociale
Last events on accompanying course: Dernières actions de suivi Last events on accompanying course: Dernières actions de suivi
Edit & activate accompanying course: Modifier et valider Edit & activate accompanying course: Modifier et valider
See accompanying periods: Voir toutes les périodes d'accompagnement See accompanying periods: Voir toutes les périodes d'accompagnement
@ -953,6 +955,7 @@ export:
Group course by referrer's scope: Grouper les parcours par service du référent Group course by referrer's scope: Grouper les parcours par service du référent
Computation date for referrer: Date à laquelle le référent était actif Computation date for referrer: Date à laquelle le référent était actif
Referrer's scope: Service du référent de parcours Referrer's scope: Service du référent de parcours
duration: duration:
day: Durée du parcours en jours day: Durée du parcours en jours
week: Durée du parcours en semaines week: Durée du parcours en semaines
@ -965,3 +968,8 @@ export:
by_referrer: by_referrer:
Computation date for referrer: Date à laquelle le référent était actif Computation date for referrer: Date à laquelle le référent était actif
social_action:
and children: et dérivés
social_issue:
and children: et dérivés