diff --git a/.changes/unreleased/Feature-20231218-153151.yaml b/.changes/unreleased/Feature-20231218-153151.yaml new file mode 100644 index 000000000..8a842bfa2 --- /dev/null +++ b/.changes/unreleased/Feature-20231218-153151.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Create new filter for persons having a participation in an accompanying period + during a certain time span +time: 2023-12-18T15:31:51.489901829+01:00 +custom: + Issue: "231" diff --git a/.changes/unreleased/Feature-20240122-124849.yaml b/.changes/unreleased/Feature-20240122-124849.yaml new file mode 100644 index 000000000..82703bd2c --- /dev/null +++ b/.changes/unreleased/Feature-20240122-124849.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: '[Export][List of accompanyign period] Add two columns: the list of persons + participating to the period, and their ids' +time: 2024-01-22T12:48:49.824833412+01:00 +custom: + Issue: "241" diff --git a/.changes/unreleased/Feature-20240129-133319.yaml b/.changes/unreleased/Feature-20240129-133319.yaml new file mode 100644 index 000000000..ff2b96ebf --- /dev/null +++ b/.changes/unreleased/Feature-20240129-133319.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: 'Add capability to generate export about change of steps of accompanying period, and generate exports for this' +time: 2024-01-29T13:33:19.190365565+01:00 +custom: + Issue: "244" diff --git a/.changes/unreleased/Feature-20240207-103951.yaml b/.changes/unreleased/Feature-20240207-103951.yaml new file mode 100644 index 000000000..b0f71b7cf --- /dev/null +++ b/.changes/unreleased/Feature-20240207-103951.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: 'Export: group accompanying period by person participating' +time: 2024-02-07T10:39:51.97331052+01:00 +custom: + Issue: "253" diff --git a/.changes/unreleased/Feature-20240207-114629.yaml b/.changes/unreleased/Feature-20240207-114629.yaml new file mode 100644 index 000000000..813d14ba7 --- /dev/null +++ b/.changes/unreleased/Feature-20240207-114629.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: 'Export: add filter for courses not linked to a reference address' +time: 2024-02-07T11:46:29.491027007+01:00 +custom: + Issue: "243" diff --git a/.changes/unreleased/Feature-20240207-164038.yaml b/.changes/unreleased/Feature-20240207-164038.yaml new file mode 100644 index 000000000..ff5096036 --- /dev/null +++ b/.changes/unreleased/Feature-20240207-164038.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: Allow to group activities linked with accompanying period by reason +time: 2024-02-07T16:40:38.408575109+01:00 +custom: + Issue: "229" diff --git a/.changes/unreleased/Fixed-20231129-113138.yaml b/.changes/unreleased/Fixed-20231129-113138.yaml new file mode 100644 index 000000000..efe9b10c1 --- /dev/null +++ b/.changes/unreleased/Fixed-20231129-113138.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Fix error in logs about wrong typing of eventArgs in onEditNotificationComment + method +time: 2023-11-29T11:31:38.933538592+01:00 +custom: + Issue: "220" diff --git a/.changes/unreleased/Fixed-20240130-140301.yaml b/.changes/unreleased/Fixed-20240130-140301.yaml new file mode 100644 index 000000000..45fb94f6a --- /dev/null +++ b/.changes/unreleased/Fixed-20240130-140301.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Fix the conditions upon which social actions should be optional or required + in relation to social issues within the activity creation form +time: 2024-01-30T14:03:01.942955636+01:00 +custom: + Issue: "256" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e976a4d18..945d13532 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,6 +34,8 @@ variables: DEFAULT_CARRIER_CODE: BE # force a timezone TZ: Europe/Brussels + # avoid direct deprecations (using symfony phpunit bridge: https://symfony.com/doc/4.x/components/phpunit_bridge.html#internal-deprecations + SYMFONY_DEPRECATIONS_HELPER: max[total]=99999999&max[self]=0&max[direct]=0&verbose=0 stages: - Composer install diff --git a/docs/source/installation/index.rst b/docs/source/installation/index.rst index 9e6f2ed87..67a0b6ec6 100644 --- a/docs/source/installation/index.rst +++ b/docs/source/installation/index.rst @@ -48,7 +48,7 @@ Clone or download the chill-skeleton project and `cd` into the main directory. .. code-block:: bash - git clone https://gitlab.com/Chill-Projet/chill-skeleton-basic.git + git clone https://gitea.champs-libres.be/Chill-project/chill-skeleton-basic.git cd chill-skeleton-basic diff --git a/exports_alias_conventions.md b/exports_alias_conventions.md index eb0545702..d4d034314 100644 --- a/exports_alias_conventions.md +++ b/exports_alias_conventions.md @@ -5,72 +5,74 @@ Add condition with distinct alias on each export join clauses (Indicators + Filt These are alias conventions : -| Entity | Join | Attribute | Alias | -|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:---------------------------------------| -| AccompanyingPeriod::class | | | acp | -| | AccompanyingPeriodWork::class | acp.works | acpw | -| | AccompanyingPeriodParticipation::class | acp.participations | acppart | -| | Location::class | acp.administrativeLocation | acploc | -| | ClosingMotive::class | acp.closingMotive | acpmotive | -| | UserJob::class | acp.job | acpjob | -| | Origin::class | acp.origin | acporigin | -| | Scope::class | acp.scopes | acpscope | -| | SocialIssue::class | acp.socialIssues | acpsocialissue | -| | User::class | acp.user | acpuser | -| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories | -| | AccompanyingPeriodInfo::class | not existing (using custom WITH clause) | acpinfo | -| AccompanyingPeriodWork::class | | | acpw | -| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | -| | SocialAction::class | acpw.socialAction | acpwsocialaction | -| | Goal::class | acpw.goals | goal | -| | Result::class | acpw.results | result | -| AccompanyingPeriodParticipation::class | | | acppart | -| | Person::class | acppart.person | partperson | -| AccompanyingPeriodWorkEvaluation::class | | | workeval | -| | Evaluation::class | workeval.evaluation | eval | -| AccompanyingPeriodInfo::class | | | acpinfo | -| | User::class | acpinfo.user | acpinfo_user | -| Goal::class | | | goal | -| | Result::class | goal.results | goalresult | -| Person::class | | | person | -| | Center::class | person.center | center | -| | HouseholdMember::class | partperson.householdParticipations | householdmember | -| | MaritalStatus::class | person.maritalStatus | personmarital | -| | VendeePerson::class | | vp | -| | VendeePersonMineur::class | | vpm | -| | CurrentPersonAddress::class | person.currentPersonAddress | currentPersonAddress (on a given date) | -| ResidentialAddress::class | | | resaddr | -| | ThirdParty::class | resaddr.hostThirdParty | tparty | -| ThirdParty::class | | | tparty | -| | ThirdPartyCategory::class | tparty.categories | tpartycat | -| HouseholdMember::class | | | householdmember | -| | Household::class | householdmember.household | household | -| | Person::class | householdmember.person | memberperson | -| | | memberperson.center | membercenter | -| Household::class | | | household | -| | HouseholdComposition::class | household.compositions | composition | -| Activity::class | | | activity | -| | Person::class | activity.person | actperson | -| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp | -| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity | -| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity | -| | ActivityType::class | activity.activityType | acttype | -| | Location::class | activity.location | actloc | -| | SocialAction::class | activity.socialActions | actsocialaction | -| | SocialIssue::class | activity.socialIssues | actsocialssue | -| | ThirdParty::class | activity.thirdParties | acttparty | -| | User::class | activity.user | actuser | -| | User::class | activity.users | actusers | -| | ActivityReason::class | activity.reasons | actreasons | -| | Center::class | actperson.center | actcenter | -| | Person::class | activity.createdBy | actcreator | -| ActivityReason::class | | | actreasons | -| | ActivityReasonCategory::class | actreason.category | actreasoncat | -| Calendar::class | | | cal | -| | CancelReason::class | cal.cancelReason | calcancel | -| | Location::class | cal.location | calloc | -| | User::class | cal.user | caluser | -| VendeePerson::class | | | vp | -| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof | -| | StatutLogement::class | vp.statutLogement | vplog | -| | TempsDeTravail::class | vp.tempsDeTravail | vptt | +| Entity | Join | Attribute | Alias | +|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:-------------------------------------------| +| AccompanyingPeriodStepHistory::class | | | acpstephistory (contexte ACP_STEP_HISTORY) | +| | AccompanyingPeriod::class | acpstephistory.period | acp | +| AccompanyingPeriod::class | | | acp | +| | AccompanyingPeriodWork::class | acp.works | acpw | +| | AccompanyingPeriodParticipation::class | acp.participations | acppart | +| | Location::class | acp.administrativeLocation | acploc | +| | ClosingMotive::class | acp.closingMotive | acpmotive | +| | UserJob::class | acp.job | acpjob | +| | Origin::class | acp.origin | acporigin | +| | Scope::class | acp.scopes | acpscope | +| | SocialIssue::class | acp.socialIssues | acpsocialissue | +| | User::class | acp.user | acpuser | +| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories | +| | AccompanyingPeriodInfo::class | not existing (using custom WITH clause) | acpinfo | +| AccompanyingPeriodWork::class | | | acpw | +| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | +| | SocialAction::class | acpw.socialAction | acpwsocialaction | +| | Goal::class | acpw.goals | goal | +| | Result::class | acpw.results | result | +| AccompanyingPeriodParticipation::class | | | acppart | +| | Person::class | acppart.person | partperson | +| AccompanyingPeriodWorkEvaluation::class | | | workeval | +| | Evaluation::class | workeval.evaluation | eval | +| AccompanyingPeriodInfo::class | | | acpinfo | +| | User::class | acpinfo.user | acpinfo_user | +| Goal::class | | | goal | +| | Result::class | goal.results | goalresult | +| Person::class | | | person | +| | Center::class | person.center | center | +| | HouseholdMember::class | partperson.householdParticipations | householdmember | +| | MaritalStatus::class | person.maritalStatus | personmarital | +| | VendeePerson::class | | vp | +| | VendeePersonMineur::class | | vpm | +| | CurrentPersonAddress::class | person.currentPersonAddress | currentPersonAddress (on a given date) | +| ResidentialAddress::class | | | resaddr | +| | ThirdParty::class | resaddr.hostThirdParty | tparty | +| ThirdParty::class | | | tparty | +| | ThirdPartyCategory::class | tparty.categories | tpartycat | +| HouseholdMember::class | | | householdmember | +| | Household::class | householdmember.household | household | +| | Person::class | householdmember.person | memberperson | +| | | memberperson.center | membercenter | +| Household::class | | | household | +| | HouseholdComposition::class | household.compositions | composition | +| Activity::class | | | activity | +| | Person::class | activity.person | actperson | +| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp | +| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity | +| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity | +| | ActivityType::class | activity.activityType | acttype | +| | Location::class | activity.location | actloc | +| | SocialAction::class | activity.socialActions | actsocialaction | +| | SocialIssue::class | activity.socialIssues | actsocialssue | +| | ThirdParty::class | activity.thirdParties | acttparty | +| | User::class | activity.user | actuser | +| | User::class | activity.users | actusers | +| | ActivityReason::class | activity.reasons | actreasons | +| | Center::class | actperson.center | actcenter | +| | Person::class | activity.createdBy | actcreator | +| ActivityReason::class | | | actreasons | +| | ActivityReasonCategory::class | actreason.category | actreasoncat | +| Calendar::class | | | cal | +| | CancelReason::class | cal.cancelReason | calcancel | +| | Location::class | cal.location | calloc | +| | User::class | cal.user | caluser | +| VendeePerson::class | | | vp | +| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof | +| | StatutLogement::class | vp.statutLogement | vplog | +| | TempsDeTravail::class | vp.tempsDeTravail | vptt | diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityType.php b/src/Bundle/ChillActivityBundle/Entity/ActivityType.php index c14c8292b..96c369b39 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityType.php @@ -291,7 +291,11 @@ class ActivityType public function checkSocialActionsVisibility(ExecutionContextInterface $context, mixed $payload) { if ($this->socialIssuesVisible !== $this->socialActionsVisible) { - if (!(2 === $this->socialIssuesVisible && 1 === $this->socialActionsVisible)) { + // if social issues are invisible then social actions cannot be optional or required + if social issues are optional then social actions shouldn't be required + if ( + (0 === $this->socialIssuesVisible && (1 === $this->socialActionsVisible || 2 === $this->socialActionsVisible)) + || (1 === $this->socialIssuesVisible && 2 === $this->socialActionsVisible) + ) { $context ->buildViolation('The socialActionsVisible value is not compatible with the socialIssuesVisible value') ->atPath('socialActionsVisible') diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php similarity index 86% rename from src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php index 569c4ca9a..9945fd80a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php @@ -9,7 +9,7 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Export\Aggregator\PersonAggregators; +namespace Chill\ActivityBundle\Export\Aggregator; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityReasonCategoryRepository; @@ -25,8 +25,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class ActivityReasonAggregator implements AggregatorInterface, ExportElementValidatedInterface { - public function __construct(protected ActivityReasonCategoryRepository $activityReasonCategoryRepository, protected ActivityReasonRepository $activityReasonRepository, protected TranslatableStringHelper $translatableStringHelper) - { + public function __construct( + protected ActivityReasonCategoryRepository $activityReasonCategoryRepository, + protected ActivityReasonRepository $activityReasonRepository, + protected TranslatableStringHelper $translatableStringHelper + ) { } public function addRole(): ?string @@ -51,7 +54,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali // make a jointure only if needed if (!\in_array('actreasons', $qb->getAllAliases(), true)) { - $qb->innerJoin('activity.reasons', 'actreasons'); + $qb->leftJoin('activity.reasons', 'actreasons'); } // join category if necessary @@ -62,19 +65,12 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali } } - // add the "group by" part - $groupBy = $qb->getDQLPart('groupBy'); - - if (\count($groupBy) > 0) { - $qb->addGroupBy($alias); - } else { - $qb->groupBy($alias); - } + $qb->addGroupBy($alias); } public function applyOn(): string { - return Declarations::ACTIVITY_PERSON; + return Declarations::ACTIVITY; } public function buildForm(FormBuilderInterface $builder) @@ -96,7 +92,9 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali public function getFormDefaultData(): array { - return []; + return [ + 'level' => 'reasons', + ]; } public function getLabels($key, array $values, $data) diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php similarity index 69% rename from src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php rename to src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php index 004de0b99..3e1d473f0 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/PersonAggregators/ActivityReasonAggregatorTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ActivityReasonAggregatorTest.php @@ -9,10 +9,10 @@ declare(strict_types=1); * the LICENSE file that was distributed with this source code. */ -namespace Chill\ActivityBundle\Tests\Export\Aggregator\PersonAggregators; +namespace Chill\ActivityBundle\Tests\Export\Aggregator; use Chill\ActivityBundle\Entity\Activity; -use Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator; +use Chill\ActivityBundle\Export\Aggregator\ActivityReasonAggregator; use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Doctrine\ORM\EntityManagerInterface; use Prophecy\PhpUnit\ProphecyTrait; @@ -33,14 +33,14 @@ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest self::bootKernel(); $this->aggregator = self::$container->get(ActivityReasonAggregator::class); + /* + $request = $this->prophesize() + ->willExtend(\Symfony\Component\HttpFoundation\Request::class); - $request = $this->prophesize() - ->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); - $request->getLocale()->willReturn('fr'); - - self::$container->get('request_stack') - ->push($request->reveal()); + self::$container->get('request_stack') + ->push($request->reveal());*/ } public function getAggregator() @@ -65,7 +65,12 @@ final class ActivityReasonAggregatorTest extends AbstractAggregatorTest return [ $em->createQueryBuilder() ->select('count(activity.id)') - ->from(Activity::class, 'activity'), + ->from(Activity::class, 'activity') + ->join('activity.person', 'person'), + $em->createQueryBuilder() + ->select('count(activity.id)') + ->from(Activity::class, 'activity') + ->join('activity.accompanyingPeriod', 'accompanyingPeriod'), $em->createQueryBuilder() ->select('count(activity.id)') ->from(Activity::class, 'activity') diff --git a/src/Bundle/ChillActivityBundle/config/services/export.yaml b/src/Bundle/ChillActivityBundle/config/services/export.yaml index ef691ba0d..b52911295 100644 --- a/src/Bundle/ChillActivityBundle/config/services/export.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/export.yaml @@ -153,7 +153,7 @@ services: ## Aggregators - Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator: + Chill\ActivityBundle\Export\Aggregator\ActivityReasonAggregator: tags: - { name: chill.export_aggregator, alias: activity_reason_aggregator } diff --git a/src/Bundle/ChillMainBundle/Notification/Counter/NotificationByUserCounter.php b/src/Bundle/ChillMainBundle/Notification/Counter/NotificationByUserCounter.php index 81a8bb3bb..e08543e8c 100644 --- a/src/Bundle/ChillMainBundle/Notification/Counter/NotificationByUserCounter.php +++ b/src/Bundle/ChillMainBundle/Notification/Counter/NotificationByUserCounter.php @@ -17,6 +17,7 @@ use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\NotificationRepository; use Chill\MainBundle\Templating\UI\NotificationCounterInterface; use Doctrine\ORM\Event\PostPersistEventArgs; +use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Event\PreFlushEventArgs; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -62,7 +63,12 @@ final readonly class NotificationByUserCounter implements NotificationCounterInt return 'chill_main_notif_unread_by_'.$user->getId(); } - public function onEditNotificationComment(NotificationComment $notificationComment, PostPersistEventArgs $eventArgs): void + public function onPersistNotificationComment(NotificationComment $notificationComment, PostPersistEventArgs $eventArgs): void + { + $this->resetCacheForNotification($notificationComment->getNotification()); + } + + public function onEditNotificationComment(NotificationComment $notificationComment, PostUpdateEventArgs $eventArgs): void { $this->resetCacheForNotification($notificationComment->getNotification()); } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php index a0474bb3f..a61d92b4a 100644 --- a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php +++ b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php @@ -37,7 +37,7 @@ final readonly class ScopeResolverDispatcher /** * @return Scope|iterable|Scope|null */ - public function resolveScope(mixed $entity, ?array $options = []): Scope|iterable|null + public function resolveScope(mixed $entity, ?array $options = []): iterable|Scope|null { foreach ($this->resolvers as $resolver) { if ($resolver->supports($entity, $options)) { diff --git a/src/Bundle/ChillMainBundle/config/services/notification.yaml b/src/Bundle/ChillMainBundle/config/services/notification.yaml index 29cbce946..be3252003 100644 --- a/src/Bundle/ChillMainBundle/config/services/notification.yaml +++ b/src/Bundle/ChillMainBundle/config/services/notification.yaml @@ -41,7 +41,7 @@ services: entity: 'Chill\MainBundle\Entity\NotificationComment' # set the 'lazy' option to TRUE to only instantiate listeners when they are used lazy: true - method: 'onEditNotificationComment' + method: 'onPersistNotificationComment' Chill\MainBundle\Notification\Email\NotificationMailer: autowire: true diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php index a2ced7fee..6acca44e3 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php @@ -62,7 +62,7 @@ class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle\Contr $workflow = $this->registry->get($accompanyingCourse); if ($workflow->can($accompanyingCourse, 'close')) { - $workflow->apply($accompanyingCourse, 'close'); + $workflow->apply($accompanyingCourse, 'close', ['closing_motive' => $form['closingMotive']->getData()]); $em->flush(); diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 3d5c0bc64..ebbfedeb3 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -98,6 +98,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/exports_accompanying_course.yaml'); $loader->load('services/exports_social_actions.yaml'); $loader->load('services/exports_evaluation.yaml'); + $loader->load('services/exports_accompanying_period_step_history.yaml'); } $loader->load('services/exports_household.yaml'); diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 9c88a4a69..4f9c480d0 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -1449,7 +1449,7 @@ class AccompanyingPeriod implements return $this; } - public function setStep(string $step): self + public function setStep(string $step, array $context = []): self { $previous = $this->step; @@ -1464,7 +1464,7 @@ class AccompanyingPeriod implements $history = new AccompanyingPeriodStepHistory(); $history->setStep($this->step)->setStartDate(new \DateTimeImmutable('now')); - $this->addStepHistory($history); + $this->addStepHistory($history, $context); } return $this; @@ -1507,11 +1507,14 @@ class AccompanyingPeriod implements return $this; } - private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory): self + private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory, array $context = []): self { if (!$this->stepHistories->contains($stepHistory)) { $this->stepHistories[] = $stepHistory; $stepHistory->setPeriod($this); + if (($context['closing_motive'] ?? null) instanceof ClosingMotive) { + $stepHistory->setClosingMotive($context['closing_motive']); + } $this->ensureStepContinuity(); } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodStepHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodStepHistory.php index 15b2f0d2e..c25c11e69 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodStepHistory.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodStepHistory.php @@ -59,6 +59,13 @@ class AccompanyingPeriodStepHistory implements TrackCreationInterface, TrackUpda */ private string $step; + /** + * @ORM\ManyToOne(targetEntity=ClosingMotive::class) + * + * @ORM\JoinColumn(nullable=true) + */ + private ?ClosingMotive $closingMotive = null; + public function getEndDate(): ?\DateTimeImmutable { return $this->endDate; @@ -114,4 +121,16 @@ class AccompanyingPeriodStepHistory implements TrackCreationInterface, TrackUpda return $this; } + + public function getClosingMotive(): ?ClosingMotive + { + return $this->closingMotive; + } + + public function setClosingMotive(?ClosingMotive $closingMotive): self + { + $this->closingMotive = $closingMotive; + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php index f736913ae..ce56fa013 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php +++ b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php @@ -147,10 +147,16 @@ class Relationship implements TrackCreationInterface, TrackUpdateInterface public function getOpposite(Person $counterpart): Person { if ($this->fromPerson !== $counterpart && $this->toPerson !== $counterpart) { - throw new \RuntimeException('the counterpart is neither the from nor to person for this relationship'); + // during tests, comparing using equality does not work. We have to compare the ids + if ( + ($this->fromPerson->getId() === $counterpart->getId() && $this->toPerson->getId() === $counterpart->getId()) + || null === $counterpart->getId() + ) { + throw new \RuntimeException(sprintf('the counterpart is neither the from nor to person for this relationship, expecting counterpart from %d and available %d and %d', $counterpart->getId(), $this->getFromPerson()->getId(), $this->getToPerson()->getId())); + } } - if ($this->fromPerson === $counterpart) { + if ($this->fromPerson === $counterpart || $this->fromPerson->getId() === $counterpart->getId()) { return $this->toPerson; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php new file mode 100644 index 000000000..632d0402d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php @@ -0,0 +1,78 @@ + $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.course.by-user.header'), + default => throw new \UnexpectedValueException('key not supported: '.$key), + }; + } + + public function getQueryKeys($data) + { + return [self::KEY]; + } + + public function getTitle() + { + return 'export.aggregator.course.by-user.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $k = self::KEY; + + if (!in_array('acppart', $qb->getAllAliases(), true)) { + $qb->join('acp.participations', 'acppart'); + } + + $qb->addSelect("IDENTITY(acppart.person) AS {$k}") + ->addGroupBy($k); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php new file mode 100644 index 000000000..b5776ebeb --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php @@ -0,0 +1,84 @@ +closingMotiveRepository->find((int) $value)) { + return ''; + } + + return $this->closingMotiveRender->renderString($closingMotive, []); + }; + } + + public function getQueryKeys($data) + { + return [ + self::KEY, + ]; + } + + public function getTitle() + { + return 'export.aggregator.step_history.by_closing_motive.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->addSelect('IDENTITY(acpstephistory.closingMotive) AS '.self::KEY) + ->addGroupBy(self::KEY); + } + + public function applyOn() + { + return Declarations::ACP_STEP_HISTORY; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php new file mode 100644 index 000000000..fbd80c7a5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php @@ -0,0 +1,90 @@ +add('frequency', ChoiceType::class, [ + 'choices' => array_combine( + array_map(fn (DateGroupingChoiceEnum $c) => 'export.enum.frequency.'.$c->value, DateGroupingChoiceEnum::cases()), + array_map(fn (DateGroupingChoiceEnum $c) => $c->value, DateGroupingChoiceEnum::cases()), + ), + 'label' => 'export.aggregator.course.by_opening_date.frequency', + 'multiple' => false, + 'expanded' => true, + ]); + } + + public function getFormDefaultData(): array + { + return ['frequency' => DateGroupingChoiceEnum::YEAR->value]; + } + + public function getLabels($key, array $values, mixed $data) + { + return function (?string $value): string { + if ('_header' === $value) { + return 'export.aggregator.step_history.by_date.header'; + } + + if (null === $value || '' === $value) { + return ''; + } + + return $value; + }; + } + + public function getQueryKeys($data) + { + return [self::KEY]; + } + + public function getTitle() + { + return 'export.aggregator.step_history.by_date.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $p = self::KEY; + + $qb->addSelect(sprintf("TO_CHAR(acpstephistory.startDate, '%s') AS {$p}", $data['frequency'])); + $qb->addGroupBy($p); + $qb->addOrderBy($p, 'DESC'); + } + + public function applyOn() + { + return Declarations::ACP_STEP_HISTORY; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php new file mode 100644 index 000000000..005d012f9 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php @@ -0,0 +1,86 @@ +translator->trans('accompanying_period.'.$step); + }; + } + + public function getQueryKeys($data) + { + return [ + self::KEY, + ]; + } + + public function getTitle() + { + return 'export.aggregator.step_history.by_step.title'; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->addSelect('acpstephistory.step AS '.self::KEY) + ->addGroupBy(self::KEY); + } + + public function applyOn() + { + return Declarations::ACP_STEP_HISTORY; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Declarations.php b/src/Bundle/ChillPersonBundle/Export/Declarations.php index 4186861cb..83673234c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Declarations.php +++ b/src/Bundle/ChillPersonBundle/Export/Declarations.php @@ -18,6 +18,8 @@ abstract class Declarations { final public const ACP_TYPE = 'accompanying_period'; + final public const ACP_STEP_HISTORY = 'accompanying_period_step_history'; + final public const EVAL_TYPE = 'evaluation'; final public const HOUSEHOLD_TYPE = 'household'; diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php new file mode 100644 index 000000000..4f620fa34 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php @@ -0,0 +1,139 @@ +repository = $em->getRepository(AccompanyingPeriod::class); + $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } + + public function buildForm(FormBuilderInterface $builder): void + { + // Nothing to add here + } + + public function getFormDefaultData(): array + { + return []; + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.export.acp_closing.description'; + } + + public function getGroup(): string + { + return 'Exports of accompanying courses'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_result' !== $key) { + throw new \LogicException("the key {$key} is not used by this export"); + } + + return fn ($value) => '_header' === $value ? $this->getTitle() : $value; + } + + public function getQueryKeys($data): array + { + return ['export_result']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.export.acp_closing.title'; + } + + public function getType(): string + { + return Declarations::ACP_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->em->createQueryBuilder() + ->select('COUNT(DISTINCT acpstephistory.id) As export_result') + ->from(AccompanyingPeriod\AccompanyingPeriodStepHistory::class, 'acpstephistory') + ->join('acpstephistory.period', 'acp'); + + $qb + ->leftJoin('acp.participations', 'acppart') + ->leftJoin('acppart.person', 'person'); + + if ($this->filterStatsByCenters) { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.PersonCenterHistory::class.' acl_count_person_history WHERE acl_count_person_history.person = person + AND acl_count_person_history.center IN (:authorized_centers) + ' + ) + ) + ->setParameter('authorized_centers', $centers); + } + + AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); + + return $qb; + } + + public function requiredRole(): string + { + return AccompanyingPeriodVoter::STATS; + } + + public function supportsModifiers(): array + { + return [ + Declarations::ACP_TYPE, + Declarations::PERSON_TYPE, + Declarations::ACP_STEP_HISTORY, + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php index ca5ccaf27..741a19961 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php @@ -71,6 +71,7 @@ class HasTemporaryLocationFilter implements FilterInterface ]) ->add('calc_date', PickRollingDateType::class, [ 'label' => 'export.filter.course.having_temporarily.Calculation date', + 'required' => true, ]); } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php new file mode 100644 index 000000000..72197234c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php @@ -0,0 +1,99 @@ +add('date_calc', PickRollingDateType::class, [ + 'label' => 'export.filter.course.not_having_address_reference.adress_at', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'date_calc' => new RollingDate(RollingDate::T_TODAY), + ]; + } + + public function describeAction($data, $format = 'string') + { + return [ + 'exports.filter.course.not_having_address_reference.describe', + [ + 'date_calc' => $this->rollingDateConverter->convert($data['date_calc']), + ], + ]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $k = 'acp_not_associated_ref_filter'; + + $qb + ->leftJoin( + 'acp.locationHistories', + $k, + Join::WITH, + "{$k}.period = acp AND {$k}.startDate <= :{$k}_date_calc AND ({$k}.endDate IS NULL OR {$k}.endDate > :{$k}_date_calc)" + ) + ->leftJoin( + PersonHouseholdAddress::class, + "{$k}_p_address", + Join::WITH, + "{$k}.personLocation = {$k}_p_address.person AND {$k}_p_address.validFrom <= :{$k}_date_calc AND ({$k}_p_address.validTo IS NULL OR {$k}_p_address.validTo > :{$k}_date_calc)" + ) + ->join( + Address::class, + "{$k}_address", + Join::WITH, + "{$k}_address.id = COALESCE(IDENTITY({$k}_p_address.address), IDENTITY({$k}.addressLocation))" + ) + ; + + $qb->andWhere("{$k}_address.addressReference IS NULL"); + $qb->setParameter("{$k}_date_calc", $this->rollingDateConverter->convert($data['date_calc'])); + } + + public function applyOn() + { + return Declarations::ACP_TYPE; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php new file mode 100644 index 000000000..c58aad3bb --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php @@ -0,0 +1,123 @@ +join('acp.userHistories', self::A) + ->andWhere( + "OVERLAPSI({$history}.startDate, {$history}.endDate),(:{$start}, :{$end}) = TRUE" + ) + ->andWhere( + "{$history}.user IN (:{$users})", + ) + ->setParameter($users, $data['accepted_referrers']) + ->setParameter($start, $this->rollingDateConverter->convert($data['start_date'])) + ->setParameter($end, $this->rollingDateConverter->convert($data['end_date'])); + } + + public function applyOn(): string + { + return Declarations::ACP_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder + ->add('accepted_referrers', PickUserDynamicType::class, [ + 'multiple' => true, + ]) + ->add('start_date', PickRollingDateType::class, [ + 'label' => 'export.filter.course.by_referrer_between_dates.start date', + 'required' => true, + ]) + ->add('end_date', PickRollingDateType::class, [ + 'label' => 'export.filter.course.by_referrer_between_dates.end date', + 'required' => true, + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'end_date' => new RollingDate(RollingDate::T_TODAY), + 'accepted_referrers' => [], + ]; + } + + public function describeAction($data, $format = 'string'): array + { + $users = []; + + foreach ($data['accepted_referrers'] as $r) { + $users[] = $this->userRender->renderString($r, []); + } + + return [ + 'exports.filter.course.by_referrer_between_dates.description', [ + 'agents' => implode(', ', $users), + 'start_date' => $this->rollingDateConverter->convert($data['start_date']), + 'end_date' => $this->rollingDateConverter->convert($data['end_date']), + ], ]; + } + + public function getTitle(): string + { + return 'export.filter.course.by_referrer_between_dates.title'; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php new file mode 100644 index 000000000..8026f4573 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php @@ -0,0 +1,96 @@ +add('start_date', PickRollingDateType::class, [ + 'label' => 'export.filter.step_history.by_date.start_date_label', + ]) + ->add('end_date', PickRollingDateType::class, [ + 'label' => 'export.filter.step_history.by_date.end_date_label', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'end_date' => new RollingDate(RollingDate::T_TODAY), + ]; + } + + public function describeAction($data, $format = 'string') + { + return [ + 'exports.filter.step_history.by_date.description', + [ + 'start_date' => $this->rollingDateConverter->convert($data['start_date']), + 'end_date' => $this->rollingDateConverter->convert($data['end_date']), + ], + ]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $startDate = 'acp_step_history_by_date_start_filter'; + $endDate = 'acp_step_history_by_date_end_filter'; + + $qb + ->andWhere( + "acpstephistory.startDate >= :{$startDate} AND (acpstephistory.endDate < :{$endDate} OR acpstephistory.endDate IS NULL)" + ) + ->setParameter($startDate, $this->rollingDateConverter->convert($data['start_date'])) + ->setParameter($endDate, $this->rollingDateConverter->convert($data['end_date'])); + } + + public function applyOn() + { + return Declarations::ACP_STEP_HISTORY; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php new file mode 100644 index 000000000..337b27e8a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php @@ -0,0 +1,87 @@ +add('steps', ChoiceType::class, [ + 'choices' => array_combine( + array_map(static fn (string $step): string => 'accompanying_period.'.$step, $steps), + $steps + ), + 'label' => 'export.filter.step_history.by_step.pick_steps', + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'steps' => [], + ]; + } + + public function describeAction($data, $format = 'string') + { + return [ + 'export.filter.step_history.by_step.description', + [ + '%steps%' => implode(', ', array_map(fn (string $step) => $this->translator->trans('accompanying_period.'.$step), $data['steps'])), + ], + ]; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->andWhere('acpstephistory.step IN (:acpstephistory_by_step_filter_steps)') + ->setParameter('acpstephistory_by_step_filter_steps', $data['steps']); + } + + public function applyOn() + { + return Declarations::ACP_STEP_HISTORY; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php new file mode 100644 index 000000000..1df656a00 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php @@ -0,0 +1,89 @@ +andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class." {$p}_acp JOIN {$p}_acp.accompanyingPeriod {$p}_acpp ". + "WHERE {$p}_acp.person = person ". + "AND OVERLAPSI({$p}_acp.startDate, {$p}_acp.endDate), (:{$p}_date_after, :{$p}_date_before) = TRUE ". + "AND OVERLAPSI({$p}_acpp.openingDate, {$p}_acpp.closingDate), (:{$p}_date_after, :{$p}_date_before) = TRUE" + ) + ) + ->setParameter("{$p}_date_after", $this->rollingDateConverter->convert($data['date_after']), Types::DATE_IMMUTABLE) + ->setParameter("{$p}_date_before", $this->rollingDateConverter->convert($data['date_before']), Types::DATE_IMMUTABLE); + } + + public function applyOn(): string + { + return Declarations::PERSON_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('date_after', PickRollingDateType::class, [ + 'label' => 'export.filter.person.with_participation_between_dates.date_after', + ]); + + $builder->add('date_before', PickRollingDateType::class, [ + 'label' => 'export.filter.person.with_participation_between_dates.date_before', + ]); + } + + public function getFormDefaultData(): array + { + return [ + 'date_after' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'date_before' => new RollingDate(RollingDate::T_TODAY), + ]; + } + + public function describeAction($data, $format = 'string') + { + return ['export.filter.person.with_participation_between_dates.Filtered by participations during period: between %dateafter% and %datebefore%', [ + '%dateafter%' => $this->rollingDateConverter->convert($data['date_after'])->format('d-m-Y'), + '%datebefore%' => $this->rollingDateConverter->convert($data['date_before'])->format('d-m-Y'), + ]]; + } + + public function getTitle() + { + return 'export.filter.person.with_participation_between_dates.title'; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/ListAccompanyingPeriodHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/ListAccompanyingPeriodHelper.php index 5865bb4da..2c2bc1d02 100644 --- a/src/Bundle/ChillPersonBundle/Export/Helper/ListAccompanyingPeriodHelper.php +++ b/src/Bundle/ChillPersonBundle/Export/Helper/ListAccompanyingPeriodHelper.php @@ -18,6 +18,7 @@ use Chill\MainBundle\Export\Helper\ExportAddressHelper; use Chill\MainBundle\Export\Helper\UserHelper; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Repository\PersonRepository; @@ -40,6 +41,8 @@ final readonly class ListAccompanyingPeriodHelper 'closingDate', 'referrer', 'referrerSince', + 'acpParticipantPersons', + 'acpParticipantPersonsIds', 'administrativeLocation', 'locationIsPerson', 'locationIsTemp', @@ -79,6 +82,7 @@ final readonly class ListAccompanyingPeriodHelper private TranslatableStringHelperInterface $translatableStringHelper, private TranslatorInterface $translator, private UserHelper $userHelper, + private LabelPersonHelper $labelPersonHelper, ) { } @@ -110,6 +114,21 @@ final readonly class ListAccompanyingPeriodHelper return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)); }, 'acpCreatedBy', 'acpUpdatedBy', 'referrer' => $this->userHelper->getLabel($key, $values, 'export.list.acp.'.$key), + + 'acpParticipantPersons' => $this->labelPersonHelper->getLabelMulti($key, $values, 'export.list.acp.'.$key), + 'acpParticipantPersonsIds' => function ($value) use ($key): string { + if ('_header' === $value) { + return 'export.list.acp.'.$key; + } + + if (null === $value || '' === $value) { + return ''; + } + + $keys = json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR); + + return implode('|', $keys); + }, 'locationPersonName', 'requestorPerson' => function ($value) use ($key) { if ('_header' === $value) { return 'export.list.acp.'.$key; @@ -222,6 +241,11 @@ final readonly class ListAccompanyingPeriodHelper ->addSelect(sprintf('%s_t.%s AS %s', $entity, $field, $entity)); } + // persons + $qb + ->addSelect(sprintf('(SELECT AGGREGATE(IDENTITY(participant_persons_n.person)) FROM %s participant_persons_n WHERE participant_persons_n.accompanyingPeriod = acp) AS acpParticipantPersons', AccompanyingPeriodParticipation::class)) + ->addSelect(sprintf('(SELECT AGGREGATE(IDENTITY(participant_persons_ids.person)) FROM %s participant_persons_ids WHERE participant_persons_ids.accompanyingPeriod = acp) AS acpParticipantPersonsIds', AccompanyingPeriodParticipation::class)); + // step at date $qb ->addSelect('stepHistory.step AS step') diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php index 5fdb51c15..9e383b789 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php @@ -312,4 +312,34 @@ final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase $this->assertNull($period->getRequestorPerson()); $this->assertNull($period->getRequestor()); } + + public function testSetStep(): void + { + $period = new AccompanyingPeriod(); + + $period->setStep(AccompanyingPeriod::STEP_CONFIRMED); + + self::assertEquals(AccompanyingPeriod::STEP_CONFIRMED, $period->getStep()); + self::assertCount(1, $period->getStepHistories()); + + $period->setStep(AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT); + + self::assertEquals(AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, $period->getStep()); + self::assertCount(2, $period->getStepHistories()); + + $periodInactiveSteps = $period->getStepHistories()->filter(fn (AccompanyingPeriod\AccompanyingPeriodStepHistory $h) => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT === $h->getStep()); + self::assertCount(1, $periodInactiveSteps); + + $period->setStep(AccompanyingPeriod::STEP_CLOSED, ['closing_motive' => $closingMotive = new AccompanyingPeriod\ClosingMotive()]); + + self::assertEquals(AccompanyingPeriod::STEP_CLOSED, $period->getStep()); + self::assertCount(3, $period->getStepHistories()); + + $periodClosedSteps = $period->getStepHistories()->filter(fn (AccompanyingPeriod\AccompanyingPeriodStepHistory $h) => AccompanyingPeriod::STEP_CLOSED === $h->getStep()); + self::assertCount(1, $periodClosedSteps); + + $periodClosedStep = $periodClosedSteps->first(); + + self::assertSame($closingMotive, $periodClosedStep->getClosingMotive()); + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregatorTest.php new file mode 100644 index 000000000..8e9cdaf6d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregatorTest.php @@ -0,0 +1,61 @@ +labelPersonHelper = self::$container->get(LabelPersonHelper::class); + } + + public function getAggregator() + { + return new PersonParticipatingAggregator($this->labelPersonHelper); + } + + public function getFormData() + { + return [[]]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('count(acp.id)') + ->from(AccompanyingPeriod::class, 'acp'), + $em->createQueryBuilder() + ->select('count(acp.id)') + ->from(AccompanyingPeriod::class, 'acp') + ->join('acp.participations', 'acppart'), + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregatorTest.php new file mode 100644 index 000000000..2ce63af34 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregatorTest.php @@ -0,0 +1,68 @@ +closingMotiveRender = self::$container->get(ClosingMotiveRender::class); + $this->closingMotiveRepository = self::$container->get(ClosingMotiveRepositoryInterface::class); + } + + public function getAggregator() + { + return new ByClosingMotiveAggregator( + $this->closingMotiveRepository, + $this->closingMotiveRender + ); + } + + public function getFormData() + { + return [ + [], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $qb = $em->createQueryBuilder() + ->select('COUNT(DISTINCT acpstephistory.id) As export_result') + ->from(AccompanyingPeriodStepHistory::class, 'acpstephistory') + ->join('acpstephistory.period', 'acp'); + + return [ + $qb, + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregatorTest.php new file mode 100644 index 000000000..74a30d1a6 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregatorTest.php @@ -0,0 +1,61 @@ + DateGroupingChoiceEnum::YEAR->value, + ], + [ + 'frequency' => DateGroupingChoiceEnum::WEEK->value, + ], + [ + 'frequency' => DateGroupingChoiceEnum::MONTH->value, + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $qb = $em->createQueryBuilder() + ->select('COUNT(DISTINCT acpstephistory.id) As export_result') + ->from(AccompanyingPeriodStepHistory::class, 'acpstephistory') + ->join('acpstephistory.period', 'acp'); + + return [ + $qb, + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregatorTest.php new file mode 100644 index 000000000..65752fea2 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregatorTest.php @@ -0,0 +1,58 @@ +get(EntityManagerInterface::class); + + $qb = $em->createQueryBuilder() + ->select('COUNT(DISTINCT acpstephistory.id) As export_result') + ->from(AccompanyingPeriodStepHistory::class, 'acpstephistory') + ->join('acpstephistory.period', 'acp'); + + return [ + $qb, + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountAccompanyingCourseStepHistoryTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountAccompanyingCourseStepHistoryTest.php new file mode 100644 index 000000000..19adee99f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Export/CountAccompanyingCourseStepHistoryTest.php @@ -0,0 +1,48 @@ +get(EntityManagerInterface::class); + + yield new CountAccompanyingCourseStepHistory($em, $this->getParameters(true)); + yield new CountAccompanyingCourseStepHistory($em, $this->getParameters(false)); + } + + public function getFormData(): array + { + return [[]]; + } + + public function getModifiersCombination() + { + return [[Declarations::ACP_TYPE], [Declarations::ACP_TYPE, Declarations::ACP_STEP_HISTORY]]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilterTest.php new file mode 100644 index 000000000..5c1b8b9c7 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilterTest.php @@ -0,0 +1,67 @@ +rollingDateConverter = self::$container->get(RollingDateConverterInterface::class); + } + + public function getFilter() + { + return new HasTemporaryLocationFilter($this->rollingDateConverter); + } + + public function getFormData() + { + return [ + [ + 'having_temporarily' => true, + 'calc_date' => new RollingDate(RollingDate::T_TODAY), + ], + [ + 'having_temporarily' => false, + 'calc_date' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->from('ChillPersonBundle:AccompanyingPeriod', 'acp') + ->select('acp.id'), + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilterTest.php new file mode 100644 index 000000000..c71088159 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilterTest.php @@ -0,0 +1,66 @@ + new RollingDate(RollingDate::T_TODAY)], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('acp.id') + ->from(AccompanyingPeriod::class, 'acp'), + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php new file mode 100644 index 000000000..9b0eb4d72 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php @@ -0,0 +1,89 @@ +rollingDateConverter = self::$container->get(RollingDateConverterInterface::class); + $this->userRender = self::$container->get(UserRender::class); + } + + public function getFilter() + { + return new ReferrerFilterBetweenDates($this->rollingDateConverter, $this->userRender); + } + + public function getFormData() + { + self:self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $users = $em->createQueryBuilder() + ->from(User::class, 'u') + ->select('u') + ->getQuery() + ->setMaxResults(1) + ->getResult(); + + return [ + [ + 'accepted_referrers' => $users[0], + 'start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'end_date' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + yield $em->createQueryBuilder() + ->from(AccompanyingPeriod::class, 'acp') + ->select('acp.id'); + + $qb = $em->createQueryBuilder(); + $qb + ->from(AccompanyingPeriod\AccompanyingPeriodWork::class, 'acpw') + ->join('acpw.accompanyingPeriod', 'acp') + ->join('acp.participations', 'acppart') + ->join('acppart.person', 'person') + ; + + $qb->select('COUNT(DISTINCT acpw.id) as export_result'); + + yield $qb; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php index a56741bd3..52173fe8c 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php @@ -31,7 +31,7 @@ final class ReferrerFilterTest extends AbstractFilterTest { self::bootKernel(); - $this->filter = self::$container->get('chill.person.export.filter_referrer'); + $this->filter = self::$container->get(ReferrerFilter::class); } public function getFilter() diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilterTest.php new file mode 100644 index 000000000..978bf9488 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilterTest.php @@ -0,0 +1,67 @@ +rollingDateConverter = self::$container->get(RollingDateConverterInterface::class); + } + + public function getFilter() + { + return new ByDateFilter($this->rollingDateConverter); + } + + public function getFormData() + { + return [ + [ + 'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'end_date' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $qb = $em->createQueryBuilder() + ->select('COUNT(DISTINCT acpstephistory.id) As export_result') + ->from(AccompanyingPeriodStepHistory::class, 'acpstephistory') + ->join('acpstephistory.period', 'acp'); + + return [ + $qb, + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilterTest.php new file mode 100644 index 000000000..2924be7e7 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilterTest.php @@ -0,0 +1,66 @@ + [AccompanyingPeriod::STEP_CONFIRMED, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG], + ], + [ + 'steps' => [AccompanyingPeriod::STEP_CLOSED], + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + $em = self::$container->get(EntityManagerInterface::class); + + $qb = $em->createQueryBuilder() + ->select('COUNT(DISTINCT acpstephistory.id) As export_result') + ->from(AccompanyingPeriodStepHistory::class, 'acpstephistory') + ->join('acpstephistory.period', 'acp'); + + return [ + $qb, + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilterTest.php new file mode 100644 index 000000000..d9c58e743 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilterTest.php @@ -0,0 +1,63 @@ +filter = self::$container->get(WithParticipationBetweenDatesFilter::class); + } + + public function getFilter() + { + return $this->filter; + } + + public function getFormData() + { + return [ + [ + 'date_after' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), + 'date_before' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + + public function getQueryBuilders() + { + self::bootKernel(); + + $em = self::$container->get(EntityManagerInterface::class); + + return [ + $em->createQueryBuilder() + ->select('person.id') + ->from(Person::class, 'person'), + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml index 4eaaf67b0..d488df8d6 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml @@ -96,11 +96,14 @@ services: tags: - { name: chill.export_filter, alias: accompanyingcourse_activeonedaybetweendates_filter } - chill.person.export.filter_referrer: - class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\ReferrerFilter + Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\ReferrerFilter: tags: - { name: chill.export_filter, alias: accompanyingcourse_referrer_filter } + Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\ReferrerFilterBetweenDates: + tags: + - { name: chill.export_filter, alias: accompanyingcourse_referrer_filter_between_dates } + chill.person.export.filter_openbetweendates: class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\OpenBetweenDatesFilter tags: @@ -147,6 +150,10 @@ services: tags: - { name: chill.export_filter, alias: accompanyingcourse_info_within_filter } + Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\NotAssociatedWithAReferenceAddressFilter: + tags: + - { name: chill.export_filter, alias: accompanyingcourse_not_having_addr_reference_filter } + ## Aggregators chill.person.export.aggregator_referrer_scope: class: Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ScopeAggregator @@ -259,3 +266,7 @@ services: Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ClosingDateAggregator: tags: - { name: chill.export_aggregator, alias: accompanyingcourse_closing_date_aggregator } + + Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\PersonParticipatingAggregator: + tags: + - { name: chill.export_aggregator, alias: accompanyingcourse_person_part_aggregator } diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_period_step_history.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_period_step_history.yaml new file mode 100644 index 000000000..09da707c9 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_period_step_history.yaml @@ -0,0 +1,31 @@ +services: + _defaults: + autowire: true + autoconfigure: true + + # exports + Chill\PersonBundle\Export\Export\CountAccompanyingCourseStepHistory: + tags: + - { name: chill.export, alias: count_acpstephistory } + + # filters + Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters\ByDateFilter: + tags: + - { name: chill.export_filter, alias: acpstephistory_filter_by_date } + + Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters\ByStepFilter: + tags: + - { name: chill.export_filter, alias: acpstephistory_filter_by_step } + + # aggregators + Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByClosingMotiveAggregator: + tags: + - { name: chill.export_aggregator, alias: acpstephistory_agg_by_closing_motive } + + Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByDateAggregator: + tags: + - { name: chill.export_aggregator, alias: acpstephistory_agg_by_date } + + Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByStepAggregator: + tags: + - { name: chill.export_aggregator, alias: acpstephistory_agg_by_step } diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml index d25a3b9e6..c0ed03115 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml @@ -116,6 +116,10 @@ services: tags: - { name: chill.export_filter, alias: person_without_household_composition_filter } + Chill\PersonBundle\Export\Filter\PersonFilters\WithParticipationBetweenDatesFilter: + tags: + - { name: chill.export_filter, alias: person_with_participation_between_dates_filter } + ## Aggregators chill.person.export.aggregator_nationality: class: Chill\PersonBundle\Export\Aggregator\PersonAggregators\NationalityAggregator diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20240123161457.php b/src/Bundle/ChillPersonBundle/migrations/Version20240123161457.php new file mode 100644 index 000000000..4c19e6c8f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20240123161457.php @@ -0,0 +1,52 @@ +addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD closingMotive_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CONSTRAINT FK_84D514AC504CB38D FOREIGN KEY (closingMotive_id) REFERENCES chill_person_accompanying_period_closingmotive (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CONSTRAINT FK_84D514AC65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql(<<<'EOF' + WITH last_step AS ( + SELECT * FROM ( + SELECT *, rank() OVER (partition by period_id ORDER BY startdate DESC, id DESC) AS r FROM chill_person_accompanying_period_step_history cpapsh + ) as sq + WHERE r = 1 + ) + UPDATE chill_person_accompanying_period_step_history + SET closingMotive_id = chill_person_accompanying_period.closingmotive_id + FROM last_step, chill_person_accompanying_period + WHERE last_step.period_id = chill_person_accompanying_period_step_history.period_id AND chill_person_accompanying_period.id = chill_person_accompanying_period_step_history.period_id + AND last_step.step = 'CLOSED'; + EOF); + $this->addSql('CREATE INDEX IDX_84D514AC504CB38D ON chill_person_accompanying_period_step_history (closingMotive_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period_step_history DROP CONSTRAINT FK_84D514AC504CB38D'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_step_history DROP CONSTRAINT FK_84D514AC65FF1AEC'); + $this->addSql('DROP INDEX IDX_84D514AC504CB38D'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_step_history DROP closingMotive_id'); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml index a8d2080fb..a268ac6c4 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml @@ -136,10 +136,22 @@ exports: Filtered by person\'s geographical unit (based on address) computed at date, only units: "Filtré par zone géographique sur base de l'adresse, calculé à {datecalc, date, short}, seulement les zones suivantes: {units}" filter: + course: + not_having_address_reference: + describe: >- + Uniquement les parcours qui ne sont pas localisés à une adresse de référence, à la date du {date_calc, date, medium} + by_referrer_between_dates: + description: >- + Filtré par référent du parcours, entre deux dates: depuis le {start_date, date, medium}, jusqu'au {end_date, date, medium}, seulement {agents} work: by_treating_agent: Filtered by treating agent at date: >- Les agents traitant au { agent_at, date, medium }, seulement {agents} + step_history: + by_date: + description: >- + Changements de statuts filtrés par date: après le { start_date, date, medium } (inclus), avant le { end_date, date, medium } + 'total persons matching the search pattern': >- { total, plural, =0 {Aucun usager ne correspond aux termes de recherche} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 82c538c69..50c6a87d5 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -785,7 +785,9 @@ accompanying_period: dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date% DRAFT: Brouillon CONFIRMED: Confirmé - CLOSED: Clotûré + CLOSED: Clôturé + CONFIRMED_INACTIVE_SHORT: Hors file active + CONFIRMED_INACTIVE_LONG: Pré-archivé emergency: Urgent occasional: ponctuel regular: régulier @@ -985,6 +987,9 @@ export: YYYY-MM: par mois YYYY: par année export: + acp_closing: + title: Nombre de changements de statuts de parcours + description: Compte le nombre de changements de statuts de parcours. Cet export est indiqué pour obtenir le nombre de parcours ouverts ou fermés pendant une période de temps (un parcours pouvant être clôturé, puis ré-ouvert pendant la période de temps indiquée) acp_stats: avg_duration: Moyenne de la durée de participation de chaque usager concerné count_participations: Nombre de participations distinctes @@ -1037,7 +1042,23 @@ export: at_date: Date de calcul de l'adresse header: Code postal + step_history: + by_step: + title: Grouper les changements de statut du parcours par étape + header: Nouveau statut du parcours + by_date: + title: Grouper les changements de statut du parcours par date + header: Date du changement de statut du parcours + date_grouping_label: Grouper par + by_closing_motive: + title: Grouper les changements de statut du parcours par motif de clôture + header: Motif de clôture + course: + by-user: + title: Grouper les parcours par usager participant + header: Usager participant + by_referrer: Computation date for referrer: Date à laquelle le référent était actif by_user_scope: @@ -1124,6 +1145,16 @@ export: by_geog_unit: Filtered by person's geographical unit (based on address) computed at %datecalc%, only %units%: Filtré par unité géographique (sur base de l'adresse), calculé le %datecalc%, seulement %units% + step_history: + by_step: + title: Filtrer les changements de statut du parcours par étape + pick_steps: Nouvelles étapes + description: "Filtré par étape: seulement %steps%" + by_date: + title: Filtrer les changements de statut du parcours par date + start_date_label: Changements après le + end_date_label: Changements avant le + person: by_composition: Filter by household composition: Filtrer les usagers par composition du ménage @@ -1142,8 +1173,16 @@ export: Filtered by person\'s address status computed at %datecalc%, only %statuses%: Filtré par comparaison à l'adresse de référence, calculé à %datecalc%, seulement %statuses% Status: Statut Address at date: Adresse à la date + with_participation_between_dates: + date_after: Concerné par un parcours après le + date_before: Concerné par un parcours avant le + title: Filtrer les usagers ayant été associés à un parcours ouverts un jour dans la période de temps indiquée + 'Filtered by participations during period: between %dateafter% and %datebefore%': 'Filtré par personne concerné par un parcours dans la periode entre: %dateafter% et %datebefore%' course: + not_having_address_reference: + title: Filtrer les parcours non localisés à une adresse de réference + adress_at: Adresse à la date du having_info_within_interval: title: Filtrer les parcours ayant reçu une intervention entre deux dates start_date: Début de la période @@ -1179,6 +1218,10 @@ export: "Filtered by user main scope: only %scope%": "Filtré par service du référent: uniquement %scope%" by_referrer: Computation date for referrer: Date à laquelle le référent était actif + by_referrer_between_dates: + title: Filtrer les parcours par référent (entre deux dates) + start date: Le référent était actif après le + end date: Le référent était actif avant le having_temporarily: label: Qualité de la localisation Having a temporarily location: Ayant une localisation temporaire @@ -1296,6 +1339,8 @@ export: socialIssues: Problématiques sociales requestorPerson: Demandeur (personne) requestorThirdParty: Demandeur (tiers) + acpParticipantPersons: Usagers concernés + acpParticipantPersonsIds: Usagers concernés (identifiants) eval: List of evaluations: Liste des évaluations