mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-04 03:08:25 +00:00 
			
		
		
		
	Merge branch 'master' into tasks_mission_type_filter
This commit is contained in:
		
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20230817-131059.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20230817-131059.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Add locations in Aside Activity. By default, suggest user location, otherwise
 | 
			
		||||
  a select with all locations.
 | 
			
		||||
time: 2023-08-17T13:10:59.152278661+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: "133"
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20230817-131152.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20230817-131152.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: 'Adapt Aside Activity exports: display location, filter by location, group by
 | 
			
		||||
  location'
 | 
			
		||||
time: 2023-08-17T13:11:52.911356021+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: "133"
 | 
			
		||||
							
								
								
									
										5
									
								
								.changes/unreleased/Fixed-20230817-131239.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/unreleased/Fixed-20230817-131239.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: Missing translation in Work Actions exports
 | 
			
		||||
time: 2023-08-17T13:12:39.159627128+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: ""
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Fixed-20230822-142809.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Fixed-20230822-142809.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: "Corrects a typing error in 2 filters, which caused an \nerror when trying to
 | 
			
		||||
  reedit a saved export\n\n"
 | 
			
		||||
time: 2023-08-22T14:28:09.485466139+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: "135"
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Fixed-20230829-181332.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Fixed-20230829-181332.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: '[household] when moving a person to a sharing position to a not-sharing position
 | 
			
		||||
  on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member.'
 | 
			
		||||
time: 2023-08-29T18:13:32.799479781+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: "136"
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Fixed-20230829-181837.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Fixed-20230829-181837.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: |
 | 
			
		||||
  Add missing translation for comment field placeholder in repositionning household editor.
 | 
			
		||||
time: 2023-08-29T18:18:37.691526331+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: ""
 | 
			
		||||
							
								
								
									
										5
									
								
								.changes/unreleased/Fixed-20230906-154856.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/unreleased/Fixed-20230906-154856.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: Do not send an email to creator twice when adding a comment to a notification
 | 
			
		||||
time: 2023-09-06T15:48:56.991246312+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: ""
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/UX-20230829-181733.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/UX-20230829-181733.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: UX
 | 
			
		||||
body: |
 | 
			
		||||
  Uniformize badge-person in household banner (background, size)
 | 
			
		||||
time: 2023-08-29T18:17:33.190396543+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: ""
 | 
			
		||||
@@ -94,7 +94,7 @@ class CountPerson implements ExportInterface
 | 
			
		||||
    public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
 | 
			
		||||
    {
 | 
			
		||||
        // we gather all center the user choose.
 | 
			
		||||
        $centers = array_map(static fn($el) => $el['center'], $acl);
 | 
			
		||||
        $centers = array_map(static fn ($el) => $el['center'], $acl);
 | 
			
		||||
 | 
			
		||||
        $qb = $this->entityManager->createQueryBuilder();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ class ChillMainConfiguration implements ConfigurationInterface
 | 
			
		||||
            ->end() // end of widgets
 | 
			
		||||
            ->end() // end of root/children
 | 
			
		||||
            ->end() // end of root
 | 
			
		||||
;
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        return $treeBuilder;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,13 @@
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
 | 
			
		||||
use Rector\Config\RectorConfig;
 | 
			
		||||
use Rector\Set\ValueObject\LevelSetList;
 | 
			
		||||
 
 | 
			
		||||
@@ -320,7 +320,6 @@ final class ActivityController extends AbstractController
 | 
			
		||||
 | 
			
		||||
    private function buildFilterOrder(AccompanyingPeriod|Person $associated): FilterOrderHelper
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        $filterBuilder = $this->filterOrderHelperFactory->create(self::class);
 | 
			
		||||
        $types = $this->activityACLAwareRepository->findActivityTypeByAssociated($associated);
 | 
			
		||||
        $jobs = $this->activityACLAwareRepository->findUserJobByAssociated($associated);
 | 
			
		||||
 
 | 
			
		||||
@@ -22,11 +22,11 @@ use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
class EmergencyFilter implements FilterInterface
 | 
			
		||||
{
 | 
			
		||||
    private const CHOICES = [
 | 
			
		||||
        'activity is emergency' => true,
 | 
			
		||||
        'activity is not emergency' => false,
 | 
			
		||||
        'activity is emergency' => 'true',
 | 
			
		||||
        'activity is not emergency' => 'false',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    private const DEFAULT_CHOICE = false;
 | 
			
		||||
    private const DEFAULT_CHOICE = 'false';
 | 
			
		||||
 | 
			
		||||
    private TranslatorInterface $translator;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -322,7 +322,7 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos
 | 
			
		||||
            $counter++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach  ($person->getAccompanyingPeriodParticipations() as $participation) {
 | 
			
		||||
        foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
 | 
			
		||||
            if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -122,5 +122,4 @@ class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase
 | 
			
		||||
        yield [$person, $scopes, true, null, new \DateTimeImmutable("1 week ago"), "content"];
 | 
			
		||||
        yield [$person, [], true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ final class AsideActivityController extends CRUDController
 | 
			
		||||
        $asideActivity = new AsideActivity();
 | 
			
		||||
 | 
			
		||||
        $asideActivity->setAgent($this->getUser());
 | 
			
		||||
        $asideActivity->setLocation($this->getUser()->getCurrentLocation());
 | 
			
		||||
 | 
			
		||||
        $duration = $request->query->get('duration', '300');
 | 
			
		||||
        $duration = DateTime::createFromFormat('U', $duration);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ namespace Chill\AsideActivityBundle\Entity;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Entity\Location;
 | 
			
		||||
use DateTimeInterface;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Symfony\Component\Validator\Constraints as Assert;
 | 
			
		||||
@@ -60,9 +61,10 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
 | 
			
		||||
    private ?int $id = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="string", length=100, nullable=true)
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity=Location::class)
 | 
			
		||||
     * @ORM\JoinColumn(nullable=true)
 | 
			
		||||
     */
 | 
			
		||||
    private $location;
 | 
			
		||||
    private ?Location $location = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="text", nullable=true)
 | 
			
		||||
@@ -115,7 +117,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLocation(): ?string
 | 
			
		||||
    public function getLocation(): ?Location
 | 
			
		||||
    {
 | 
			
		||||
        return $this->location;
 | 
			
		||||
    }
 | 
			
		||||
@@ -175,7 +177,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setLocation(?string $location): self
 | 
			
		||||
    public function setLocation(?Location $location): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->location = $location;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,100 @@
 | 
			
		||||
<?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\AsideActivityBundle\Export\Aggregator;
 | 
			
		||||
 | 
			
		||||
use Chill\AsideActivityBundle\Export\Declarations;
 | 
			
		||||
use Chill\MainBundle\Export\AggregatorInterface;
 | 
			
		||||
use Chill\MainBundle\Repository\LocationRepository;
 | 
			
		||||
use Closure;
 | 
			
		||||
use Doctrine\ORM\QueryBuilder;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
 | 
			
		||||
class ByLocationAggregator implements AggregatorInterface
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private LocationRepository $locationRepository)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder): void
 | 
			
		||||
    {
 | 
			
		||||
        // no form
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function getLabels($key, array $values, $data)
 | 
			
		||||
    {
 | 
			
		||||
        return function ($value): string {
 | 
			
		||||
            if ('_header' === $value) {
 | 
			
		||||
                return 'export.aggregator.Aside activity localisation';
 | 
			
		||||
            }
 | 
			
		||||
            if (null === $value || '' === $value || null === $l = $this->locationRepository->find($value)) {
 | 
			
		||||
                return '';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return $l->getName();
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function getQueryKeys($data): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['by_aside_activity_location_aggregator'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function getTitle(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'export.aggregator.Group by aside activity location';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function addRole(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function alterQuery(QueryBuilder $qb, $data): void
 | 
			
		||||
    {
 | 
			
		||||
        $qb->addSelect('IDENTITY(aside.location) AS by_aside_activity_location_aggregator')
 | 
			
		||||
            ->addGroupBy('by_aside_activity_location_aggregator');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function applyOn(): string
 | 
			
		||||
    {
 | 
			
		||||
        return Declarations::ASIDE_ACTIVITY_TYPE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,6 +23,7 @@ use Chill\MainBundle\Export\Helper\DateTimeHelper;
 | 
			
		||||
use Chill\MainBundle\Export\Helper\UserHelper;
 | 
			
		||||
use Chill\MainBundle\Export\ListInterface;
 | 
			
		||||
use Chill\MainBundle\Repository\CenterRepositoryInterface;
 | 
			
		||||
use Chill\MainBundle\Repository\LocationRepository;
 | 
			
		||||
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
 | 
			
		||||
use DateTimeInterface;
 | 
			
		||||
@@ -58,6 +59,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
 | 
			
		||||
        CenterRepositoryInterface $centerRepository,
 | 
			
		||||
        AsideActivityCategoryRepository $asideActivityCategoryRepository,
 | 
			
		||||
        CategoryRender $categoryRender,
 | 
			
		||||
        private LocationRepository $locationRepository,
 | 
			
		||||
        TranslatableStringHelperInterface $translatableStringHelper
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
@@ -73,6 +75,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [];
 | 
			
		||||
@@ -145,6 +148,19 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
 | 
			
		||||
                    return $this->categoryRender->renderString($c, []);
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
            case 'location':
 | 
			
		||||
                return function ($value) {
 | 
			
		||||
                    if ('_header' === $value) {
 | 
			
		||||
                        return 'export.aside_activity.location';
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (null === $value || '' === $value || null === $l = $this->locationRepository->find($value)) {
 | 
			
		||||
                        return '';
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return $l->getName();
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
            case 'main_scope':
 | 
			
		||||
                return function ($value) {
 | 
			
		||||
                    if ('_header' === $value) {
 | 
			
		||||
@@ -191,6 +207,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
 | 
			
		||||
            'date',
 | 
			
		||||
            'duration',
 | 
			
		||||
            'note',
 | 
			
		||||
            'location',
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -226,6 +243,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
 | 
			
		||||
            ->addSelect('IDENTITY(aside.type) AS aside_activity_type')
 | 
			
		||||
            ->addSelect('aside.date')
 | 
			
		||||
            ->addSelect('aside.duration')
 | 
			
		||||
            ->addSelect('IDENTITY(aside.location) AS location')
 | 
			
		||||
            ->addSelect('aside.note');
 | 
			
		||||
 | 
			
		||||
        return $qb;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,105 @@
 | 
			
		||||
<?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\AsideActivityBundle\Export\Filter;
 | 
			
		||||
 | 
			
		||||
use Chill\AsideActivityBundle\Export\Declarations;
 | 
			
		||||
use Chill\MainBundle\Entity\Location;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Export\FilterInterface;
 | 
			
		||||
use Chill\MainBundle\Form\Type\PickUserLocationType;
 | 
			
		||||
use Chill\MainBundle\Repository\LocationRepository;
 | 
			
		||||
use Doctrine\ORM\QueryBuilder;
 | 
			
		||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
 | 
			
		||||
final readonly class ByLocationFilter implements FilterInterface
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private Security $security
 | 
			
		||||
    ) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function getTitle(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'export.filter.Filter by aside activity location';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder): void
 | 
			
		||||
    {
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('locations', PickUserLocationType::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->security->getUser();
 | 
			
		||||
 | 
			
		||||
        if ($user instanceof User) {
 | 
			
		||||
            return [
 | 
			
		||||
                'locations' => $user->getCurrentLocation(),
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'locations' => null,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function describeAction($data, $format = 'string'): array
 | 
			
		||||
    {
 | 
			
		||||
        $locations = $data['locations']->map(fn (Location $l): string => $l->getName());
 | 
			
		||||
 | 
			
		||||
        return ['export.filter.Filtered by aside activity location: only %location%', [
 | 
			
		||||
            '%location%' => implode(', ', $locations),
 | 
			
		||||
        ]];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function addRole(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function alterQuery(QueryBuilder $qb, $data): void
 | 
			
		||||
    {
 | 
			
		||||
        $clause = $qb->expr()->in('aside.location', ':locations');
 | 
			
		||||
 | 
			
		||||
        $qb->andWhere($clause);
 | 
			
		||||
        $qb->setParameter('locations', $data['locations']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritDoc
 | 
			
		||||
     */
 | 
			
		||||
    public function applyOn(): string
 | 
			
		||||
    {
 | 
			
		||||
        return Declarations::ASIDE_ACTIVITY_TYPE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,13 +13,16 @@ namespace Chill\AsideActivityBundle\Form;
 | 
			
		||||
 | 
			
		||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
 | 
			
		||||
use Chill\AsideActivityBundle\Form\Type\PickAsideActivityCategoryType;
 | 
			
		||||
use Chill\MainBundle\Entity\Location;
 | 
			
		||||
use Chill\MainBundle\Form\Type\ChillDateType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\PickUserLocationType;
 | 
			
		||||
use DateInterval;
 | 
			
		||||
use DateTime;
 | 
			
		||||
use DateTimeImmutable;
 | 
			
		||||
use DateTimeZone;
 | 
			
		||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
 | 
			
		||||
@@ -77,7 +80,9 @@ final class AsideActivityFormType extends AbstractType
 | 
			
		||||
            ->add('note', ChillTextareaType::class, [
 | 
			
		||||
                'label' => 'Note',
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('location', PickUserLocationType::class)
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        foreach (['duration'] as $fieldName) {
 | 
			
		||||
            $builder->get($fieldName)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,9 @@
 | 
			
		||||
										</span>
 | 
			
		||||
									{% endif %}
 | 
			
		||||
								</div>
 | 
			
		||||
                                {%- if entity.location.name is defined -%}
 | 
			
		||||
                                    <div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div>
 | 
			
		||||
                                {%- endif -%}
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="item-col" style="justify-content: flex-end;">
 | 
			
		||||
								<div class="box">
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,13 @@
 | 
			
		||||
 | 
			
		||||
				<dt class="inline">{{ 'Created for'|trans }}</dt>
 | 
			
		||||
				<dd>{{ entity.agent }}</dd>
 | 
			
		||||
                
 | 
			
		||||
                <dt class="inline">{{ 'Asideactivity location'|trans }}</dt>
 | 
			
		||||
                {%- if entity.location.name is defined -%}
 | 
			
		||||
                    <dd>{{ entity.location.name }}</dd>
 | 
			
		||||
                {%- else -%}
 | 
			
		||||
                    <dd><span class="chill-no-data-statement">{{ 'No data given'|trans }}</span></dd>
 | 
			
		||||
                {%- endif -%}
 | 
			
		||||
 | 
			
		||||
				<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,19 +46,27 @@ services:
 | 
			
		||||
      tags:
 | 
			
		||||
          - { name: chill.export_filter, alias: 'aside_activity_user_filter' }
 | 
			
		||||
 | 
			
		||||
  Chill\AsideActivityBundle\Export\Filter\ByLocationFilter:
 | 
			
		||||
      tags:
 | 
			
		||||
          - { name: chill.export_filter, alias: 'aside_activity_location_filter' }
 | 
			
		||||
 | 
			
		||||
  ## Aggregators
 | 
			
		||||
 | 
			
		||||
  chill.aside_activity.export.type_aggregator:
 | 
			
		||||
    class: Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator
 | 
			
		||||
    tags:
 | 
			
		||||
      - { name: chill.export_aggregator, alias: activity_type_aggregator }
 | 
			
		||||
      - { name: chill.export_aggregator, alias: 'activity_type_aggregator' }
 | 
			
		||||
 | 
			
		||||
  chill.aside_activity.export.user_job_aggregator:
 | 
			
		||||
      class: Chill\AsideActivityBundle\Export\Aggregator\ByUserJobAggregator
 | 
			
		||||
      tags:
 | 
			
		||||
          - { name: chill.export_aggregator, alias: aside_activity_user_job_aggregator }
 | 
			
		||||
          - { name: chill.export_aggregator, alias: 'aside_activity_user_job_aggregator' }
 | 
			
		||||
 | 
			
		||||
  chill.aside_activity.export.user_scope_aggregator:
 | 
			
		||||
      class: Chill\AsideActivityBundle\Export\Aggregator\ByUserScopeAggregator
 | 
			
		||||
      tags:
 | 
			
		||||
          - { name: chill.export_aggregator, alias: aside_activity_user_scope_aggregator }
 | 
			
		||||
          - { name: chill.export_aggregator, alias: 'aside_activity_user_scope_aggregator' }
 | 
			
		||||
 | 
			
		||||
  Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
 | 
			
		||||
      tags:
 | 
			
		||||
          - { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
<?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\Migrations\AsideActivity;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
final class Version20230816112809 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Update location attribute in asideactivity';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql('DROP INDEX chill_asideactivity.IDX_A866DA0E64D218E');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP CONSTRAINT FK_A866DA0E64D218E');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP location_id');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity ADD location VARCHAR(100) DEFAULT NULL');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD location_id INT DEFAULT NULL');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_asideactivity.asideactivity DROP location');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD CONSTRAINT FK_A866DA0E64D218E FOREIGN KEY (location_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
 | 
			
		||||
        $this->addSql('CREATE INDEX IDX_A866DA0E64D218E ON chill_asideactivity.asideactivity (location_id)');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -26,6 +26,7 @@ Users: Utilisateurs
 | 
			
		||||
Emergency: Urgent
 | 
			
		||||
by: "Par "
 | 
			
		||||
location: Lieu
 | 
			
		||||
Asideactivity location: Localisation de l'activité
 | 
			
		||||
 | 
			
		||||
# Crud
 | 
			
		||||
crud:
 | 
			
		||||
@@ -182,6 +183,7 @@ export:
 | 
			
		||||
        duration: Durée
 | 
			
		||||
        note: Note
 | 
			
		||||
        id: Identifiant
 | 
			
		||||
        location: Localisation
 | 
			
		||||
 | 
			
		||||
    Exports of aside activities: Exports des activités annexes
 | 
			
		||||
    Count aside activities: Nombre d'activités annexes
 | 
			
		||||
@@ -202,11 +204,16 @@ export:
 | 
			
		||||
        Filter by user jobs: Filtrer les activités annexes par métier des utilisateurs
 | 
			
		||||
        'Filtered aside activities by user scope: only %scopes%': "Filtré par service des utilisateur: uniquement %scopes%"
 | 
			
		||||
        Filter by user scope: Filtrer les activités annexes par service d'utilisateur
 | 
			
		||||
        Filter by aside activity location: Filtrer les activités annexes par localisation
 | 
			
		||||
        'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%"
 | 
			
		||||
    aggregator:
 | 
			
		||||
        Group by aside activity type: Grouper les activités annexes par type d'activité
 | 
			
		||||
        Aside activity type: Type d'activité annexe
 | 
			
		||||
        Aggregate by user job: Grouper les activités annexes par métier des utilisateurs
 | 
			
		||||
        Aggregate by user scope: Grouper les activités annexes par service des utilisateurs
 | 
			
		||||
        Aside activity location: Localisation des activités annexe
 | 
			
		||||
        Group by aside activity location: Grouper les activités annexes par localisation
 | 
			
		||||
        Aside activity localisation: Localisation
 | 
			
		||||
 | 
			
		||||
# ROLES
 | 
			
		||||
CHILL_ASIDE_ACTIVITY_STATS: Statistiques pour les activités annexes
 | 
			
		||||
 
 | 
			
		||||
@@ -29,11 +29,11 @@ use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
class CalendarRangeFilter implements FilterInterface
 | 
			
		||||
{
 | 
			
		||||
    private const CHOICES = [
 | 
			
		||||
        'Not made within a calendar range' => true,
 | 
			
		||||
        'Made within a calendar range' => false,
 | 
			
		||||
        'Not made within a calendar range' => 'true',
 | 
			
		||||
        'Made within a calendar range' => 'false',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    private const DEFAULT_CHOICE = false;
 | 
			
		||||
    private const DEFAULT_CHOICE = 'false';
 | 
			
		||||
 | 
			
		||||
    private TranslatorInterface $translator;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -65,5 +65,4 @@ final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface
 | 
			
		||||
            default => throw new UserAbsenceSyncException("this status is not documented by Microsoft")
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -190,6 +190,4 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
 | 
			
		||||
 | 
			
		||||
        return $query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -172,5 +172,4 @@ class MSUserAbsenceReaderTest extends TestCase
 | 
			
		||||
            "User is absent: absence is always enabled"
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -94,5 +94,4 @@ final readonly class GenericDocForAccompanyingPeriodController
 | 
			
		||||
            ]
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -91,5 +91,4 @@ final readonly class GenericDocForPerson
 | 
			
		||||
            ]
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,6 @@ class FetchQuery implements FetchQueryInterface
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        unset($this->wheres[$index], $this->whereParams[$index], $this->whereTypes[$index]);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeJoinClause(int $index): void
 | 
			
		||||
@@ -92,7 +91,6 @@ class FetchQuery implements FetchQueryInterface
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        unset($this->joins[$index], $this->joinParams[$index], $this->joinTypes[$index]);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getSelectKeyString(): string
 | 
			
		||||
 
 | 
			
		||||
@@ -27,5 +27,4 @@ interface GenericDocForAccompanyingPeriodProviderInterface
 | 
			
		||||
     * Return true if the user is allowed to see some documents for this provider.
 | 
			
		||||
     */
 | 
			
		||||
    public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -143,5 +143,4 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
 | 
			
		||||
 | 
			
		||||
        return $query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -46,5 +46,4 @@ final readonly class GenericDocExtensionRuntime implements RuntimeExtensionInter
 | 
			
		||||
 | 
			
		||||
        throw new \LogicException("no renderer found");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,5 +20,4 @@ interface GenericDocRendererInterface
 | 
			
		||||
    public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
 | 
			
		||||
 | 
			
		||||
    public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,6 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $orPersonId[] = $participation->getPerson()->getId();
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ([] === $orPersonId) {
 | 
			
		||||
 
 | 
			
		||||
@@ -54,9 +54,6 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase
 | 
			
		||||
        );
 | 
			
		||||
        self::assertEquals(['foo', 'bar_baz', 'baz'], $params);
 | 
			
		||||
        self::assertEquals([Types::STRING, Types::STRING, Types::STRING], $types);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testToSqlWithoutWhere(): void
 | 
			
		||||
@@ -85,5 +82,4 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase
 | 
			
		||||
        self::assertEquals([], $params);
 | 
			
		||||
        self::assertEquals([], $types);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -142,7 +142,6 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase
 | 
			
		||||
        yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null];
 | 
			
		||||
        yield [$period, null, null, 'test'];
 | 
			
		||||
        yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test'];
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function provideDataBuildFetchQueryForPerson(): iterable
 | 
			
		||||
@@ -154,5 +153,4 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase
 | 
			
		||||
        yield [null, null, 'test'];
 | 
			
		||||
        yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -152,7 +152,7 @@ class EventSearch extends AbstractSearch
 | 
			
		||||
            $orWhere = $qb->expr()->orX();
 | 
			
		||||
 | 
			
		||||
            foreach ($reachableCenters as $center) {
 | 
			
		||||
                $n = $n+1;
 | 
			
		||||
                $n = $n + 1;
 | 
			
		||||
                $circles = $this->authorizationHelper->getReachableScopes(
 | 
			
		||||
                    $this->security->getUser(),
 | 
			
		||||
                    'CHILL_EVENT_SEE',
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,6 @@ class CronManager implements CronManagerInterface
 | 
			
		||||
        foreach ($orderedJobs as $job) {
 | 
			
		||||
            if ($job->canRun($lasts[$job->getKey()] ?? null)) {
 | 
			
		||||
                if (array_key_exists($job->getKey(), $lasts)) {
 | 
			
		||||
 | 
			
		||||
                    $executionData = $lasts[$job->getKey()]->getLastExecutionData();
 | 
			
		||||
 | 
			
		||||
                    $this->entityManager
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,6 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
 | 
			
		||||
            foreach ($helper->getUserPickers() as $name => [
 | 
			
		||||
                'label' => $label, 'options' => $opts
 | 
			
		||||
            ]) {
 | 
			
		||||
 | 
			
		||||
                $userPickersBuilder->add(
 | 
			
		||||
                    $name,
 | 
			
		||||
                    PickUserDynamicType::class,
 | 
			
		||||
@@ -136,7 +135,6 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
 | 
			
		||||
 | 
			
		||||
            $builder->add($userPickersBuilder);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function buildCheckboxChoices(array $choices, array $trans = []): array
 | 
			
		||||
 
 | 
			
		||||
@@ -40,13 +40,18 @@ class NotificationMailer
 | 
			
		||||
 | 
			
		||||
    public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void
 | 
			
		||||
    {
 | 
			
		||||
        foreach (
 | 
			
		||||
            array_merge(
 | 
			
		||||
                $comment->getNotification()->getAddressees()->toArray(),
 | 
			
		||||
                [$comment->getNotification()->getSender()]
 | 
			
		||||
            ) as $dest
 | 
			
		||||
        ) {
 | 
			
		||||
            if (null === $dest->getEmail() || $comment->getCreatedBy() !== $dest) {
 | 
			
		||||
        $dests = [$comment->getNotification()->getSender(), ...$comment->getNotification()->getAddressees()->toArray()];
 | 
			
		||||
 | 
			
		||||
        $uniqueDests = [];
 | 
			
		||||
        foreach ($dests as $dest) {
 | 
			
		||||
            // avoid duplication
 | 
			
		||||
            if (in_array(spl_object_hash($dest), $uniqueDests, true)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $uniqueDests[] = spl_object_hash($dest);
 | 
			
		||||
 | 
			
		||||
            // do not send if the sender does not have any email, nor to the creator of the comment
 | 
			
		||||
            if (null === $dest->getEmail() || $comment->getCreatedBy() === $dest) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $email = new TemplatedEmail();
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,6 @@ class CronJobDatabaseInteractionTest extends KernelTestCase
 | 
			
		||||
        // run a second time
 | 
			
		||||
        $manager->run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class JobWithReturn implements CronJobInterface
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,128 @@
 | 
			
		||||
<?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 Notification\Email;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\Notification;
 | 
			
		||||
use Chill\MainBundle\Entity\NotificationComment;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Notification\Email\NotificationMailer;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Doctrine\ORM\Event\PostPersistEventArgs;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
use Prophecy\Argument;
 | 
			
		||||
use Prophecy\PhpUnit\ProphecyTrait;
 | 
			
		||||
use Psr\Log\NullLogger;
 | 
			
		||||
use Symfony\Component\Mailer\MailerInterface;
 | 
			
		||||
use Symfony\Component\Mime\Email;
 | 
			
		||||
use Symfony\Component\Translation\Translator;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @internal
 | 
			
		||||
 * @coversNothing
 | 
			
		||||
 */
 | 
			
		||||
class NotificationMailerTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    use ProphecyTrait;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     */
 | 
			
		||||
    public function testPostPersistComment(): void
 | 
			
		||||
    {
 | 
			
		||||
        $user1 = (new User())->setEmail('user1@foo.com');
 | 
			
		||||
        $user2 = (new User())->setEmail('user2@foo.com');
 | 
			
		||||
        $user3 = (new User())->setEmail('user3@foo.com');
 | 
			
		||||
 | 
			
		||||
        $notification = new Notification();
 | 
			
		||||
        $notification
 | 
			
		||||
            ->setTitle('test notification')
 | 
			
		||||
            ->setSender($user1)
 | 
			
		||||
            ->addAddressee($user2)
 | 
			
		||||
            ->addAddressee($user3)
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        $comment = (new NotificationComment())
 | 
			
		||||
            ->setContent("foo bar baz")
 | 
			
		||||
            ->setCreatedBy($user2)
 | 
			
		||||
        ;
 | 
			
		||||
        $notification->addComment($comment);
 | 
			
		||||
 | 
			
		||||
        $mailer = $this->prophesize(MailerInterface::class);
 | 
			
		||||
 | 
			
		||||
        // a mail only to user1 and user3 should have been sent
 | 
			
		||||
        $mailer->send(Argument::that(function (Email $email) {
 | 
			
		||||
            foreach ($email->getTo() as $address) {
 | 
			
		||||
                if ($address->getAddress() === 'user1@foo.com' || $address->getAddress() === 'user3@foo.com') {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }))->shouldBeCalledTimes(2);
 | 
			
		||||
 | 
			
		||||
        $objectManager = $this->prophesize(EntityManagerInterface::class);
 | 
			
		||||
 | 
			
		||||
        $mailer = $this->buildNotificationMailer($mailer->reveal());
 | 
			
		||||
        $mailer->postPersistComment($comment, new PostPersistEventArgs($comment, $objectManager->reveal()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testPostPersistCommentDestWithNullEmail(): void
 | 
			
		||||
    {
 | 
			
		||||
        $user1 = (new User())->setEmail('user1@foo.com');
 | 
			
		||||
        $user2 = (new User())->setEmail('user2@foo.com');
 | 
			
		||||
        $user3 = (new User())->setEmail(null);
 | 
			
		||||
 | 
			
		||||
        $notification = new Notification();
 | 
			
		||||
        $notification
 | 
			
		||||
            ->setTitle('test notification')
 | 
			
		||||
            ->setSender($user1)
 | 
			
		||||
            ->addAddressee($user2)
 | 
			
		||||
            ->addAddressee($user3)
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
        $comment = (new NotificationComment())
 | 
			
		||||
            ->setContent("foo bar baz")
 | 
			
		||||
            ->setCreatedBy($user2)
 | 
			
		||||
        ;
 | 
			
		||||
        $notification->addComment($comment);
 | 
			
		||||
 | 
			
		||||
        $mailer = $this->prophesize(MailerInterface::class);
 | 
			
		||||
 | 
			
		||||
        // a mail only to user1 and user3 should have been sent
 | 
			
		||||
        $mailer->send(Argument::that(function (Email $email) {
 | 
			
		||||
            foreach ($email->getTo() as $address) {
 | 
			
		||||
                if ($address->getAddress() === 'user1@foo.com') {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }))->shouldBeCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
        $objectManager = $this->prophesize(EntityManagerInterface::class);
 | 
			
		||||
 | 
			
		||||
        $mailer = $this->buildNotificationMailer($mailer->reveal());
 | 
			
		||||
        $mailer->postPersistComment($comment, new PostPersistEventArgs($comment, $objectManager->reveal()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function buildNotificationMailer(
 | 
			
		||||
        MailerInterface $mailer = null,
 | 
			
		||||
    ): NotificationMailer {
 | 
			
		||||
        return new NotificationMailer(
 | 
			
		||||
            $mailer,
 | 
			
		||||
            new NullLogger(),
 | 
			
		||||
            new Translator('fr')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -64,5 +64,4 @@ class CollateAddressWithReferenceOrPostalCodeCronJobTest extends TestCase
 | 
			
		||||
        yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-01T12:00:00'), true];
 | 
			
		||||
        yield [new \DateTimeImmutable('2023-07-10T12:00:00'), null, true];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -214,7 +214,7 @@ final class PersonController extends AbstractController
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
 | 
			
		||||
        $authorizedCenters =$this->authorizationHelper->getReachableCenters($this->getUser(), PersonVoter::CREATE);
 | 
			
		||||
        $authorizedCenters = $this->authorizationHelper->getReachableCenters($this->getUser(), PersonVoter::CREATE);
 | 
			
		||||
 | 
			
		||||
        if (1 === count($authorizedCenters)) {
 | 
			
		||||
            $person->setCenter($authorizedCenters[0]);
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,14 @@ class MembersEditor
 | 
			
		||||
        $this->eventDispatcher = $eventDispatcher;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a person to the household
 | 
			
		||||
     *
 | 
			
		||||
     * The person is added to the household associated with this editor's instance.
 | 
			
		||||
     *
 | 
			
		||||
     * If the person is also a member of another household, or the same household at the same position, the person
 | 
			
		||||
     * is not associated any more with the previous household.
 | 
			
		||||
     */
 | 
			
		||||
    public function addMovement(DateTimeImmutable $date, Person $person, ?Position $position, ?bool $holder = false, ?string $comment = null): self
 | 
			
		||||
    {
 | 
			
		||||
        if (null === $this->household) {
 | 
			
		||||
@@ -69,68 +77,66 @@ class MembersEditor
 | 
			
		||||
            ->setComment($comment);
 | 
			
		||||
        $this->household->addMember($membership);
 | 
			
		||||
 | 
			
		||||
        if (null !== $position) {
 | 
			
		||||
            if ($position->getShareHousehold()) {
 | 
			
		||||
                // launch event only if moving to a "share household" position,
 | 
			
		||||
                // and if the destination household is different than the previous one
 | 
			
		||||
                $event = new PersonAddressMoveEvent($person);
 | 
			
		||||
                $event->setNextMembership($membership);
 | 
			
		||||
        if ($membership->getShareHousehold()) {
 | 
			
		||||
            // launch event only if moving to a "share household" position,
 | 
			
		||||
            // and if the destination household is different than the previous one
 | 
			
		||||
            $event = new PersonAddressMoveEvent($person);
 | 
			
		||||
            $event->setNextMembership($membership);
 | 
			
		||||
 | 
			
		||||
                $counter = 0;
 | 
			
		||||
            $counter = 0;
 | 
			
		||||
 | 
			
		||||
                foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) {
 | 
			
		||||
                    if ($participation === $membership) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ($participation->getStartDate() > $membership->getStartDate()) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    ++$counter;
 | 
			
		||||
 | 
			
		||||
                    if ($participation->getEndDate() === null || $participation->getEndDate() > $date) {
 | 
			
		||||
                        $participation->setEndDate($date);
 | 
			
		||||
                        $this->membershipsAffected[] = $participation;
 | 
			
		||||
                        $this->oldMembershipsHashes[] = spl_object_hash($participation);
 | 
			
		||||
 | 
			
		||||
                        if ($participation->getHousehold() !== $this->household) {
 | 
			
		||||
                            $event->setPreviousMembership($participation);
 | 
			
		||||
                            $this->events[] = $event;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
            foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) {
 | 
			
		||||
                if ($participation === $membership) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // send also the event if there was no participation before
 | 
			
		||||
                if (0 === $counter) {
 | 
			
		||||
                    $this->events[] = $event;
 | 
			
		||||
                if ($participation->getStartDate() > $membership->getStartDate()) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
 | 
			
		||||
                    if ($participation->getHousehold() === $this->household
 | 
			
		||||
                        && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()
 | 
			
		||||
                        && $participation->getStartDate() <= $membership->getStartDate()
 | 
			
		||||
                    ) {
 | 
			
		||||
                        $participation->setEndDate($membership->getStartDate());
 | 
			
		||||
                ++$counter;
 | 
			
		||||
 | 
			
		||||
                if ($participation->getEndDate() === null || $participation->getEndDate() > $date) {
 | 
			
		||||
                    $participation->setEndDate($date);
 | 
			
		||||
                    $this->membershipsAffected[] = $participation;
 | 
			
		||||
                    $this->oldMembershipsHashes[] = spl_object_hash($participation);
 | 
			
		||||
 | 
			
		||||
                    if ($participation->getHousehold() !== $this->household) {
 | 
			
		||||
                        $event->setPreviousMembership($participation);
 | 
			
		||||
                        $this->events[] = $event;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // if a members is moved to the same household than the one he belongs to,
 | 
			
		||||
                // we should make it leave the household
 | 
			
		||||
                if ($person->getCurrentHousehold($date) === $this->household) {
 | 
			
		||||
                    $this->leaveMovement($date, $person);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // send also the event if there was no participation before
 | 
			
		||||
            if (0 === $counter) {
 | 
			
		||||
                $this->events[] = $event;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
 | 
			
		||||
                if ($participation->getHousehold() === $this->household
 | 
			
		||||
                    && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()
 | 
			
		||||
                    && $participation->getStartDate() <= $membership->getStartDate()
 | 
			
		||||
                ) {
 | 
			
		||||
                    $participation->setEndDate($membership->getStartDate());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // if there are multiple belongings not sharing household, close the others
 | 
			
		||||
            foreach ($person->getHouseholdParticipations() as $participation) {
 | 
			
		||||
                if ($participation === $membership) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // if there are multiple belongings not sharing household, close the others
 | 
			
		||||
                foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
 | 
			
		||||
                    if ($participation === $membership) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ($participation->getHousehold() === $this->household
 | 
			
		||||
                        && ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate())
 | 
			
		||||
                        && $participation->getStartDate() <= $membership->getStartDate()
 | 
			
		||||
                    ) {
 | 
			
		||||
                if ($participation->getHousehold() === $this->household
 | 
			
		||||
                    && ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate())
 | 
			
		||||
                    && $participation->getStartDate() <= $membership->getStartDate()
 | 
			
		||||
                ) {
 | 
			
		||||
                    if ($participation->getShareHousehold()) {
 | 
			
		||||
                        // if a members is moved to the same household than the one he belongs to,
 | 
			
		||||
                        // we should make it leave the household
 | 
			
		||||
                        $this->leaveMovement($date, $person);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $participation->setEndDate($membership->getStartDate());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -158,6 +164,15 @@ class MembersEditor
 | 
			
		||||
        return null !== $this->household;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes a person leave the household.
 | 
			
		||||
     *
 | 
			
		||||
     * Makes a person leave the household **associated with this editor**.
 | 
			
		||||
     *
 | 
			
		||||
     * @param DateTimeImmutable $date
 | 
			
		||||
     * @param Person $person
 | 
			
		||||
     * @return $this
 | 
			
		||||
     */
 | 
			
		||||
    public function leaveMovement(
 | 
			
		||||
        DateTimeImmutable $date,
 | 
			
		||||
        Person $person
 | 
			
		||||
@@ -167,8 +182,9 @@ class MembersEditor
 | 
			
		||||
 | 
			
		||||
        $criteria->where(
 | 
			
		||||
            $expr->andX(
 | 
			
		||||
                $expr->lt('startDate', $date),
 | 
			
		||||
                $expr->isNull('endDate')
 | 
			
		||||
                $expr->lte('startDate', $date),
 | 
			
		||||
                $expr->isNull('endDate'),
 | 
			
		||||
                $expr->eq('shareHousehold', true)
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -207,10 +207,11 @@ div.banner {
 | 
			
		||||
            span.badge-member {
 | 
			
		||||
                flex-shrink: 0; flex-grow: 0; flex-basis: auto;
 | 
			
		||||
                color: $white;
 | 
			
		||||
                background-color: transparentize($white, 0.85);
 | 
			
		||||
                border: 1px solid transparentize($white, 0.75);
 | 
			
		||||
                border-bottom: 3px solid transparentize( shade-color( $chill-green, 20%), 0.3);
 | 
			
		||||
                border-radius: 8px;
 | 
			
		||||
                padding: 0.2em 0.7em;
 | 
			
		||||
                padding: 0.0em 0.5em;
 | 
			
		||||
                margin-bottom: 0.2em;
 | 
			
		||||
                margin-right: 0.3em;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <ckeditor
 | 
			
		||||
      name="content"
 | 
			
		||||
      v-bind:placeholder="$t('comment.content')"
 | 
			
		||||
      v-bind:placeholder="$t('household_members_editor.positioning.comment_placeholder')"
 | 
			
		||||
      :editor="editor"
 | 
			
		||||
      v-model="content"
 | 
			
		||||
      tag-name="textarea">
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ const appMessages = {
 | 
			
		||||
         persons_to_positionnate: 'Usagers à positionner',
 | 
			
		||||
         holder: "Titulaire",
 | 
			
		||||
         comment: "Commentaire",
 | 
			
		||||
         comment_placeholder: "Associer un commentaire",
 | 
			
		||||
       },
 | 
			
		||||
      app: {
 | 
			
		||||
       next: 'Suivant',
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,55 @@ final class MembersEditorTest extends TestCase
 | 
			
		||||
        $this->assertContains(null, $endDates);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * We test that a leave move is possible when member startdate is same as current date
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public function testLeaveMovementInSameHouseholdFromShareHouseholdToNotShareHouseholdOnSameDate()
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
        $household = new Household();
 | 
			
		||||
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory();
 | 
			
		||||
 | 
			
		||||
        $positionSharing = (new Position())->setShareHousehold(true);
 | 
			
		||||
        $positionNotSharing = (new Position())->setShareHousehold(false);
 | 
			
		||||
 | 
			
		||||
        // create add move
 | 
			
		||||
        $editor = $factory->createEditor($household);
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('today'), $person, $positionSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        self::assertContains($person, $household->getCurrentPersons());
 | 
			
		||||
        self::assertSame($household, $person->getCurrentHousehold());
 | 
			
		||||
        self::assertCount(1, $household->getMembers());
 | 
			
		||||
 | 
			
		||||
        // create leave move
 | 
			
		||||
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
 | 
			
		||||
        $eventDispatcher
 | 
			
		||||
            ->dispatch(Argument::type(PersonAddressMoveEvent::class))
 | 
			
		||||
            ->shouldBeCalled();
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory(
 | 
			
		||||
            $eventDispatcher->reveal(),
 | 
			
		||||
            null
 | 
			
		||||
        );
 | 
			
		||||
        $editor = $factory->createEditor($household);
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('today'), $person, $positionNotSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        $participations = $household->getMembers();
 | 
			
		||||
        self::assertCount(2, $participations);
 | 
			
		||||
 | 
			
		||||
        $sharing = $participations->filter(fn (HouseholdMember $hm) => $hm->getShareHousehold());
 | 
			
		||||
        self::assertCount(1, $sharing);
 | 
			
		||||
 | 
			
		||||
        $notSharing = $participations->filter(fn (HouseholdMember $hm) => !$hm->getShareHousehold());
 | 
			
		||||
        self::assertCount(1, $notSharing);
 | 
			
		||||
 | 
			
		||||
        self::assertNotNull($sharing[0]->getEndDate());
 | 
			
		||||
        self::assertEquals(new DateTimeImmutable('today'), $sharing[0]->getEndDate());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * We test here a move for a person:.
 | 
			
		||||
     *
 | 
			
		||||
@@ -98,8 +147,17 @@ final class MembersEditorTest extends TestCase
 | 
			
		||||
        $this->assertContains($person, $household->getCurrentPersons());
 | 
			
		||||
 | 
			
		||||
        // we do the move to the position not sharing household
 | 
			
		||||
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
 | 
			
		||||
        $eventDispatcher
 | 
			
		||||
            ->dispatch(Argument::type(PersonAddressMoveEvent::class))
 | 
			
		||||
            ->shouldNotBeCalled();
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory(
 | 
			
		||||
            $eventDispatcher->reveal(),
 | 
			
		||||
            null
 | 
			
		||||
        );
 | 
			
		||||
        $editor = $factory->createEditor($household2 = new Household());
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold());
 | 
			
		||||
        $notSharing = $household2->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold());
 | 
			
		||||
@@ -118,7 +176,7 @@ final class MembersEditorTest extends TestCase
 | 
			
		||||
     * * which was in a position "sharing household"
 | 
			
		||||
     * * which move to the same household, in a position "not sharing household"
 | 
			
		||||
     */
 | 
			
		||||
    public function testMoveFromSharingHouseholdToNotSharingHousehouldInSamehousehold()
 | 
			
		||||
    public function testMoveFromSharingHouseholdToNotSharingHousehouldInSamehouseholdOnDifferentDate()
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
        $household = new Household();
 | 
			
		||||
@@ -134,8 +192,17 @@ final class MembersEditorTest extends TestCase
 | 
			
		||||
        $this->assertContains($person, $household->getCurrentPersons());
 | 
			
		||||
 | 
			
		||||
        // we do the move to the position not sharing household
 | 
			
		||||
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
 | 
			
		||||
        $eventDispatcher
 | 
			
		||||
            ->dispatch(Argument::type(PersonAddressMoveEvent::class))
 | 
			
		||||
            ->shouldBeCalled();
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory(
 | 
			
		||||
            $eventDispatcher->reveal(),
 | 
			
		||||
            null
 | 
			
		||||
        );
 | 
			
		||||
        $editor = $factory->createEditor($household);
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold());
 | 
			
		||||
        $notSharing = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold());
 | 
			
		||||
@@ -148,6 +215,84 @@ final class MembersEditorTest extends TestCase
 | 
			
		||||
        $this->assertContains($person, $notSharing->map($getPerson));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * We test here a move for a person:.
 | 
			
		||||
     *
 | 
			
		||||
     * * which was in a position "not sharing household"
 | 
			
		||||
     * * which move to the same household, in a position "sharing household"
 | 
			
		||||
     */
 | 
			
		||||
    public function testMoveFromNotSharingHouseholdToSharingHousehouldInSamehousehold()
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
        $household = new Household();
 | 
			
		||||
        $positionSharing = (new Position())->setShareHousehold(true);
 | 
			
		||||
        $positionNotSharing = (new Position())->setShareHousehold(false);
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory();
 | 
			
		||||
        $editor = $factory->createEditor($household);
 | 
			
		||||
 | 
			
		||||
        // we add the member to the household, at the position "not sharing"
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('1 month ago'), $person, $positionNotSharing);
 | 
			
		||||
 | 
			
		||||
        // double check that the person is in the household
 | 
			
		||||
        $this->assertContains($person, $household->getCurrentPersons());
 | 
			
		||||
 | 
			
		||||
        // we do the move to the position sharing household
 | 
			
		||||
        $editor = $factory->createEditor($household);
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionSharing);
 | 
			
		||||
 | 
			
		||||
        self::assertCount(2, $household->getMembers());
 | 
			
		||||
 | 
			
		||||
        $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold());
 | 
			
		||||
        $notSharing = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold());
 | 
			
		||||
 | 
			
		||||
        $this->assertCount(0, $notSharing);
 | 
			
		||||
        $this->assertCount(1, $sharings);
 | 
			
		||||
 | 
			
		||||
        $getPerson = static fn (HouseholdMember $m) => $m->getPerson();
 | 
			
		||||
 | 
			
		||||
        $this->assertContains($person, $sharings->map($getPerson));
 | 
			
		||||
        $this->assertNotContains($person, $notSharing->map($getPerson));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * We test here a move for a person:.
 | 
			
		||||
     *
 | 
			
		||||
     * * which was in a position "not sharing household"
 | 
			
		||||
     * * which move to the same household, in a position "sharing household"
 | 
			
		||||
     */
 | 
			
		||||
    public function testMoveFromNotSharingHouseholdToSharingHousehouldInSamehouseholdOnSameDate()
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
        $household = new Household();
 | 
			
		||||
        $positionSharing = (new Position())->setShareHousehold(true);
 | 
			
		||||
        $positionNotSharing = (new Position())->setShareHousehold(false);
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory();
 | 
			
		||||
        $editor = $factory->createEditor($household);
 | 
			
		||||
 | 
			
		||||
        // we add the member to the household, at the position "not sharing"
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('today'), $person, $positionNotSharing);
 | 
			
		||||
 | 
			
		||||
        // double check that the person is in the household
 | 
			
		||||
        $this->assertContains($person, $household->getCurrentPersons());
 | 
			
		||||
 | 
			
		||||
        // we do the move to the position sharing household
 | 
			
		||||
        $editor = $factory->createEditor($household);
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('today'), $person, $positionSharing);
 | 
			
		||||
 | 
			
		||||
        self::assertCount(2, $household->getMembers());
 | 
			
		||||
 | 
			
		||||
        $sharings = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => $m->getShareHousehold());
 | 
			
		||||
        $notSharing = $household->getCurrentMembers()->filter(static fn (HouseholdMember $m) => !$m->getShareHousehold());
 | 
			
		||||
 | 
			
		||||
        $this->assertCount(0, $notSharing);
 | 
			
		||||
        $this->assertCount(1, $sharings);
 | 
			
		||||
 | 
			
		||||
        $getPerson = static fn (HouseholdMember $m) => $m->getPerson();
 | 
			
		||||
 | 
			
		||||
        $this->assertContains($person, $sharings->map($getPerson));
 | 
			
		||||
        $this->assertNotContains($person, $notSharing->map($getPerson));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testMovePersonWithoutSharedHousehold()
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
@@ -235,13 +380,26 @@ final class MembersEditorTest extends TestCase
 | 
			
		||||
        $this->assertEquals($date, $membership1->getEndDate());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testPostMoveToAPositionNotSharingHousehold()
 | 
			
		||||
    public function testPostMoveToAPositionNotSharingHouseholdOnSameDay()
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
        $position = (new Position())
 | 
			
		||||
        $positionNotSharing = (new Position())
 | 
			
		||||
            ->setShareHousehold(false);
 | 
			
		||||
        $positionSharing = (new Position())->setShareHousehold(true);
 | 
			
		||||
        $household1 = new Household();
 | 
			
		||||
        $household2 = new Household();
 | 
			
		||||
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory();
 | 
			
		||||
        $editor = $factory->createEditor($household1);
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('today'), $person, $positionSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentPersons());
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentMembers()
 | 
			
		||||
            ->filter(fn (HouseholdMember $m) => $m->getShareHousehold())
 | 
			
		||||
            ->map(fn (HouseholdMember $m) => $m->getPerson()));
 | 
			
		||||
        self::assertSame($household1, $person->getCurrentHousehold());
 | 
			
		||||
 | 
			
		||||
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
 | 
			
		||||
        $eventDispatcher
 | 
			
		||||
            ->dispatch(Argument::type(PersonAddressMoveEvent::class))
 | 
			
		||||
@@ -250,11 +408,78 @@ final class MembersEditorTest extends TestCase
 | 
			
		||||
            $eventDispatcher->reveal(),
 | 
			
		||||
            null
 | 
			
		||||
        );
 | 
			
		||||
        $editor = $factory->createEditor($household1);
 | 
			
		||||
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('now'), $person, $position);
 | 
			
		||||
        $editor = $factory->createEditor($household2);
 | 
			
		||||
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('today'), $person, $positionNotSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        // $household1 still contains $person
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentPersons());
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentMembers()
 | 
			
		||||
            ->filter(fn (HouseholdMember $m) => $m->getShareHousehold())
 | 
			
		||||
            ->map(fn (HouseholdMember $m) => $m->getPerson()));
 | 
			
		||||
        self::assertSame($household1, $person->getCurrentHousehold());
 | 
			
		||||
 | 
			
		||||
        // $household2 contains $person at non-sharing position
 | 
			
		||||
        self::assertContains($person, $household2->getCurrentMembers()
 | 
			
		||||
            ->filter(fn (HouseholdMember $m) => !$m->getShareHousehold())
 | 
			
		||||
            ->map(fn (HouseholdMember $m) => $m->getPerson()));
 | 
			
		||||
        self::assertContains(
 | 
			
		||||
            $household2,
 | 
			
		||||
            $person->getHouseholdParticipationsNotShareHousehold()
 | 
			
		||||
                ->map(fn (HouseholdMember $hm) => $hm->getHousehold())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testPostMoveToAPositionNotSharingHouseholdOnDifferentDays()
 | 
			
		||||
    {
 | 
			
		||||
        $person = new Person();
 | 
			
		||||
        $positionNotSharing = (new Position())
 | 
			
		||||
            ->setShareHousehold(false);
 | 
			
		||||
        $positionSharing = (new Position())->setShareHousehold(true);
 | 
			
		||||
        $household1 = new Household();
 | 
			
		||||
        $household2 = new Household();
 | 
			
		||||
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory();
 | 
			
		||||
        $editor = $factory->createEditor($household1);
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('1 year ago'), $person, $positionSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentPersons());
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentMembers()
 | 
			
		||||
            ->filter(fn (HouseholdMember $m) => $m->getShareHousehold())
 | 
			
		||||
            ->map(fn (HouseholdMember $m) => $m->getPerson()));
 | 
			
		||||
        self::assertSame($household1, $person->getCurrentHousehold());
 | 
			
		||||
 | 
			
		||||
        $eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
 | 
			
		||||
        $eventDispatcher
 | 
			
		||||
            ->dispatch(Argument::type(PersonAddressMoveEvent::class))
 | 
			
		||||
            ->shouldNotBeCalled();
 | 
			
		||||
        $factory = $this->buildMembersEditorFactory(
 | 
			
		||||
            $eventDispatcher->reveal(),
 | 
			
		||||
            null
 | 
			
		||||
        );
 | 
			
		||||
        $editor = $factory->createEditor($household2);
 | 
			
		||||
 | 
			
		||||
        $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing);
 | 
			
		||||
        $editor->postMove();
 | 
			
		||||
 | 
			
		||||
        // $household1 still contains $person
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentPersons());
 | 
			
		||||
        self::assertContains($person, $household1->getCurrentMembers()
 | 
			
		||||
            ->filter(fn (HouseholdMember $m) => $m->getShareHousehold())
 | 
			
		||||
            ->map(fn (HouseholdMember $m) => $m->getPerson()));
 | 
			
		||||
        self::assertSame($household1, $person->getCurrentHousehold());
 | 
			
		||||
 | 
			
		||||
        // $household2 contains $person at non-sharing position
 | 
			
		||||
        self::assertContains($person, $household2->getCurrentMembers()
 | 
			
		||||
            ->filter(fn (HouseholdMember $m) => !$m->getShareHousehold())
 | 
			
		||||
            ->map(fn (HouseholdMember $m) => $m->getPerson()));
 | 
			
		||||
        self::assertContains(
 | 
			
		||||
            $household2,
 | 
			
		||||
            $person->getHouseholdParticipationsNotShareHousehold()
 | 
			
		||||
                ->map(fn (HouseholdMember $hm) => $hm->getHousehold())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testPostMoveToAPositionSharingHouseholdAndSameHousehold()
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ class AccompanyingPeriodContextTest extends KernelTestCase
 | 
			
		||||
    ): void {
 | 
			
		||||
        $context = $this->buildContext();
 | 
			
		||||
        $template = new DocGeneratorTemplate();
 | 
			
		||||
        $template->setName(["fr" =>"test"])->setContext(AccompanyingPeriodContext::class)
 | 
			
		||||
        $template->setName(["fr" => "test"])->setContext(AccompanyingPeriodContext::class)
 | 
			
		||||
            ->setDescription("description")->setActive(true)
 | 
			
		||||
            ->setOptions($options);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -102,7 +102,7 @@ final class PersonContextTest extends KernelTestCase
 | 
			
		||||
            self::$container->get(ThirdPartyRepository::class)
 | 
			
		||||
        );
 | 
			
		||||
        $template = new DocGeneratorTemplate();
 | 
			
		||||
        $template->setName(["fr" =>"test"])->setContext(AccompanyingPeriodContext::class)
 | 
			
		||||
        $template->setName(["fr" => "test"])->setContext(AccompanyingPeriodContext::class)
 | 
			
		||||
            ->setDescription("description")->setActive(true)
 | 
			
		||||
            ->setOptions($options);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,11 +47,9 @@ class RelationshipNoDuplicateValidator extends ConstraintValidator
 | 
			
		||||
 | 
			
		||||
        foreach ($relationships as $r) {
 | 
			
		||||
            if (spl_object_hash($r) !== spl_object_hash($value)
 | 
			
		||||
                and
 | 
			
		||||
                (
 | 
			
		||||
                and (
 | 
			
		||||
                    ($r->getFromPerson() === $fromPerson and $r->getToPerson() === $toPerson)
 | 
			
		||||
                    ||
 | 
			
		||||
                    ($r->getFromPerson() === $toPerson and $r->getToPerson() === $fromPerson)
 | 
			
		||||
                    || ($r->getFromPerson() === $toPerson and $r->getToPerson() === $fromPerson)
 | 
			
		||||
                )
 | 
			
		||||
            ) {
 | 
			
		||||
                $this->context->buildViolation($constraint->message)
 | 
			
		||||
 
 | 
			
		||||
@@ -1225,6 +1225,7 @@ export:
 | 
			
		||||
        acpw:
 | 
			
		||||
            List of accompanying period works: Liste des actions
 | 
			
		||||
            List description: Génère une liste des actions d'accompagnement, filtrée sur différents paramètres.
 | 
			
		||||
            Date of calculation for associated elements: Date de calcul des éléments associés
 | 
			
		||||
            help_description: L'agent traitant de l'action sera valide à cette date
 | 
			
		||||
            id: Identifiant de l'action
 | 
			
		||||
            startDate: Date de début
 | 
			
		||||
 
 | 
			
		||||
@@ -43,5 +43,4 @@ class SingleTaskStateRepository
 | 
			
		||||
 | 
			
		||||
        return $states;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user