Merge branch '147-history-on-acp-work' into '147-history-user-scope-job'

Finalisation de l'historique des métiers et services des utilisateurs: adaptation pour les actions d'accompagnement

See merge request Chill-Projet/chill-bundles!597
This commit is contained in:
Julien Fastré 2023-10-16 15:39:25 +00:00
commit 05865521b5
106 changed files with 1241 additions and 496 deletions

View File

@ -0,0 +1,5 @@
kind: Feature
body: '[export] add an aggregator for activities: group by job scope aggregator'
time: 2023-10-11T15:51:15.022779832+02:00
custom:
Issue: ""

View File

@ -1,63 +0,0 @@
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
AccompanyingPeriodWork::class,,,acpw
,AccompanyingPeriodWorkEvaluation::class,acpw.accompanyingPeriodWorkEvaluations,workeval
,User::class,acpw.referrers,acpwuser
,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
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
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
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
1 Entity Join Attribute Alias
2 AccompanyingPeriod::class acp
3 AccompanyingPeriodWork::class acp.works acpw
4 AccompanyingPeriodParticipation::class acp.participations acppart
5 Location::class acp.administrativeLocation acploc
6 ClosingMotive::class acp.closingMotive acpmotive
7 UserJob::class acp.job acpjob
8 Origin::class acp.origin acporigin
9 Scope::class acp.scopes acpscope
10 SocialIssue::class acp.socialIssues acpsocialissue
11 User::class acp.user acpuser
12 AccompanyingPeriodWork::class acpw
13 AccompanyingPeriodWorkEvaluation::class acpw.accompanyingPeriodWorkEvaluations workeval
14 User::class acpw.referrers acpwuser
15 SocialAction::class acpw.socialAction acpwsocialaction
16 Goal::class acpw.goals goal
17 Result::class acpw.results result
18 AccompanyingPeriodParticipation::class acppart
19 Person::class acppart.person partperson
20 AccompanyingPeriodWorkEvaluation::class workeval
21 Evaluation::class workeval.evaluation eval
22 Goal::class goal
23 Result::class goal.results goalresult
24 Person::class person
25 Center::class person.center center
26 HouseholdMember::class partperson.householdParticipations householdmember
27 MaritalStatus::class person.maritalStatus personmarital
28 VendeePerson::class vp
29 VendeePersonMineur::class vpm
30 ResidentialAddress::class resaddr
31 ThirdParty::class resaddr.hostThirdParty tparty
32 ThirdParty::class tparty
33 ThirdPartyCategory::class tparty.categories tpartycat
34 HouseholdMember::class householdmember
35 Household::class householdmember.household household
36 Person::class householdmember.person memberperson
37 memberperson.center membercenter
38 Household::class household
39 HouseholdComposition::class household.compositions composition
40 Activity::class activity
41 Person::class activity.person actperson
42 AccompanyingPeriod::class activity.accompanyingPeriod acp
43 Person::class activity_person_having_activity.person person_person_having_activity
44 ActivityReason::class activity_person_having_activity.reasons reasons_person_having_activity
45 ActivityType::class activity.activityType acttype
46 Location::class activity.location actloc
47 SocialAction::class activity.socialActions actsocialaction
48 SocialIssue::class activity.socialIssues actsocialssue
49 ThirdParty::class activity.thirdParties acttparty
50 User::class activity.user actuser
51 User::class activity.users actusers
52 ActivityReason::class activity.reasons actreasons
53 Center::class actperson.center actcenter
54 ActivityReason::class actreasons
55 ActivityReasonCategory::class actreason.category actreasoncat
56 Calendar::class cal
57 CancelReason::class cal.cancelReason calcancel
58 Location::class cal.location calloc
59 User::class cal.user caluser
60 VendeePerson::class vp
61 SituationProfessionelle::class vp.situationProfessionelle vpprof
62 StatutLogement::class vp.statutLogement vplog
63 TempsDeTravail::class vp.tempsDeTravail vptt

View File

@ -21,7 +21,6 @@ These are alias conventions :
| | AccompanyingPeriodInfo::class | not existing (using custom WITH clause) | acpinfo | | | AccompanyingPeriodInfo::class | not existing (using custom WITH clause) | acpinfo |
| AccompanyingPeriodWork::class | | | acpw | | AccompanyingPeriodWork::class | | | acpw |
| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | | | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval |
| | User::class | acpw.referrers | acpwuser |
| | SocialAction::class | acpw.socialAction | acpwsocialaction | | | SocialAction::class | acpw.socialAction | acpwsocialaction |
| | Goal::class | acpw.goals | goal | | | Goal::class | acpw.goals | goal |
| | Result::class | acpw.results | result | | | Result::class | acpw.results | result |

View File

@ -93,7 +93,7 @@ class CreatorScopeAggregator implements AggregatorInterface
public function getQueryKeys($data): array public function getQueryKeys($data): array
{ {
return ['_select']; return [self::PREFIX . '_select'];
} }
public function getTitle(): string public function getTitle(): string

View File

@ -0,0 +1,104 @@
<?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\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class JobScopeAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_agg_creator_job';
public function __construct(
private readonly ScopeRepository $scopeRepository,
private readonly TranslatableStringHelper $translatableStringHelper
) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin("activity.createdBy", "{$p}_user")
->leftJoin(
UserJobHistory::class,
"{$p}_history",
Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// job_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", "activity.date"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", "activity.date")
)
)
)
->addSelect("IDENTITY({$p}_history.job) AS {$p}_select")
->addGroupBy("{$p}_select");
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Scope';
}
if (null === $value || '' === $value) {
return '';
}
$s = $this->scopeRepository->find($value);
return $this->translatableStringHelper->localize(
$s->getName()
);
};
}
public function getQueryKeys($data): array
{
return [self::PREFIX . '_select'];
}
public function getTitle(): string
{
return 'export.aggregator.activity.by_creator_job.Group activity by creator job';
}
}

View File

@ -57,7 +57,7 @@ class ListActivityHelper
->addSelect('AGGREGATE(actPerson.id) AS personsNames') ->addSelect('AGGREGATE(actPerson.id) AS personsNames')
->leftJoin('activity.users', 'users_u') ->leftJoin('activity.users', 'users_u')
->addSelect('AGGREGATE(users_u.id) AS usersIds') ->addSelect('AGGREGATE(users_u.id) AS usersIds')
->addSelect('AGGREGATE(users_u.id) AS usersNames') ->addSelect('AGGREGATE(JSON_BUILD_OBJECT(\'uid\', users_u.id, \'d\', activity.date)) AS usersNames')
->leftJoin('activity.thirdParties', 'thirdparty') ->leftJoin('activity.thirdParties', 'thirdparty')
->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesIds') ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesIds')
->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesNames') ->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesNames')
@ -68,9 +68,9 @@ class ListActivityHelper
->leftJoin('activity.location', 'location') ->leftJoin('activity.location', 'location')
->addSelect('location.name AS locationName') ->addSelect('location.name AS locationName')
->addSelect('activity.sentReceived') ->addSelect('activity.sentReceived')
->addSelect('IDENTITY(activity.createdBy) AS createdBy') ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.createdBy), \'d\', activity.createdAt) AS createdBy')
->addSelect('activity.createdAt') ->addSelect('activity.createdAt')
->addSelect('IDENTITY(activity.updatedBy) AS updatedBy') ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.updatedBy), \'d\', activity.updatedAt) AS updatedBy')
->addSelect('activity.updatedAt') ->addSelect('activity.updatedAt')
->addGroupBy('activity.id') ->addGroupBy('activity.id')
->addGroupBy('location.id'); ->addGroupBy('location.id');

View File

@ -59,9 +59,10 @@ class UserScopeFilter implements FilterInterface
->andWhere( ->andWhere(
$qb->expr()->in("{$p}_history.scope", ":{$p}_scopes") $qb->expr()->in("{$p}_history.scope", ":{$p}_scopes")
) )
->setParameters([ ->setParameter(
"{$p}_scopes" => $data["scopes"], "{$p}_scopes",
]); $data["scopes"],
);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -51,9 +51,10 @@ class UsersJobFilter implements FilterInterface
. "AND {$p}_history.job IN ( :{$p}_jobs )" . "AND {$p}_history.job IN ( :{$p}_jobs )"
) )
) )
->setParameters([ ->setParameter(
"{$p}_jobs" => $data["jobs"], "{$p}_jobs",
]); $data["jobs"]
);
} }
public function applyOn() public function applyOn()

View File

@ -53,9 +53,10 @@ class UsersScopeFilter implements FilterInterface
. "AND {$p}_history.scope IN ( :{$p}_scopes )" . "AND {$p}_history.scope IN ( :{$p}_scopes )"
) )
) )
->setParameters([ ->setParameter(
"{$p}_scopes" => $data["scopes"], "{$p}_scopes",
]); $data["scopes"]
);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -122,9 +122,30 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos
->leftJoin('a.user', 'activity_u') ->leftJoin('a.user', 'activity_u')
->andWhere( ->andWhere(
$qb->expr()->orX( $qb->expr()->orX(
'creator.userJob IN (:jobs)', $qb->expr()->exists(
'activity_u.userJob IN (:jobs)', sprintf(
'EXISTS (SELECT 1 FROM ' . User::class . ' activity_user WHERE activity_user MEMBER OF a.users AND activity_user.userJob IN (:jobs))' "SELECT 1 FROM %s ujh_creator WHERE ujh_creator.user = a.createdBy "
. "AND ujh_creator.job IN (:jobs) AND a.createdAt > ujh_creator.startDate "
. "AND (ujh_creator.endDate IS NULL or ujh_creator.endDate > a.date)",
User\UserJobHistory::class
)
),
$qb->expr()->exists(
sprintf(
"SELECT 1 FROM %s ujh_u WHERE ujh_u.user = a.user "
. "AND ujh_u.job IN (:jobs) AND a.createdAt > ujh_u.startDate "
. "AND (ujh_u.endDate IS NULL or ujh_u.endDate > a.date)",
User\UserJobHistory::class
)
),
$qb->expr()->exists(
sprintf(
"SELECT 1 FROM %s ujh_users WHERE ujh_users.user MEMBER OF a.users "
. "AND ujh_users.job IN (:jobs) AND a.createdAt > ujh_users.startDate "
. "AND (ujh_users.endDate IS NULL or ujh_users.endDate > a.date)",
User\UserJobHistory::class
)
),
) )
) )
->setParameter('jobs', $jobs); ->setParameter('jobs', $jobs);
@ -172,16 +193,17 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();
} }
public function findUserJobByAssociated(Person|AccompanyingPeriod $associated): array public function findUserJobByAssociated(AccompanyingPeriod|Person $associated): array
{ {
$in = $this->em->createQueryBuilder(); $in = $this->em->createQueryBuilder();
$in->select('IDENTITY(u.userJob)') $in->select('IDENTITY(u.job)')
->from(User::class, 'u') ->distinct()
->from(User\UserJobHistory::class, 'u')
->join( ->join(
Activity::class, Activity::class,
'a', 'a',
Join::WITH, Join::WITH,
'a.createdBy = u OR a.user = u OR u MEMBER OF a.users' 'a.createdBy = u.user OR a.user = u.user OR u.user MEMBER OF a.users AND a.date >= u.startDate ANd (u.endDate IS NULL or u.endDate > a.date)'
); );
if ($associated instanceof Person) { if ($associated instanceof Person) {

View File

@ -0,0 +1,60 @@
<?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\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\CreatorScopeAggregator;
use Chill\ActivityBundle\Export\Aggregator\JobScopeAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
* @coversNothing
*/
final class JobScopeAggregatorTest extends AbstractAggregatorTest
{
private JobScopeAggregator $aggregator;
protected function setUp(): void
{
self::bootKernel();
$this->aggregator = self::$container->get(JobScopeAggregator::class);
}
public function getAggregator()
{
return $this->aggregator;
}
public function getFormData(): array
{
return [
[],
];
}
public function getQueryBuilders(): array
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity')
->leftJoin('activity.accompanyingPeriod', 'acp')
->leftJoin('activity.user', 'actuser'),
];
}
}

View File

@ -177,6 +177,10 @@ services:
tags: tags:
- { name: chill.export_aggregator, alias: activity_creator_scope_aggregator } - { name: chill.export_aggregator, alias: activity_creator_scope_aggregator }
Chill\ActivityBundle\Export\Aggregator\JobScopeAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_creator_job_aggregator }
Chill\ActivityBundle\Export\Aggregator\ActivityUsersAggregator: Chill\ActivityBundle\Export\Aggregator\ActivityUsersAggregator:
tags: tags:
- { name: chill.export_aggregator, alias: activity_users_aggregator } - { name: chill.export_aggregator, alias: activity_users_aggregator }

View File

@ -395,6 +395,9 @@ export:
by_creator_scope: by_creator_scope:
Group activity by creator scope: Grouper les échanges par service du créateur de l'échange Group activity by creator scope: Grouper les échanges par service du créateur de l'échange
Calc date: Date de calcul du service du créateur de l'échange Calc date: Date de calcul du service du créateur de l'échange
by_creator_job:
Group activity by creator job: Grouper les échanges par service du créateur de l'échange
Calc date: Date de calcul du service du créateur de l'échange
generic_doc: generic_doc:
filter: filter:

View File

@ -182,7 +182,10 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn
{ {
$qb = $this->em->createQueryBuilder() $qb = $this->em->createQueryBuilder()
->from(AsideActivity::class, 'aside') ->from(AsideActivity::class, 'aside')
->leftJoin('aside.agent', 'agent'); ->leftJoin('aside.agent', 'agent')
->leftJoin('agent.scopeHistories', 'scopeHistories')
->andWhere('scopeHistories.startDate <= aside.date AND (scopeHistories.endDate IS NULL or scopeHistories.endDate > aside.date)')
;
$qb $qb
->addSelect('aside.id AS id') ->addSelect('aside.id AS id')
@ -190,7 +193,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn
->addSelect('aside.updatedAt AS updatedAt') ->addSelect('aside.updatedAt AS updatedAt')
->addSelect('IDENTITY(aside.agent) AS agent_id') ->addSelect('IDENTITY(aside.agent) AS agent_id')
->addSelect('IDENTITY(aside.createdBy) AS creator_id') ->addSelect('IDENTITY(aside.createdBy) AS creator_id')
->addSelect('IDENTITY(agent.mainScope) AS main_scope') /// ->addSelect('IDENTITY(scopeHistories.scope) AS main_scope')
->addSelect('IDENTITY(agent.mainCenter) AS main_center') ->addSelect('IDENTITY(agent.mainCenter) AS main_center')
->addSelect('IDENTITY(aside.type) AS aside_activity_type') ->addSelect('IDENTITY(aside.type) AS aside_activity_type')
->addSelect('aside.date') ->addSelect('aside.date')

View File

@ -51,9 +51,10 @@ class ByUserJobFilter implements FilterInterface
. "AND {$p}_history.job IN ( :{$p}_jobs )" . "AND {$p}_history.job IN ( :{$p}_jobs )"
) )
) )
->setParameters([ ->setParameter(
"{$p}_jobs" => $data["jobs"], "{$p}_jobs",
]); $data["jobs"],
);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -53,9 +53,10 @@ class ByUserScopeFilter implements FilterInterface
. "AND {$p}_history.scope IN ( :{$p}_scopes )" . "AND {$p}_history.scope IN ( :{$p}_scopes )"
) )
) )
->setParameters([ ->setParameter(
"{$p}_scopes" => $data["scopes"], "{$p}_scopes",
]); $data["scopes"],
);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -0,0 +1,43 @@
<?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\Tests\Export\Export;
use Chill\AsideActivityBundle\Export\Export\ListAsideActivity;
use Chill\MainBundle\Test\Export\AbstractExportTest;
use Doctrine\ORM\AbstractQuery;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class ListAsideActivityTest extends KernelTestCase
{
private ListAsideActivity $listAsideActivity;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->listAsideActivity = self::$container->get(ListAsideActivity::class);
}
public function testExecuteQuery(): void
{
$qb = $this->listAsideActivity->initiateQuery([], [], [])
->setMaxResults(1);
$results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
self::assertIsArray($results, "smoke test: test that the result is an array");
}
}

View File

@ -34,7 +34,7 @@ class ChargeRepository extends ServiceEntityRepository
/** /**
* @return Charge[] * @return Charge[]
*/ */
public function findAllByEntity(Person|Household $entity): array public function findAllByEntity(Household|Person $entity): array
{ {
$qb = $this->createQueryBuilder('c'); $qb = $this->createQueryBuilder('c');

View File

@ -35,7 +35,7 @@ class ResourceRepository extends ServiceEntityRepository
/** /**
* @return Resource[] * @return Resource[]
*/ */
public function findAllByEntity(Person|Household $entity): array public function findAllByEntity(Household|Person $entity): array
{ {
$qb = $this->createQueryBuilder('r'); $qb = $this->createQueryBuilder('r');
@ -47,7 +47,7 @@ class ResourceRepository extends ServiceEntityRepository
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();
} }
public function findByEntityAndDate(Person|Household $entity, DateTime $date, $sort = null) public function findByEntityAndDate(Household|Person $entity, DateTime $date, $sort = null)
{ {
$qb = $this->createQueryBuilder('c'); $qb = $this->createQueryBuilder('c');

View File

@ -22,13 +22,12 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class JobFilter implements FilterInterface final readonly class JobFilter implements FilterInterface
{ {
private const PREFIX = 'cal_filter_job'; private const PREFIX = 'cal_filter_job';
public function __construct( public function __construct(
protected TranslatorInterface $translator, private TranslatableStringHelper $translatableStringHelper
private readonly TranslatableStringHelper $translatableStringHelper
) {} ) {}
public function addRole(): ?string public function addRole(): ?string
@ -59,9 +58,10 @@ class JobFilter implements FilterInterface
) )
) )
->andWhere($qb->expr()->in("{$p}_history.job", ":{$p}_job")) ->andWhere($qb->expr()->in("{$p}_history.job", ":{$p}_job"))
->setParameters([ ->setParameter(
"{$p}_job" => $data["job"], "{$p}_job",
]); $data["job"]
);
} }

View File

@ -59,9 +59,10 @@ class ScopeFilter implements FilterInterface
) )
) )
->andWhere($qb->expr()->in("{$p}_history.scope", ":{$p}_scope")) ->andWhere($qb->expr()->in("{$p}_history.scope", ":{$p}_scope"))
->setParameters([ ->setParameter(
"{$p}_scope" => $data["scope"], "{$p}_scope",
]); $data["scope"]
);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -33,7 +33,7 @@ final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface
/** /**
* @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft * @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft
*/ */
public function isUserAbsent(User $user): bool|null public function isUserAbsent(User $user): null|bool
{ {
$id = $this->mapCalendarToUser->getUserId($user); $id = $this->mapCalendarToUser->getUserId($user);

View File

@ -18,5 +18,5 @@ interface MSUserAbsenceReaderInterface
/** /**
* @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft * @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft
*/ */
public function isUserAbsent(User $user): bool|null; public function isUserAbsent(User $user): null|bool;
} }

View File

@ -22,6 +22,7 @@ use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Export\Filter\JobFilter; use Chill\CalendarBundle\Export\Filter\JobFilter;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
/** /**
@ -62,15 +63,11 @@ final class JobFilterTest extends AbstractFilterTest
->setMaxResults(1) ->setMaxResults(1)
->getResult(); ->getResult();
$data = []; return [
[
foreach ($array as $a) { 'job' => new ArrayCollection($array)
$data[] = [ ]
'job' => $a, ];
];
}
return $data;
} }
public function getQueryBuilders(): array public function getQueryBuilders(): array

View File

@ -22,6 +22,7 @@ use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Export\Filter\ScopeFilter; use Chill\CalendarBundle\Export\Filter\ScopeFilter;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
/** /**
@ -62,15 +63,11 @@ final class ScopeFilterTest extends AbstractFilterTest
->setMaxResults(1) ->setMaxResults(1)
->getResult(); ->getResult();
$data = []; return [
[
foreach ($array as $a) { 'scope' => new ArrayCollection($array)
$data[] = [ ]
'scope' => $a, ];
];
}
return $data;
} }
public function getQueryBuilders(): array public function getQueryBuilders(): array

View File

@ -366,7 +366,7 @@ class CustomFieldChoice extends AbstractCustomField
* If the value had an 'allow_other' = true option, the returned value * If the value had an 'allow_other' = true option, the returned value
* **is not** the content of the _other field, but the `_other` string. * **is not** the content of the _other field, but the `_other` string.
*/ */
private function guessValue(array|string|null $value) private function guessValue(null|array|string $value)
{ {
if (null === $value) { if (null === $value) {
return null; return null;

View File

@ -138,7 +138,7 @@ class CustomFieldsGroup
/** /**
* Get name. * Get name.
*/ */
public function getName(?string $language = null): string|array public function getName(?string $language = null): array|string
{ {
//TODO set this in a service, PLUS twig function //TODO set this in a service, PLUS twig function
if (null !== $language) { if (null !== $language) {

View File

@ -44,7 +44,7 @@ class ParticipationController extends AbstractController
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/create", name="chill_event_participation_create") * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/create", name="chill_event_participation_create")
*/ */
public function createAction(Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function createAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
// test the request is correct // test the request is correct
try { try {
@ -162,7 +162,7 @@ class ParticipationController extends AbstractController
/** /**
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
*/ */
public function createMultiple(Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function createMultiple(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
$participations = $this->handleRequest($request, new Participation(), true); $participations = $this->handleRequest($request, new Participation(), true);
@ -205,7 +205,7 @@ class ParticipationController extends AbstractController
/** /**
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
*/ */
public function createSingle(Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function createSingle(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
$participation = $this->handleRequest($request, new Participation(), false); $participation = $this->handleRequest($request, new Participation(), false);
@ -249,7 +249,7 @@ class ParticipationController extends AbstractController
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/{participation_id}/delete", name="chill_event_participation_delete", requirements={"participation_id"="\d+"}, methods={"GET", "DELETE"}) * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/{participation_id}/delete", name="chill_event_participation_delete", requirements={"participation_id"="\d+"}, methods={"GET", "DELETE"})
*/ */
public function deleteAction($participation_id, Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function deleteAction($participation_id, Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$participation = $em->getRepository(\Chill\EventBundle\Entity\Participation::class)->findOneBy([ $participation = $em->getRepository(\Chill\EventBundle\Entity\Participation::class)->findOneBy([
@ -330,7 +330,7 @@ class ParticipationController extends AbstractController
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/{event_id}/edit_multiple", name="chill_event_participation_edit_multiple") * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/{event_id}/edit_multiple", name="chill_event_participation_edit_multiple")
*/ */
public function editMultipleAction($event_id): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function editMultipleAction($event_id): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
$event = $this->getDoctrine()->getRepository(\Chill\EventBundle\Entity\Event::class) $event = $this->getDoctrine()->getRepository(\Chill\EventBundle\Entity\Event::class)
->find($event_id); ->find($event_id);
@ -388,7 +388,7 @@ class ParticipationController extends AbstractController
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/new", name="chill_event_participation_new") * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/new", name="chill_event_participation_new")
*/ */
public function newAction(Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function newAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
// test the request is correct // test the request is correct
try { try {
@ -560,7 +560,7 @@ class ParticipationController extends AbstractController
Request $request, Request $request,
Participation $participation, Participation $participation,
bool $multiple = false bool $multiple = false
): \Chill\EventBundle\Entity\Participation|array { ): array|\Chill\EventBundle\Entity\Participation {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
if ($em->contains($participation)) { if ($em->contains($participation)) {
@ -637,7 +637,7 @@ class ParticipationController extends AbstractController
* *
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
*/ */
protected function newMultiple(Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse protected function newMultiple(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
$participations = $this->handleRequest($request, new Participation(), true); $participations = $this->handleRequest($request, new Participation(), true);
$ignoredParticipations = $newParticipations = []; $ignoredParticipations = $newParticipations = [];

View File

@ -135,7 +135,7 @@ class Event implements HasCenterInterface, HasScopeInterface
return $this->id; return $this->id;
} }
public function getModerator(): User|null public function getModerator(): null|User
{ {
return $this->moderator; return $this->moderator;
} }

View File

@ -96,7 +96,7 @@ class LoadPostalCodesCommand extends Command
try { try {
$this->addPostalCode($row, $output); $this->addPostalCode($row, $output);
++$num; ++$num;
} catch (ExistingPostalCodeException|CountryCodeNotFoundException|PostalCodeNotValidException $ex) { } catch (CountryCodeNotFoundException|ExistingPostalCodeException|PostalCodeNotValidException $ex) {
$output->writeln('<warning> on line ' . $line . ' : ' . $ex->getMessage() . '</warning>'); $output->writeln('<warning> on line ' . $line . ' : ' . $ex->getMessage() . '</warning>');
} }
++$line; ++$line;

View File

@ -99,7 +99,7 @@ class PasswordController extends AbstractController
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
* @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/recover", name="password_recover") * @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/recover", name="password_recover")
*/ */
public function recoverAction(Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function recoverAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) {
return new Response($this->translator->trans('You are not allowed ' return new Response($this->translator->trans('You are not allowed '
@ -168,7 +168,7 @@ class PasswordController extends AbstractController
* @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse
* @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-recover", name="password_request_recover") * @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-recover", name="password_request_recover")
*/ */
public function requestRecoverAction(Request $request): \Symfony\Component\HttpFoundation\Response|\Symfony\Component\HttpFoundation\RedirectResponse public function requestRecoverAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
{ {
if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) {
return new Response($this->translator->trans('You are not allowed ' return new Response($this->translator->trans('You are not allowed '

View File

@ -50,7 +50,7 @@ final readonly class UserExportController
fn (string $e) => $this->translator->trans('admin.users.export.' . $e), fn (string $e) => $this->translator->trans('admin.users.export.' . $e),
[ [
'id', 'id',
'username', // 'username',
'email', 'email',
'enabled', 'enabled',
'civility_id', 'civility_id',
@ -59,10 +59,10 @@ final readonly class UserExportController
'label', 'label',
'mainCenter_id' , 'mainCenter_id' ,
'mainCenter_name', 'mainCenter_name',
'mainScope_id', /// 'mainScope_id',
'mainScope_name', /// 'mainScope_name',
'userJob_id', /// 'userJob_id',
'userJob_name', /// 'userJob_name',
'currentLocation_id', 'currentLocation_id',
'currentLocation_name', 'currentLocation_name',
'mainLocation_id', 'mainLocation_id',

View File

@ -33,6 +33,7 @@ use Chill\MainBundle\Doctrine\DQL\Greatest;
use Chill\MainBundle\Doctrine\DQL\JsonAggregate; use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength; use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray; use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
use Chill\MainBundle\Doctrine\DQL\JsonBuildObject;
use Chill\MainBundle\Doctrine\DQL\JsonExtract; use Chill\MainBundle\Doctrine\DQL\JsonExtract;
use Chill\MainBundle\Doctrine\DQL\Least; use Chill\MainBundle\Doctrine\DQL\Least;
use Chill\MainBundle\Doctrine\DQL\OverlapsI; use Chill\MainBundle\Doctrine\DQL\OverlapsI;
@ -255,6 +256,7 @@ class ChillMainExtension extends Extension implements
'AGGREGATE' => JsonAggregate::class, 'AGGREGATE' => JsonAggregate::class,
'REPLACE' => Replace::class, 'REPLACE' => Replace::class,
'JSON_EXTRACT' => JsonExtract::class, 'JSON_EXTRACT' => JsonExtract::class,
'JSON_BUILD_OBJECT' => JsonBuildObject::class,
], ],
'numeric_functions' => [ 'numeric_functions' => [
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class, 'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,

View File

@ -29,7 +29,7 @@ class Extract extends FunctionNode
{ {
private string $field; private string $field;
private \Doctrine\ORM\Query\AST\Node|string|null $value = null; private null|\Doctrine\ORM\Query\AST\Node|string $value = null;
//private PathExpression $value; //private PathExpression $value;
//private FunctionNode $value; //private FunctionNode $value;
//private DateDiffFunction $value; //private DateDiffFunction $value;

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Return an aggregation of values in a json representation, as a string.
*
* Internally, this function use the postgresql `jsonb_agg` function. Using
* json allow to aggregate data from different types without have to cast them.
*/
class JsonBuildObject extends FunctionNode
{
/**
* @var array|Node[]
*/
private array $exprs = [];
public function getSql(SqlWalker $sqlWalker)
{
return 'JSONB_BUILD_OBJECT(' . implode(', ', array_map(static fn (Node $expr) => $expr->dispatch($sqlWalker), $this->exprs)) . ')';
}
public function parse(Parser $parser)
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->exprs[] = $parser->ArithmeticPrimary();
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
$parser->match(Lexer::T_COMMA);
$this->exprs[] = $parser->ArithmeticPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -18,7 +18,7 @@ use Doctrine\ORM\Query\SqlWalker;
class JsonExtract extends FunctionNode class JsonExtract extends FunctionNode
{ {
private \Doctrine\ORM\Query\AST\Node|string|null $element = null; private null|\Doctrine\ORM\Query\AST\Node|string $element = null;
private ?\Doctrine\ORM\Query\AST\ArithmeticExpression $keyToExtract = null; private ?\Doctrine\ORM\Query\AST\ArithmeticExpression $keyToExtract = null;

View File

@ -23,7 +23,7 @@ class ToChar extends FunctionNode
{ {
private ?\Doctrine\ORM\Query\AST\ArithmeticExpression $datetime = null; private ?\Doctrine\ORM\Query\AST\ArithmeticExpression $datetime = null;
private \Doctrine\ORM\Query\AST\Node|string|null $fmt = null; private null|\Doctrine\ORM\Query\AST\Node|string $fmt = null;
public function getSql(SqlWalker $sqlWalker) public function getSql(SqlWalker $sqlWalker)
{ {

View File

@ -292,9 +292,7 @@ class User implements UserInterface, \Stringable
$sortedScopeHistories = $scopeHistories->toArray(); $sortedScopeHistories = $scopeHistories->toArray();
usort($sortedScopeHistories, function ($a, $b) { usort($sortedScopeHistories, fn ($a, $b) => $a->getStartDate() < $b->getStartDate() ? 1 : -1);
return $a->getStartDate() < $b->getStartDate() ? 1 : -1;
});
return new ArrayCollection($sortedScopeHistories); return new ArrayCollection($sortedScopeHistories);
} }
@ -346,9 +344,7 @@ class User implements UserInterface, \Stringable
$sortedJobHistories = $jobHistories->toArray(); $sortedJobHistories = $jobHistories->toArray();
usort($sortedJobHistories, function ($a, $b) { usort($sortedJobHistories, fn ($a, $b) => $a->getStartDate() < $b->getStartDate() ? 1 : -1);
return $a->getStartDate() < $b->getStartDate() ? 1 : -1;
});
return new ArrayCollection($sortedJobHistories); return new ArrayCollection($sortedJobHistories);
} }

View File

@ -28,7 +28,7 @@ final readonly class ExportFormHelper
private FormFactoryInterface $formFactory, private FormFactoryInterface $formFactory,
) {} ) {}
public function getDefaultData(string $step, ExportInterface|DirectExportInterface $export, array $options = []): array public function getDefaultData(string $step, DirectExportInterface|ExportInterface $export, array $options = []): array
{ {
return match ($step) { return match ($step) {
'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole())], 'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole())],
@ -45,7 +45,7 @@ final readonly class ExportFormHelper
return $formatter->getFormDefaultData($options['aggregator_aliases']); return $formatter->getFormDefaultData($options['aggregator_aliases']);
} }
private function getDefaultDataStepExport(ExportInterface|DirectExportInterface $export, array $options): array private function getDefaultDataStepExport(DirectExportInterface|ExportInterface $export, array $options): array
{ {
$data = [ $data = [
ExportType::EXPORT_KEY => $export->getFormDefaultData(), ExportType::EXPORT_KEY => $export->getFormDefaultData(),

View File

@ -101,7 +101,7 @@ class ExportManager
* *
* @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias
*/ */
public function &getFiltersApplyingOn(ExportInterface|DirectExportInterface $export, ?array $centers = null): iterable public function &getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, ?array $centers = null): iterable
{ {
if ($export instanceof DirectExportInterface) { if ($export instanceof DirectExportInterface) {
return; return;
@ -124,7 +124,7 @@ class ExportManager
* *
* @return null|iterable<string, AggregatorInterface> a \Generator that contains aggretagors. The key is the filter's alias * @return null|iterable<string, AggregatorInterface> a \Generator that contains aggretagors. The key is the filter's alias
*/ */
public function &getAggregatorsApplyingOn(ExportInterface|DirectExportInterface $export, ?array $centers = null): ?iterable public function &getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, ?array $centers = null): ?iterable
{ {
if ($export instanceof ListInterface || $export instanceof DirectExportInterface) { if ($export instanceof ListInterface || $export instanceof DirectExportInterface) {
return; return;
@ -307,7 +307,7 @@ class ExportManager
* *
* @throws RuntimeException * @throws RuntimeException
*/ */
public function getExport($alias): ExportInterface|DirectExportInterface public function getExport($alias): DirectExportInterface|ExportInterface
{ {
if (!array_key_exists($alias, $this->exports)) { if (!array_key_exists($alias, $this->exports)) {
throw new RuntimeException("The export with alias {$alias} is not known."); throw new RuntimeException("The export with alias {$alias} is not known.");
@ -453,7 +453,7 @@ class ExportManager
* *
*/ */
public function isGrantedForElement( public function isGrantedForElement(
ExportInterface|DirectExportInterface|ModifierInterface $element, DirectExportInterface|ExportInterface|ModifierInterface $element,
\Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export = null, \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export = null,
?array $centers = null ?array $centers = null
): bool { ): bool {

View File

@ -281,9 +281,9 @@ class ExportAddressHelper
}; };
case 'country': case 'country':
return function ($value) use ($key) { return function ($value) use ($sanitizedKey, $translationPrefix) {
if ('_header' === $value) { if ('_header' === $value) {
return 'export.list.acp' . $key; return $translationPrefix . $sanitizedKey;
} }
if (null === $value) { if (null === $value) {

View File

@ -20,21 +20,64 @@ class UserHelper
{ {
public function __construct(private readonly UserRender $userRender, private readonly UserRepositoryInterface $userRepository) {} public function __construct(private readonly UserRender $userRender, private readonly UserRepositoryInterface $userRepository) {}
/**
* Return a callable that will transform a value into a string representing a user
*
* The callable may receive as argument:
*
* - an int or a string, the id of the user;
* - a string containing a json which will be decoded, and will have this structure: array{uid: int, d: string}. The job and scopes will be shown at this date
*
* @param string $key the key of the content
* @param array $values the list of values
* @param string $header the header's content
*/
public function getLabel($key, array $values, string $header): callable public function getLabel($key, array $values, string $header): callable
{ {
return function ($value) use ($header) { return function (null|int|string $value) use ($header) {
if ('_header' === $value) { if ('_header' === $value) {
return $header; return $header;
} }
if (null === $value || null === $user = $this->userRepository->find($value)) { if (null === $value) {
return ''; return '';
} }
return $this->userRender->renderString($user, []); if (is_numeric($value)) {
$uid = $value;
$date = null;
} else {
$decode = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
$uid = $decode['uid'];
if (null === $uid) {
return '';
}
$date = new \DateTimeImmutable($decode['d']);
}
if (null === $user = $this->userRepository->find($uid)) {
return '';
}
return $this->userRender->renderString($user, ['at' => $date]);
}; };
} }
/**
* Return a callable that will transform a value into a string representing a user
*
* The callable may receive as argument:
*
* - an int or a string, the id of the user;
* - a string containing a json which will be decoded, and will have this structure: array{uid: int, d: string}. The job and scopes will be shown at this date * @param $key
*
* @param string $key the key of the element
* @param array $values a list of values
* @param string $header the header's content
* @return callable
*/
public function getLabelMulti($key, array $values, string $header): callable public function getLabelMulti($key, array $values, string $header): callable
{ {
return function ($value) use ($header) { return function ($value) use ($header) {
@ -46,31 +89,36 @@ class UserHelper
return ''; return '';
} }
$decoded = json_decode((string) $value, null, 512, JSON_THROW_ON_ERROR); $decoded = json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);
if (0 === count($decoded)) { if (0 === count($decoded)) {
return ''; return '';
} }
$asStrings = [];
return foreach ($decoded as $userId) {
implode( if (is_array($userId)) {
'|', $uid = $userId['uid'];
array_map( $date = new \DateTimeImmutable($userId['d']);
function (int $userId) { } else {
$user = $this->userRepository->find($userId); $uid = $userId;
$date = null;
}
if (null === $user) { if (null === $uid) {
return ''; continue;
} }
return $this->userRender->renderString($user, []); $user = $this->userRepository->find($uid);
},
array_unique( if (null === $user) {
array_filter($decoded, static fn (?int $userId) => null !== $userId), continue;
SORT_NUMERIC }
)
) $asStrings[$uid] = $this->userRender->renderString($user, ['absence' => false, 'at' => $date]);
); }
return implode('|', $asStrings);
}; };
} }
} }

View File

@ -13,7 +13,8 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\GroupCenter; use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Doctrine\ORM\AbstractQuery; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException; use Doctrine\ORM\NoResultException;
@ -27,7 +28,11 @@ final readonly class UserRepository implements UserRepositoryInterface
{ {
private EntityRepository $repository; private EntityRepository $repository;
public function __construct(private EntityManagerInterface $entityManager) private const FIELDS = ['id', 'email', 'enabled', 'civility_id', 'civility_abbreviation', 'civility_name', 'label', 'mainCenter_id',
'mainCenter_name', 'mainScope_id', 'mainScope_name', 'userJob_id', 'userJob_name', 'currentLocation_id', 'currentLocation_name',
'mainLocation_id', 'mainLocation_name'];
public function __construct(private EntityManagerInterface $entityManager, private Connection $connection)
{ {
$this->repository = $entityManager->getRepository(User::class); $this->repository = $entityManager->getRepository(User::class);
} }
@ -75,48 +80,55 @@ final readonly class UserRepository implements UserRepositoryInterface
} }
/** /**
* @param string $lang * @throws Exception
*/ */
public function findAllAsArray(string $lang): iterable public function findAllAsArray(string $lang): iterable
{ {
$dql = sprintf(<<<'DQL' $sql = sprintf(<<<'SQL'
SELECT SELECT
u.id AS id, u.id,
u.username AS username, u.username AS username,
u.email, u.email AS email,
u.enabled, u.enabled,
IDENTITY(u.civility) AS civility_id, u.civility_id,
JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation, civility.abbreviation->>:lang AS civility_abbreviation,
JSON_EXTRACT(civility.name, :lang) AS civility_name, civility.name->>:lang AS civility_name,
u.label, u.label,
mainCenter.id AS mainCenter_id, mainCenter.id AS mainCenter_id,
mainCenter.name AS mainCenter_name, mainCenter.name AS mainCenter_name,
IDENTITY(u.mainScope) AS mainScope_id, mainScope.id AS mainScope_id,
JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name, mainScope.name->>:lang AS mainScope_name,
IDENTITY(u.userJob) AS userJob_id, userJob.id AS userJob_id,
JSON_EXTRACT(userJob.label, :lang) AS userJob_name, userJob.label->>:lang AS userJob_name,
currentLocation.id AS currentLocation_id, currentLocation.id AS currentLocation_id,
currentLocation.name AS currentLocation_name, currentLocation.name AS currentLocation_name,
mainLocation.id AS mainLocation_id, mainLocation.id AS mainLocation_id,
mainLocation.name AS mainLocation_name, mainLocation.name AS mainLocation_name,
u.absenceStart u.absenceStart
FROM Chill\MainBundle\Entity\User u FROM users u
LEFT JOIN u.civility civility LEFT JOIN chill_main_civility civility ON u.civility_id = civility.id
LEFT JOIN u.currentLocation currentLocation LEFT JOIN centers mainCenter ON u.maincenter_id = mainCenter.id
LEFT JOIN u.mainLocation mainLocation LEFT JOIN chill_main_user_job_history userJobHistory ON u.id = userJobHistory.user_id
LEFT JOIN u.mainCenter mainCenter LEFT JOIN chill_main_user_job userJob ON userJobHistory.job_id = userJob.id AND tstzrange(userJobHistory.startdate, userJobHistory.enddate) @> NOW()
LEFT JOIN u.mainScope mainScope LEFT JOIN chill_main_user_scope_history userScopeHistory ON u.id = userScopeHistory.user_id AND tstzrange(userScopeHistory.startdate, userScopeHistory.enddate) @> NOW()
LEFT JOIN u.userJob userJob LEFT JOIN scopes mainScope ON userScopeHistory.scope_id = mainScope.id
ORDER BY u.label LEFT JOIN chill_main_location currentLocation ON u.currentlocation_id = currentLocation.id
DQL); /// mainScope userJob LEFT JOIN chill_main_location mainLocation ON u.mainlocation_id = mainLocation.id
ORDER BY u.label, u.id
SQL);
$query = $this->entityManager->createQuery($dql) $query = $this->connection->prepare($sql);
->setHydrationMode(AbstractQuery::HYDRATE_ARRAY)
->setParameter('lang', $lang)
;
foreach ($query->toIterable() as $u) { foreach ($query->executeQuery(['lang' => $lang])->iterateAssociative() as $u) {
yield $u; $converted = [];
foreach (self::FIELDS as $f) {
$converted[$f] = $u[strtolower($f)];
}
$converted['absenceStart'] = null !== $u['absencestart'] ? new \DateTimeImmutable($u['absencestart']) : null;
/** @phpstan-ignore-next-line phpstan does not take into account that all required keys will be present */
yield $converted;
} }
} }

View File

@ -1,10 +1,10 @@
<span class="chill-entity entity-user"> <span class="chill-entity entity-user">
{{- user.label }} {{- user.label }}
{%- if opts['user_job'] and user.userJob is not null %} {%- if opts['user_job'] and user.userJob(opts['at']) is not null %}
<span class="user-job">({{ user.userJob.label|localize_translatable_string }})</span> <span class="user-job">({{ user.userJob(opts['at']).label|localize_translatable_string }})</span>
{%- endif -%} {%- endif -%}
{%- if opts['main_scope'] and user.mainScope is not null %} {%- if opts['main_scope'] and user.mainScope(opts['at']) is not null %}
<span class="main-scope">({{ user.mainScope.name|localize_translatable_string }})</span> <span class="main-scope">({{ user.mainScope(opts['at']).name|localize_translatable_string }})</span>
{%- endif -%} {%- endif -%}
{%- if opts['absence'] and user.isAbsent %} {%- if opts['absence'] and user.isAbsent %}
<span class="badge bg-danger rounded-pill" title="{{ 'absence.Absent'|trans|escape('html_attr') }}">{{ 'absence.A'|trans }}</span> <span class="badge bg-danger rounded-pill" title="{{ 'absence.Absent'|trans|escape('html_attr') }}">{{ 'absence.A'|trans }}</span>

View File

@ -66,7 +66,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface
* *
* @return User[] * @return User[]
*/ */
public function findUsersReaching(string $role, array|\Chill\MainBundle\Entity\Center $center, array|\Chill\MainBundle\Entity\Scope|null $scope = null, bool $onlyEnabled = true): array public function findUsersReaching(string $role, array|\Chill\MainBundle\Entity\Center $center, null|array|\Chill\MainBundle\Entity\Scope $scope = null, bool $onlyEnabled = true): array
{ {
return $this->userACLAwareRepository return $this->userACLAwareRepository
->findUsersByReachedACL($role, $center, $scope, $onlyEnabled); ->findUsersByReachedACL($role, $center, $scope, $onlyEnabled);
@ -130,7 +130,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface
* @param Center|Center[] $center * @param Center|Center[] $center
* @return Scope[] * @return Scope[]
*/ */
public function getReachableCircles(UserInterface $user, string $role, \Chill\MainBundle\Entity\Center|array $center) public function getReachableCircles(UserInterface $user, string $role, array|\Chill\MainBundle\Entity\Center $center)
{ {
$scopes = []; $scopes = [];
@ -162,7 +162,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface
/** /**
* Return all reachable scope for a given user, center and role. * Return all reachable scope for a given user, center and role.
*/ */
public function getReachableScopes(UserInterface $user, string $role, Center|array $center): array public function getReachableScopes(UserInterface $user, string $role, array|Center $center): array
{ {
return $this->getReachableCircles($user, $role, $center); return $this->getReachableCircles($user, $role, $center);
} }
@ -172,7 +172,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface
* *
* @param Center|Center[] $center May be an array of center * @param Center|Center[] $center May be an array of center
*/ */
public function userCanReachCenter(User $user, \Chill\MainBundle\Entity\Center|array $center): bool public function userCanReachCenter(User $user, array|\Chill\MainBundle\Entity\Center $center): bool
{ {
if ($center instanceof Traversable) { if ($center instanceof Traversable) {
foreach ($center as $c) { foreach ($center as $c) {

View File

@ -29,5 +29,5 @@ interface AuthorizationHelperInterface
* @param Center|array<Center> $center * @param Center|array<Center> $center
* @return list<Scope> * @return list<Scope>
*/ */
public function getReachableScopes(UserInterface $user, string $role, Center|array $center): array; public function getReachableScopes(UserInterface $user, string $role, array|Center $center): array;
} }

View File

@ -32,7 +32,7 @@ final readonly class ScopeResolverDispatcher
return false; return false;
} }
public function resolveScope(mixed $entity, ?array $options = []): iterable|\Chill\MainBundle\Entity\Scope|null public function resolveScope(mixed $entity, ?array $options = []): null|\Chill\MainBundle\Entity\Scope|iterable
{ {
foreach ($this->resolvers as $resolver) { foreach ($this->resolvers as $resolver) {
if ($resolver->supports($entity, $options)) { if ($resolver->supports($entity, $options)) {

View File

@ -45,7 +45,7 @@ class DiscriminatedObjectDenormalizer implements ContextAwareDenormalizerInterfa
if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) { if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) {
try { try {
return $this->denormalizer->denormalize($data, $localType, $format, $context); return $this->denormalizer->denormalize($data, $localType, $format, $context);
} catch (RuntimeException|NotNormalizableValueException $e) { } catch (NotNormalizableValueException|RuntimeException $e) {
$lastException = $e; $lastException = $e;
} }
} }

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Service\EntityInfo; namespace Chill\MainBundle\Service\EntityInfo;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
class ViewEntityInfoManager class ViewEntityInfoManager
{ {
@ -21,6 +22,7 @@ class ViewEntityInfoManager
*/ */
private readonly iterable $vienEntityInfoProviders, private readonly iterable $vienEntityInfoProviders,
private readonly Connection $connection, private readonly Connection $connection,
private readonly LoggerInterface $logger,
) {} ) {}
public function synchronizeOnDB(): void public function synchronizeOnDB(): void
@ -28,6 +30,8 @@ class ViewEntityInfoManager
$this->connection->transactional(function (Connection $conn): void { $this->connection->transactional(function (Connection $conn): void {
foreach ($this->vienEntityInfoProviders as $viewProvider) { foreach ($this->vienEntityInfoProviders as $viewProvider) {
foreach ($this->createOrReplaceViewSQL($viewProvider, $viewProvider->getViewName()) as $sql) { foreach ($this->createOrReplaceViewSQL($viewProvider, $viewProvider->getViewName()) as $sql) {
$this->logger->debug("Will execute create view sql", ['sql' => $sql]);
$this->logger->debug($sql);
$conn->executeQuery($sql); $conn->executeQuery($sql);
} }
} }
@ -41,7 +45,7 @@ class ViewEntityInfoManager
{ {
return [ return [
"DROP VIEW IF EXISTS {$viewName}", "DROP VIEW IF EXISTS {$viewName}",
sprintf("CREATE VIEW {$viewName} AS %s", $viewProvider->getViewQuery()) sprintf("CREATE OR REPLACE VIEW {$viewName} AS %s", $viewProvider->getViewQuery())
]; ];
} }
} }

View File

@ -28,10 +28,14 @@ class UserRender implements ChillEntityRenderInterface
'main_scope' => true, 'main_scope' => true,
'user_job' => true, 'user_job' => true,
'absence' => true, 'absence' => true,
'at' => null,
]; ];
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {} public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {}
/**
* @param mixed $entity
*/
public function renderBox($entity, array $options): string public function renderBox($entity, array $options): string
{ {
$opts = array_merge(self::DEFAULT_OPTIONS, $options); $opts = array_merge(self::DEFAULT_OPTIONS, $options);
@ -42,20 +46,23 @@ class UserRender implements ChillEntityRenderInterface
]); ]);
} }
/**
* @param mixed $entity
*/
public function renderString($entity, array $options): string public function renderString($entity, array $options): string
{ {
$opts = array_merge(self::DEFAULT_OPTIONS, $options); $opts = array_merge(self::DEFAULT_OPTIONS, $options);
$str = $entity->getLabel(); $str = $entity->getLabel();
if (null !== $entity->getUserJob() && $opts['user_job']) { if (null !== $entity->getUserJob($opts['at']) && $opts['user_job']) {
$str .= ' (' . $this->translatableStringHelper $str .= ' (' . $this->translatableStringHelper
->localize($entity->getUserJob()->getLabel()) . ')'; ->localize($entity->getUserJob($opts['at'])->getLabel()) . ')';
} }
if (null !== $entity->getMainScope() && $opts['main_scope']) { if (null !== $entity->getMainScope($opts['at']) && $opts['main_scope']) {
$str .= ' (' . $this->translatableStringHelper $str .= ' (' . $this->translatableStringHelper
->localize($entity->getMainScope()->getName()) . ')'; ->localize($entity->getMainScope($opts['at'])->getName()) . ')';
} }
if ($entity->isAbsent() && $opts['absence']) { if ($entity->isAbsent() && $opts['absence']) {

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Doctrine\DQL;
use Chill\MainBundle\Entity\Address;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class JsonBuildObjectTest extends KernelTestCase
{
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->entityManager = self::$container->get(EntityManagerInterface::class);
}
/**
* @dataProvider provideQueries
*/
public function testQuery(string $sql, array $params, array $paramType): void
{
$query = $this->entityManager->createQuery($sql);
foreach ($params as $k => $v) {
$query->setParameter($k, $v, $paramType[$k]);
}
$query->setMaxResults(1);
$result = $query->getResult(AbstractQuery::HYDRATE_ARRAY);
self::assertIsArray($result);
}
public function provideQueries(): iterable
{
yield ["SELECT JSON_BUILD_OBJECT(1, 2, 3, 4) FROM " . Address::class . " a", [], []];
yield ["SELECT JSON_BUILD_OBJECT('st', a.street, 'sn', a.streetNumber) FROM " . Address::class . ' a', [], []];
// next query make the test fails. But we do not need it for now.
//yield ["SELECT JSON_BUILD_OBJECT(a.street, :param), LOWER(:param) FROM " . Address::class . " a", ['param' => 1], ['param' => Types::INTEGER]];
}
}

View File

@ -40,7 +40,7 @@ abstract class AddressPart extends FunctionNode
'country_id', 'country_id',
]; ];
private \Doctrine\ORM\Query\AST\Node|string|null $date = null; private null|\Doctrine\ORM\Query\AST\Node|string $date = null;
/** /**
* @var \Doctrine\ORM\Query\AST\Node * @var \Doctrine\ORM\Query\AST\Node

View File

@ -977,7 +977,7 @@ class AccompanyingPeriod implements
/** /**
* @Groups({"read"}) * @Groups({"read"})
*/ */
public function getRequestor(): Person|ThirdParty|null public function getRequestor(): null|Person|ThirdParty
{ {
return $this->requestorPerson ?? $this->requestorThirdParty; return $this->requestorPerson ?? $this->requestorThirdParty;
} }

View File

@ -156,8 +156,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
/** /**
* @var Collection<int, AccompanyingPeriodWorkReferrerHistory> * @var Collection<int, AccompanyingPeriodWorkReferrerHistory>
* @ORM\OneToMany(targetEntity=AccompanyingPeriodWorkReferrerHistory::class, cascade={"persist", "remove"}, mappedBy="accompanyingPeriodWork") * @ORM\OneToMany(targetEntity=AccompanyingPeriodWorkReferrerHistory::class, cascade={"persist", "remove"}, mappedBy="accompanyingPeriodWork", orphanRemoval=true)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer")
*/ */
private Collection $referrersHistory; private Collection $referrersHistory;
@ -357,9 +356,17 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
*/ */
public function getReferrers(): ReadableCollection public function getReferrers(): ReadableCollection
{ {
return $this->referrersHistory->map(fn (AccompanyingPeriodWorkReferrerHistory $h) => $h->getUser()); return $this->referrersHistory
->filter(fn (AccompanyingPeriodWorkReferrerHistory $h) => null === $h->getEndDate())
->map(fn (AccompanyingPeriodWorkReferrerHistory $h) => $h->getUser());
} }
public function getReferrersHistory(): Collection
{
return $this->referrersHistory;
}
/** /**
* @return Collection<int, Result> * @return Collection<int, Result>
*/ */
@ -442,6 +449,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
if ($history->isDateRangeEmpty()) { if ($history->isDateRangeEmpty()) {
$history->removeAccompanyingPeriodWork(); $history->removeAccompanyingPeriodWork();
$this->referrersHistory->removeElement($history);
} }
} }
} }

View File

@ -38,29 +38,24 @@ class AccompanyingPeriodWorkReferrerHistory implements TrackCreationInterface, T
* @var \DateTimeImmutable|null * @var \DateTimeImmutable|null
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) * @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
*/ */
private ?\DateTimeImmutable $endDate; private ?\DateTimeImmutable $endDate = null;
/**
* @var AccompanyingPeriodWork|null
* @ORM\ManyToOne(targetEntity=AccompanyingPeriodWork::class, inversedBy="referrersHistory")
*/
private ?AccompanyingPeriodWork $accompanyingPeriodWork;
public function __construct( public function __construct(
AccompanyingPeriodWork $accompanyingPeriodWork, /**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriodWork::class, inversedBy="referrersHistory")
*/
private ?AccompanyingPeriodWork $accompanyingPeriodWork,
/** /**
* @var User * @var User
* @ORM\ManyToOne(targetEntity=User::class) * @ORM\ManyToOne(targetEntity=User::class)
*/ */
private readonly User $user, private User $user,
/** /**
* @var \DateTimeImmutable * @var \DateTimeImmutable
* @ORM\Column(type="date_immutable", nullable=false) * @ORM\Column(type="date_immutable", nullable=false)
*/ */
private readonly \DateTimeImmutable $startDate, private \DateTimeImmutable $startDate
) { ) {}
$this->accompanyingPeriodWork = $accompanyingPeriodWork;
}
public function getId(): ?int public function getId(): ?int
{ {

View File

@ -96,7 +96,7 @@ class Resource
/** /**
* @Groups({"read"}) * @Groups({"read"})
*/ */
public function getResource(): \Chill\PersonBundle\Entity\Person|\Chill\ThirdPartyBundle\Entity\ThirdParty|null public function getResource(): null|\Chill\PersonBundle\Entity\Person|\Chill\ThirdPartyBundle\Entity\ThirdParty
{ {
return $this->person ?? $this->thirdParty; return $this->person ?? $this->thirdParty;
} }

View File

@ -619,7 +619,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @return true | array True if the accompanying periods are not collapsing, * @return true | array True if the accompanying periods are not collapsing,
* an array with data for displaying the error * an array with data for displaying the error
*/ */
public function checkAccompanyingPeriodsAreNotCollapsing(): bool|array public function checkAccompanyingPeriodsAreNotCollapsing(): array|bool
{ {
$periods = $this->getAccompanyingPeriodsOrdered(); $periods = $this->getAccompanyingPeriodsOrdered();
$periodsNbr = count($periods); $periodsNbr = count($periods);

View File

@ -156,7 +156,7 @@ class SocialAction
* *
* @return Collection|SocialAction[] a list with the elements of the given list which are parent of other elements in the given list * @return Collection|SocialAction[] a list with the elements of the given list which are parent of other elements in the given list
*/ */
public static function findAncestorSocialActions(\Doctrine\Common\Collections\Collection|array $socialActions): Collection public static function findAncestorSocialActions(array|\Doctrine\Common\Collections\Collection $socialActions): Collection
{ {
$ancestors = new ArrayCollection(); $ancestors = new ArrayCollection();
@ -236,7 +236,7 @@ class SocialAction
/** /**
* @param Collection|SocialAction[] $socialActions * @param Collection|SocialAction[] $socialActions
*/ */
public static function getDescendantsWithThisForActions(\Doctrine\Common\Collections\Collection|array $socialActions): Collection public static function getDescendantsWithThisForActions(array|\Doctrine\Common\Collections\Collection $socialActions): Collection
{ {
$unique = []; $unique = [];
@ -420,7 +420,7 @@ class SocialAction
return $this; return $this;
} }
public static function filterRemoveDeactivatedActions(ReadableCollection|array $actions, \DateTime $comparisonDate): ReadableCollection|array public static function filterRemoveDeactivatedActions(array|ReadableCollection $actions, \DateTime $comparisonDate): array|ReadableCollection
{ {
$filterFn = fn (SocialAction $socialAction) => !$socialAction->isDesactivated($comparisonDate); $filterFn = fn (SocialAction $socialAction) => !$socialAction->isDesactivated($comparisonDate);

View File

@ -110,7 +110,7 @@ class SocialIssue
* *
* @return Collection|SocialIssue[] * @return Collection|SocialIssue[]
*/ */
public static function findAncestorSocialIssues(\Doctrine\Common\Collections\Collection|array $socialIssues): Collection public static function findAncestorSocialIssues(array|\Doctrine\Common\Collections\Collection $socialIssues): Collection
{ {
$ancestors = new ArrayCollection(); $ancestors = new ArrayCollection();

View File

@ -81,7 +81,7 @@ final readonly class JobWorkingOnCourseAggregator implements AggregatorInterface
public function getLabels($key, array $values, $data): \Closure public function getLabels($key, array $values, $data): \Closure
{ {
return function (int|string|null $jobId) { return function (null|int|string $jobId) {
if (null === $jobId || '' === $jobId) { if (null === $jobId || '' === $jobId) {
return ''; return '';
} }

View File

@ -81,7 +81,7 @@ final readonly class ScopeWorkingOnCourseAggregator implements AggregatorInterfa
public function getLabels($key, array $values, $data): \Closure public function getLabels($key, array $values, $data): \Closure
{ {
return function (int|string|null $scopeId) { return function (null|int|string $scopeId) {
if (null === $scopeId || '' === $scopeId) { if (null === $scopeId || '' === $scopeId) {
return ''; return '';
} }

View File

@ -41,7 +41,7 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac
public function getLabels($key, array $values, $data): \Closure public function getLabels($key, array $values, $data): \Closure
{ {
return function (int|string|null $userId) { return function (null|int|string $userId) {
if (null === $userId || '' === $userId) { if (null === $userId || '' === $userId) {
return ''; return '';
} }

View File

@ -46,7 +46,7 @@ final readonly class CenterAggregator implements AggregatorInterface
public function getLabels($key, array $values, $data): Closure public function getLabels($key, array $values, $data): Closure
{ {
return function (int|string|null $value) { return function (null|int|string $value) {
if (null === $value || '' === $value) { if (null === $value || '' === $value) {
return ''; return '';
} }

View File

@ -11,13 +11,16 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators; namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Repository\UserJobRepository; use Chill\MainBundle\Repository\UserJobRepository;
use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Service\RollingDate\RollingDateConverter;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -29,7 +32,6 @@ final readonly class JobAggregator implements AggregatorInterface
private const PREFIX = 'acp_work_action_agg_user_job'; private const PREFIX = 'acp_work_action_agg_user_job';
public function __construct( public function __construct(
private RollingDateConverter $rollingDateConverter,
private UserJobRepository $jobRepository, private UserJobRepository $jobRepository,
private TranslatableStringHelper $translatableStringHelper private TranslatableStringHelper $translatableStringHelper
) {} ) {}
@ -44,27 +46,16 @@ final readonly class JobAggregator implements AggregatorInterface
$p = self::PREFIX; $p = self::PREFIX;
$qb $qb
->leftJoin("acpw.referrers", "{$p}_user")
->leftJoin( ->leftJoin(
UserJobHistory::class, UserJob::class,
"{$p}_history", "{$p}_job",
Expr\Join::WITH, Expr\Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user") 'EXISTS (SELECT 1 FROM ' . AccompanyingPeriodWorkReferrerHistory::class . " {$p}_ref_history
) JOIN {$p}_ref_history.user {$p}_ref_history_user JOIN {$p}_ref_history_user.jobHistories {$p}_job_history
->andWhere( WHERE {$p}_ref_history.accompanyingPeriodWork = acpw AND IDENTITY({$p}_job_history.job) = {$p}_job.id AND {$p}_job_history.startDate <= {$p}_ref_history.startDate
$qb->expr()->andX( AND ({$p}_job_history.endDate IS NULL or {$p}_job_history.endDate >= {$p}_ref_history.startDate))"
$qb->expr()->lte("{$p}_history.startDate", ":{$p}_at"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", ":{$p}_at")
)
)
)
->addSelect("IDENTITY({$p}_history.job) as {$p}_select")
->setParameter(
"{$p}_at",
$this->rollingDateConverter->convert($data['job_at'])
) )
->addSelect("{$p}_job.id as {$p}_select")
->addGroupBy("{$p}_select"); ->addGroupBy("{$p}_select");
} }
@ -73,17 +64,11 @@ final readonly class JobAggregator implements AggregatorInterface
return Declarations::SOCIAL_WORK_ACTION_TYPE; return Declarations::SOCIAL_WORK_ACTION_TYPE;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder) {}
{
$builder->add('job_at', PickRollingDateType::class, [
'label' => 'export.aggregator.course_work.by_agent_job.Calc date',
'required' => true
]);
}
public function getFormDefaultData(): array public function getFormDefaultData(): array
{ {
return ['job_at' => new RollingDate(RollingDate::T_TODAY)]; return [];
} }
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)

View File

@ -12,7 +12,10 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators; namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -21,7 +24,13 @@ use function in_array;
final readonly class ReferrerAggregator implements AggregatorInterface final readonly class ReferrerAggregator implements AggregatorInterface
{ {
public function __construct(private UserRepository $userRepository, private UserRender $userRender) {} private const PREFIX = 'acpw_referrer_aggregator';
public function __construct(
private UserRepository $userRepository,
private UserRender $userRender,
private RollingDateConverterInterface $rollingDateConverter
) {}
public function addRole(): ?string public function addRole(): ?string
{ {
@ -30,11 +39,16 @@ final readonly class ReferrerAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
if (!in_array('acpwuser', $qb->getAllAliases(), true)) { $p = self::PREFIX;
$qb->leftJoin('acpw.referrers', 'acpwuser');
}
$qb->addSelect('acpwuser.id AS referrer_aggregator'); $qb
->leftJoin('acpw.referrersHistory', $p . "_acpwusers_history")
->andWhere("{$p}_acpwusers_history.startDate <= :{$p}_calc_date AND ({$p}_acpwusers_history.endDate IS NULL or {$p}_acpwusers_history.endDate > :{$p}_calc_date)");
$qb->setParameter("{$p}_calc_date", $this->rollingDateConverter->convert(
$data['referrer_at'] ?? new RollingDate(RollingDate::T_TODAY)
));
$qb->addSelect("IDENTITY({$p}_acpwusers_history.user) AS referrer_aggregator");
$qb->addGroupBy('referrer_aggregator'); $qb->addGroupBy('referrer_aggregator');
} }
@ -45,11 +59,16 @@ final readonly class ReferrerAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
// no form $builder->add('referrer_at', PickRollingDateType::class, [
'label' => 'export.aggregator.course_work.by_treating_agent.Calc date'
]);
} }
public function getFormDefaultData(): array public function getFormDefaultData(): array
{ {
return []; return [
'referrer_at' => new RollingDate(RollingDate::T_TODAY),
];
} }
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)
@ -65,7 +84,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface
$r = $this->userRepository->find($value); $r = $this->userRepository->find($value);
return $this->userRender->renderString($r, []); return $this->userRender->renderString($r, ['absence' => false, 'user_job' => false, 'main_scope' => false]);
}; };
} }
@ -76,6 +95,6 @@ final readonly class ReferrerAggregator implements AggregatorInterface
public function getTitle(): string public function getTitle(): string
{ {
return 'Group by treating agent'; return 'export.aggregator.course_work.by_treating_agent.Group by treating agent';
} }
} }

View File

@ -11,13 +11,16 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators; namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Service\RollingDate\RollingDateConverter;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -29,7 +32,6 @@ final readonly class ScopeAggregator implements AggregatorInterface
private const PREFIX = 'acp_work_action_agg_user_scope'; private const PREFIX = 'acp_work_action_agg_user_scope';
public function __construct( public function __construct(
private RollingDateConverter $rollingDateConverter,
private ScopeRepository $scopeRepository, private ScopeRepository $scopeRepository,
private TranslatableStringHelper $translatableStringHelper private TranslatableStringHelper $translatableStringHelper
) {} ) {}
@ -44,27 +46,16 @@ final readonly class ScopeAggregator implements AggregatorInterface
$p = self::PREFIX; $p = self::PREFIX;
$qb $qb
->leftJoin("acpw.referrers", "{$p}_user")
->leftJoin( ->leftJoin(
UserScopeHistory::class, Scope::class,
"{$p}_history", "{$p}_scope",
Expr\Join::WITH, Expr\Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user") 'EXISTS (SELECT 1 FROM ' . AccompanyingPeriodWorkReferrerHistory::class . " {$p}_ref_history
) JOIN {$p}_ref_history.user {$p}_ref_history_user JOIN {$p}_ref_history_user.scopeHistories {$p}_scope_history
->andWhere( WHERE {$p}_ref_history.accompanyingPeriodWork = acpw AND IDENTITY({$p}_scope_history.scope) = {$p}_scope.id AND {$p}_scope_history.startDate <= {$p}_ref_history.startDate
$qb->expr()->andX( AND ({$p}_scope_history.endDate IS NULL or {$p}_scope_history.endDate >= {$p}_ref_history.startDate))"
$qb->expr()->lte("{$p}_history.startDate", ":{$p}_at"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", ":{$p}_at")
)
)
)
->addSelect("IDENTITY({$p}_history.scope) as {$p}_select")
->setParameter(
"{$p}_at",
$this->rollingDateConverter->convert($data['scope_at'])
) )
->addSelect("{$p}_scope.id as {$p}_select")
->addGroupBy("{$p}_select"); ->addGroupBy("{$p}_select");
} }
@ -73,17 +64,11 @@ final readonly class ScopeAggregator implements AggregatorInterface
return Declarations::SOCIAL_WORK_ACTION_TYPE; return Declarations::SOCIAL_WORK_ACTION_TYPE;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder) {}
{
$builder->add('scope_at', PickRollingDateType::class, [
'label' => 'export.aggregator.course_work.by_agent_scope.Calc date',
'required' => true,
]);
}
public function getFormDefaultData(): array public function getFormDefaultData(): array
{ {
return ['scope_at' => new RollingDate(RollingDate::T_TODAY)]; return [];
} }
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)

View File

@ -24,6 +24,7 @@ use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\UserHistory; use Chill\PersonBundle\Entity\AccompanyingPeriod\UserHistory;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
@ -69,6 +70,7 @@ class ListAccompanyingPeriodWork implements ListInterface, GroupedExportInterfac
'personsName', 'personsName',
'thirdParties', 'thirdParties',
'handlingThierParty', 'handlingThierParty',
//'acpwReferrers',
'referrers', 'referrers',
'createdAt', 'createdAt',
'createdBy', 'createdBy',
@ -76,7 +78,20 @@ class ListAccompanyingPeriodWork implements ListInterface, GroupedExportInterfac
'updatedBy', 'updatedBy',
]; ];
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly DateTimeHelper $dateTimeHelper, private readonly UserHelper $userHelper, private readonly LabelPersonHelper $personHelper, private readonly LabelThirdPartyHelper $thirdPartyHelper, private readonly TranslatableStringExportLabelHelper $translatableStringExportLabelHelper, private readonly SocialIssueRender $socialIssueRender, private readonly SocialIssueRepository $socialIssueRepository, private readonly SocialActionRender $socialActionRender, private readonly RollingDateConverterInterface $rollingDateConverter, private readonly AggregateStringHelper $aggregateStringHelper, private readonly SocialActionRepository $socialActionRepository) {} public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly DateTimeHelper $dateTimeHelper,
private readonly UserHelper $userHelper,
private readonly LabelPersonHelper $personHelper,
private readonly LabelThirdPartyHelper $thirdPartyHelper,
private readonly TranslatableStringExportLabelHelper $translatableStringExportLabelHelper,
private readonly SocialIssueRender $socialIssueRender,
private readonly SocialIssueRepository $socialIssueRepository,
private readonly SocialActionRender $socialActionRender,
private readonly RollingDateConverterInterface $rollingDateConverter,
private readonly AggregateStringHelper $aggregateStringHelper,
private readonly SocialActionRepository $socialActionRepository
) {}
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
@ -141,6 +156,7 @@ class ListAccompanyingPeriodWork implements ListInterface, GroupedExportInterfac
}, },
'createdBy', 'updatedBy', 'acp_user' => $this->userHelper->getLabel($key, $values, 'export.list.acpw.' . $key), 'createdBy', 'updatedBy', 'acp_user' => $this->userHelper->getLabel($key, $values, 'export.list.acpw.' . $key),
'referrers' => $this->userHelper->getLabel($key, $values, 'export.list.acpw.' . $key), 'referrers' => $this->userHelper->getLabel($key, $values, 'export.list.acpw.' . $key),
//'acpwReferrers' => $this->userHelper->getLabelMulti($key, $values, 'export.list.acpw.' . $key),
'personsName' => $this->personHelper->getLabelMulti($key, $values, 'export.list.acpw.' . $key), 'personsName' => $this->personHelper->getLabelMulti($key, $values, 'export.list.acpw.' . $key),
'handlingThierParty' => $this->thirdPartyHelper->getLabel($key, $values, 'export.list.acpw.' . $key), 'handlingThierParty' => $this->thirdPartyHelper->getLabel($key, $values, 'export.list.acpw.' . $key),
'thirdParties' => $this->thirdPartyHelper->getLabelMulti($key, $values, 'export.list.acpw.' . $key), 'thirdParties' => $this->thirdPartyHelper->getLabelMulti($key, $values, 'export.list.acpw.' . $key),
@ -167,7 +183,7 @@ class ListAccompanyingPeriodWork implements ListInterface, GroupedExportInterfac
public function getResult($query, $data) public function getResult($query, $data)
{ {
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); return dump($query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR));
} }
public function getTitle(): string public function getTitle(): string
@ -269,9 +285,20 @@ class ListAccompanyingPeriodWork implements ListInterface, GroupedExportInterfac
// referrers => at date XXXX // referrers => at date XXXX
$qb $qb
->addSelect('(SELECT IDENTITY(history.user) FROM ' . UserHistory::class . ' history ' . ->addSelect('(SELECT JSON_BUILD_OBJECT(\'uid\', IDENTITY(history.user), \'d\', history.startDate) FROM ' . UserHistory::class . ' history ' .
'WHERE history.accompanyingPeriod = acp AND history.startDate <= :calcDate AND (history.endDate IS NULL OR history.endDate > :calcDate)) AS referrers'); 'WHERE history.accompanyingPeriod = acp AND history.startDate <= :calcDate AND (history.endDate IS NULL OR history.endDate > :calcDate)) AS referrers');
/*
// acpwReferrers at date XXX
$qb
->addSelect('(
SELECT IDENTITY(acpw_ref_history.accompanyingPeriodWork) AS acpw_ref_history_id,
JSON_BUILD_OBJECT(\'uid\', IDENTITY(acpw_ref_history.user), \'d\', acpw_ref_history.startDate)
FROM ' . AccompanyingPeriodWorkReferrerHistory::class . ' acpw_ref_history ' .
'WHERE acpw_ref_history.accompanyingPeriodWork = acpw AND acpw_ref_history.startDate <= :calcDate AND (acpw_ref_history.endDate IS NULL or acpw_ref_history.endDate > :calcDate) GROUP BY acpw_ref_history_id) AS acpwReferrers'
);
*/
// thirdparties // thirdparties
$qb $qb
->addSelect('(SELECT AGGREGATE(tp.id) FROM ' . ThirdParty::class . ' tp ' ->addSelect('(SELECT AGGREGATE(tp.id) FROM ' . ThirdParty::class . ' tp '

View File

@ -132,7 +132,7 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
); );
}, },
'createdBy', 'updatedBy', 'acpw_acp_user' => $this->userHelper->getLabel($key, $values, 'export.list.eval.' . $key), 'createdBy', 'updatedBy', 'acpw_acp_user' => $this->userHelper->getLabel($key, $values, 'export.list.eval.' . $key),
'acpw_referrers' => $this->userHelper->getLabel($key, $values, 'export.list.eval.' . $key), 'acpw_referrers' => $this->userHelper->getLabelMulti($key, $values, 'export.list.eval.' . $key),
'acpw_persons_id' => $this->aggregateStringHelper->getLabelMulti($key, $values, 'export.list.eval.' . $key), 'acpw_persons_id' => $this->aggregateStringHelper->getLabelMulti($key, $values, 'export.list.eval.' . $key),
'acpw_persons' => $this->personHelper->getLabelMulti($key, $values, 'export.list.eval.' . $key), 'acpw_persons' => $this->personHelper->getLabelMulti($key, $values, 'export.list.eval.' . $key),
'eval_title' => $this->translatableStringExportLabelHelper 'eval_title' => $this->translatableStringExportLabelHelper
@ -252,8 +252,8 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
// referrers => at date XXXX // referrers => at date XXXX
$qb $qb
->addSelect('(SELECT IDENTITY(history.user) FROM ' . UserHistory::class . ' history ' . ->addSelect('(SELECT JSON_BUILD_OBJECT(\'uid\', IDENTITY(history.user), \'d\', history.startDate) FROM ' . UserHistory::class . ' history ' .
'WHERE history.accompanyingPeriod = acp AND history.startDate <= :calc_date AND (history.endDate IS NULL OR history.endDate > :calc_date)) AS acpw_referrers'); 'WHERE history.accompanyingPeriod = acp AND history.startDate <= :calc_date AND (history.endDate IS NULL OR history.endDate > :calc_date)) AS referrers');
// persons // persons
$qb $qb

View File

@ -72,9 +72,10 @@ class CreatorJobFilter implements FilterInterface
) )
) )
->andWhere($qb->expr()->in("{$p}_history.job", ":{$p}_jobs")) ->andWhere($qb->expr()->in("{$p}_history.job", ":{$p}_jobs"))
->setParameters([ ->setParameter(
"{$p}_jobs" => $data["creator_job"], "{$p}_jobs",
]); $data["creator_job"],
);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -63,11 +63,15 @@ readonly class JobWorkingOnCourseFilter implements FilterInterface
. "AND {$p}_info.infoDate >= :{$p}_start and {$p}_info.infoDate < :{$p}_end" . "AND {$p}_info.infoDate >= :{$p}_start and {$p}_info.infoDate < :{$p}_end"
) )
) )
->setParameters([ ->setParameter("{$p}_jobs", $data['jobs'])
"{$p}_jobs" => $data['jobs'], ->setParameter(
"{$p}_start" => $this->rollingDateConverter->convert($data['start_date']), "{$p}_start",
"{$p}_end" => $this->rollingDateConverter->convert($data['end_date']) $this->rollingDateConverter->convert($data['start_date']),
]) )
->setParameter(
"{$p}_end",
$this->rollingDateConverter->convert($data['end_date'])
)
; ;
} }

View File

@ -63,11 +63,15 @@ readonly class ScopeWorkingOnCourseFilter implements FilterInterface
. "AND {$p}_info.infoDate >= :{$p}_start AND {$p}_info.infoDate < :{$p}_end" . "AND {$p}_info.infoDate >= :{$p}_start AND {$p}_info.infoDate < :{$p}_end"
) )
) )
->setParameters([ ->setParameter("{$p}_scopes", $data["scopes"])
"{$p}_scopes" => $data["scopes"], ->setParameter(
"{$p}_start" => $this->rollingDateConverter->convert($data["start_date"]), "{$p}_start",
"{$p}_end" => $this->rollingDateConverter->convert($data["end_date"]) $this->rollingDateConverter->convert($data["start_date"]),
]) )
->setParameter(
"{$p}_end",
$this->rollingDateConverter->convert($data["end_date"])
)
; ;
} }

View File

@ -72,9 +72,10 @@ class UserJobFilter implements FilterInterface
) )
) )
->andWhere($qb->expr()->in("{$p}_jobHistory.job", ":{$p}_job")) ->andWhere($qb->expr()->in("{$p}_jobHistory.job", ":{$p}_job"))
->setParameters([ ->setParameter(
"{$p}_job" => $data["jobs"], "{$p}_job",
]) $data["jobs"],
)
; ;
} }

View File

@ -72,9 +72,10 @@ class UserScopeFilter implements FilterInterface
) )
) )
->andWhere($qb->expr()->in("{$p}_scopeHistory.scope", ":{$p}_scopes")) ->andWhere($qb->expr()->in("{$p}_scopeHistory.scope", ":{$p}_scopes"))
->setParameters([ ->setParameter(
"{$p}_scopes" => $data["scopes"], "{$p}_scopes",
]) $data["scopes"],
)
; ;
} }

View File

@ -18,6 +18,7 @@ use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Service\RollingDate\RollingDateConverter;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\Query\Expr\Andx;
@ -32,7 +33,6 @@ class JobFilter implements FilterInterface
private const PREFIX = 'acp_work_action_filter_user_job'; private const PREFIX = 'acp_work_action_filter_user_job';
public function __construct( public function __construct(
private readonly RollingDateConverter $rollingDateConverter,
protected TranslatorInterface $translator, protected TranslatorInterface $translator,
private readonly TranslatableStringHelper $translatableStringHelper private readonly TranslatableStringHelper $translatableStringHelper
) {} ) {}
@ -46,30 +46,14 @@ class JobFilter implements FilterInterface
{ {
$p = self::PREFIX; $p = self::PREFIX;
$qb $qb->andWhere(
->leftJoin("acpw.referrers", "{$p}_user") 'EXISTS (SELECT 1 FROM ' . AccompanyingPeriodWorkReferrerHistory::class . " {$p}_ref_history
->leftJoin( JOIN {$p}_ref_history.user {$p}_ref_history_user JOIN {$p}_ref_history_user.jobHistories {$p}_job_history
UserJobHistory::class, WHERE {$p}_ref_history.accompanyingPeriodWork = acpw AND {$p}_job_history.job IN (:{$p}_job) AND {$p}_job_history.startDate <= {$p}_ref_history.startDate
"{$p}_history", AND ({$p}_job_history.endDate IS NULL or {$p}_job_history.endDate >= {$p}_ref_history.startDate))"
Expr\Join::WITH, );
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
) $qb->setParameter("{$p}_job", $data["job"]);
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", ":{$p}_at"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", ":{$p}_at")
)
)
)
->andWhere(
$qb->expr()->in("{$p}_history.job", ":{$p}_job")
)
->setParameters([
"{$p}_job" => $data["job"],
"{$p}_at" => $this->rollingDateConverter->convert($data['job_at'])
]);
} }
public function applyOn(): string public function applyOn(): string
@ -88,10 +72,6 @@ class JobFilter implements FilterInterface
'multiple' => true, 'multiple' => true,
'expanded' => true, 'expanded' => true,
]) ])
->add('job_at', PickRollingDateType::class, [
'label' => 'export.filter.work.by_user_job.Calc date',
'required' => true,
])
; ;
} }

View File

@ -12,7 +12,10 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\Query\Expr\Andx;
@ -20,8 +23,12 @@ use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
class ReferrerFilter implements FilterInterface final readonly class ReferrerFilter implements FilterInterface
{ {
private const PREFIX = 'acpw_referrer_filter';
public function __construct(private RollingDateConverterInterface $rollingDateConverter) {}
public function addRole(): ?string public function addRole(): ?string
{ {
return null; return null;
@ -29,21 +36,19 @@ class ReferrerFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
if (!in_array('acpwuser', $qb->getAllAliases(), true)) { $p = self::PREFIX;
$qb->join('acpw.referrers', 'acpwuser');
}
$where = $qb->getDQLPart('where'); $qb
$clause = $qb->expr()->in('acpwuser', ':agents'); ->leftJoin('acpw.referrersHistory', $p . "_acpwusers_history")
->andWhere("{$p}_acpwusers_history.startDate <= :{$p}_calc_date AND ({$p}_acpwusers_history.endDate IS NULL or {$p}_acpwusers_history.endDate > :{$p}_calc_date)")
->andWhere("{$p}_acpwusers_history.user IN (:{$p}_agents)");
if ($where instanceof Andx) { $qb
$where->add($clause); ->setParameter("{$p}_agents", $data['accepted_agents'])
} else { ->setParameter("{$p}_calc_date", $this->rollingDateConverter->convert(
$where = $qb->expr()->andX($clause); $data['agent_at'] ?? new RollingDate(RollingDate::T_TODAY)
} ))
;
$qb->add('where', $where);
$qb->setParameter('agents', $data['accepted_agents']);
} }
public function applyOn(): string public function applyOn(): string
@ -53,13 +58,23 @@ class ReferrerFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_agents', PickUserDynamicType::class, [ $builder
'multiple' => true, ->add('accepted_agents', PickUserDynamicType::class, [
]); 'multiple' => true,
'label' => 'export.filter.work.by_treating_agent.Accepted agents'
])
->add('agent_at', PickRollingDateType::class, [
'label' => 'export.filter.work.by_treating_agent.Calc date',
'help' => 'export.filter.work.by_treating_agent.calc_date_help',
])
;
} }
public function getFormDefaultData(): array public function getFormDefaultData(): array
{ {
return []; return [
'accepted_agents' => [],
'agent_at' => new RollingDate(RollingDate::T_TODAY),
];
} }
public function describeAction($data, $format = 'string'): array public function describeAction($data, $format = 'string'): array
@ -71,13 +86,14 @@ class ReferrerFilter implements FilterInterface
} }
return [ return [
'Filtered by treating agent: only %agents%', [ 'exports.filter.work.by_treating_agent.Filtered by treating agent at date', [
'%agents' => implode(', ', $users), 'agents' => implode(', ', $users),
'agent_at' => $this->rollingDateConverter->convert($data['agent_at'] ?? new RollingDate(RollingDate::T_TODAY)),
], ]; ], ];
} }
public function getTitle(): string public function getTitle(): string
{ {
return 'Filter by treating agent'; return 'export.filter.work.by_treating_agent.Filter by treating agent';
} }
} }

View File

@ -18,6 +18,7 @@ use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use Chill\MainBundle\Service\RollingDate\RollingDateConverter;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -30,7 +31,6 @@ class ScopeFilter implements FilterInterface
private const PREFIX = 'acp_work_action_filter_user_scope'; private const PREFIX = 'acp_work_action_filter_user_scope';
public function __construct( public function __construct(
private readonly RollingDateConverter $rollingDateConverter,
protected TranslatorInterface $translator, protected TranslatorInterface $translator,
private readonly TranslatableStringHelper $translatableStringHelper private readonly TranslatableStringHelper $translatableStringHelper
) {} ) {}
@ -44,30 +44,13 @@ class ScopeFilter implements FilterInterface
{ {
$p = self::PREFIX; $p = self::PREFIX;
$qb $qb->andWhere(
->leftJoin("acpw.referrers", "{$p}_user") 'EXISTS (SELECT 1 FROM ' . AccompanyingPeriodWorkReferrerHistory::class . " {$p}_ref_history
->leftJoin( JOIN {$p}_ref_history.user {$p}_ref_history_user JOIN {$p}_ref_history_user.scopeHistories {$p}_scope_history
UserScopeHistory::class, WHERE {$p}_ref_history.accompanyingPeriodWork = acpw AND {$p}_scope_history.scope IN (:{$p}_scope) AND {$p}_scope_history.startDate <= {$p}_ref_history.startDate
"{$p}_history", AND ({$p}_scope_history.endDate IS NULL or {$p}_scope_history.endDate >= {$p}_ref_history.startDate))"
Expr\Join::WITH, )
$qb->expr()->eq("{$p}_history.user", "{$p}_user") ->setParameter("{$p}_scope", $data["scope"]);
)
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", ":{$p}_at"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", ":{$p}_at")
)
)
)
->andWhere(
$qb->expr()->in("{$p}_history.scope", ":{$p}_scope")
)
->setParameters([
"{$p}_scope" => $data["scope"],
"{$p}_at" => $this->rollingDateConverter->convert($data['scope_at'])
]);
} }
public function applyOn() public function applyOn()
@ -86,10 +69,6 @@ class ScopeFilter implements FilterInterface
'multiple' => true, 'multiple' => true,
'expanded' => true, 'expanded' => true,
]) ])
->add('scope_at', PickRollingDateType::class, [
'label' => 'export.filter.work.by_user_scope.Calc date',
'required' => true,
])
; ;
} }

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\Helper\DateTimeHelper; use Chill\MainBundle\Export\Helper\DateTimeHelper;
use Chill\MainBundle\Export\Helper\ExportAddressHelper; use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\Helper\UserHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
@ -59,8 +60,10 @@ final readonly class ListAccompanyingPeriodHelper
'scopes', 'scopes',
'socialIssues', 'socialIssues',
'acpCreatedAt', 'acpCreatedAt',
'acpCreatedBy_id',
'acpCreatedBy', 'acpCreatedBy',
'acpUpdatedAt', 'acpUpdatedAt',
'acpUpdatedBy_id',
'acpUpdatedBy', 'acpUpdatedBy',
]; ];
@ -75,6 +78,7 @@ final readonly class ListAccompanyingPeriodHelper
private SocialIssueRender $socialIssueRender, private SocialIssueRender $socialIssueRender,
private TranslatableStringHelperInterface $translatableStringHelper, private TranslatableStringHelperInterface $translatableStringHelper,
private TranslatorInterface $translator, private TranslatorInterface $translator,
private UserHelper $userHelper,
) {} ) {}
public function getQueryKeys($data) public function getQueryKeys($data)
@ -104,6 +108,7 @@ final readonly class ListAccompanyingPeriodHelper
return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)); 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),
'locationPersonName', 'requestorPerson' => function ($value) use ($key) { 'locationPersonName', 'requestorPerson' => function ($value) use ($key) {
if ('_header' === $value) { if ('_header' === $value) {
return 'export.list.acp.' . $key; return 'export.list.acp.' . $key;
@ -204,11 +209,11 @@ final readonly class ListAccompanyingPeriodHelper
// add the field which are simple association // add the field which are simple association
$qb $qb
->leftJoin('acp.createdBy', "acp_created_by_t") ->addSelect('IDENTITY(acp.createdBy) AS acpCreatedBy_id')
->addSelect('acp_created_by_t.label AS acpCreatedBy'); ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(acp.createdBy), \'d\', acp.createdAt) AS acpCreatedBy');
$qb $qb
->leftJoin('acp.updatedBy', "acp_updated_by_t") ->addSelect('IDENTITY(acp.updatedBy) AS acpUpdatedBy_id')
->addSelect('acp_updated_by_t.label AS acpUpdatedBy'); ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(acp.updatedBy), \'d\', acp.updatedAt) AS acpUpdatedBy');
foreach (['origin' => 'label', 'closingMotive' => 'name', 'job' => 'label', 'administrativeLocation' => 'name'] as $entity => $field) { foreach (['origin' => 'label', 'closingMotive' => 'name', 'job' => 'label', 'administrativeLocation' => 'name'] as $entity => $field) {
$qb $qb
@ -230,7 +235,7 @@ final readonly class ListAccompanyingPeriodHelper
// referree at date // referree at date
$qb $qb
->addSelect('referrer_t.label AS referrer') ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(userHistory.user), \'d\', userHistory.startDate) AS referrer')
->addSelect('userHistory.startDate AS referrerSince') ->addSelect('userHistory.startDate AS referrerSince')
->leftJoin('acp.userHistories', 'userHistory') ->leftJoin('acp.userHistories', 'userHistory')
->leftJoin('userHistory.user', 'referrer_t') ->leftJoin('userHistory.user', 'referrer_t')

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Repository\AccompanyingPeriod; namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -91,7 +92,9 @@ class AccompanyingPeriodWorkEvaluationRepository implements ObjectRepository
$qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')), $qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')),
$qb->expr()->orX( $qb->expr()->orX(
$qb->expr()->eq('period.user', ':user'), $qb->expr()->eq('period.user', ':user'),
$qb->expr()->isMemberOf(':user', 'work.referrers') $qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodWork::class . ' subw JOIN subw.referrersHistory subw_ref_history WHERE subw.id = work.id AND subw_ref_history.user = :user'
)
) )
) )
) )

View File

@ -132,10 +132,10 @@ final readonly class AccompanyingPeriodWorkRepository implements ObjectRepositor
// set limit and offset // set limit and offset
$sql .= " ORDER BY $sql .= " ORDER BY
CASE WHEN enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC, CASE WHEN w.enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC,
startdate DESC, w.startdate DESC,
enddate DESC, w.enddate DESC,
id DESC"; w.id DESC";
$sql .= " LIMIT :limit OFFSET :offset"; $sql .= " LIMIT :limit OFFSET :offset";
@ -239,7 +239,9 @@ final readonly class AccompanyingPeriodWorkRepository implements ObjectRepositor
$qb->expr()->lte('w.startDate', ':until'), $qb->expr()->lte('w.startDate', ':until'),
$qb->expr()->orX( $qb->expr()->orX(
$qb->expr()->eq('period.user', ':user'), $qb->expr()->eq('period.user', ':user'),
$qb->expr()->isMemberOf(':user', 'w.referrers') $qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodWork::class . ' subw JOIN subw.referrersHistory subw_ref_history WHERE subw.id = w.id AND subw_ref_history.user = :user and subw.ref_history.endDate IS NULL'
)
) )
) )
) )

View File

@ -37,7 +37,7 @@ class AccompanyingPeriodWorkNormalizer implements ContextAwareNormalizerInterfac
* *
* @throws ExceptionInterface * @throws ExceptionInterface
*/ */
public function normalize($object, ?string $format = null, array $context = []): array|\ArrayObject|bool|float|int|string|null public function normalize($object, ?string $format = null, array $context = []): null|array|\ArrayObject|bool|float|int|string
{ {
$initial = $this->normalizer->normalize($object, $format, array_merge( $initial = $this->normalizer->normalize($object, $format, array_merge(
$context, $context,

View File

@ -54,7 +54,7 @@ class AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo implements Acc
public function getFromStatement(): string public function getFromStatement(): string
{ {
return 'chill_person_accompanying_period_work w return 'chill_person_accompanying_period_work w
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id'; LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON w.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> w.endDate';
} }
public function getWhereClause(): string public function getWhereClause(): string

View File

@ -11,10 +11,11 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart; namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface; use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface class AccompanyingPeriodWorkEvaluationCreationQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
{ {
public function getAccompanyingPeriodIdColumn(): string public function getAccompanyingPeriodIdColumn(): string
{ {
@ -33,12 +34,12 @@ class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodI
public function getUserIdColumn(): string public function getUserIdColumn(): string
{ {
return 'cpapwr.user_id'; return 'e.createdBy_id';
} }
public function getDateTimeColumn(): string public function getDateTimeColumn(): string
{ {
return 'e.maxDate'; return 'e.createdAt';
} }
public function getMetadataColumn(): string public function getMetadataColumn(): string
@ -48,18 +49,18 @@ class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodI
public function getDiscriminator(): string public function getDiscriminator(): string
{ {
return 'accompanying_period_work_evaluation_max'; return 'accompanying_period_work_evaluation_creation';
} }
public function getFromStatement(): string public function getFromStatement(): string
{ {
return 'chill_person_accompanying_period_work_evaluation e return 'chill_person_accompanying_period_work_evaluation e
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id'; LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> e.startDate';
} }
public function getWhereClause(): string public function getWhereClause(): string
{ {
return 'e.maxDate IS NOT NULL'; return 'e.createdAt IS NOT NULL';
} }
} }

View File

@ -56,7 +56,7 @@ class AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo impl
{ {
return 'chill_person_accompanying_period_work_evaluation e return 'chill_person_accompanying_period_work_evaluation e
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id'; LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> e.maxDate';
} }
public function getWhereClause(): string public function getWhereClause(): string

View File

@ -56,11 +56,11 @@ class AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo im
{ {
return 'chill_person_accompanying_period_work_evaluation e return 'chill_person_accompanying_period_work_evaluation e
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id'; LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> e.startDate';
} }
public function getWhereClause(): string public function getWhereClause(): string
{ {
return ''; return 'e.startDate IS NOT NULL';
} }
} }

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
class AccompanyingPeriodWorkNewReferrerQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
{
public function getAccompanyingPeriodIdColumn(): string
{
return 'w.accompanyingperiod_id';
}
public function getRelatedEntityColumn(): string
{
return AccompanyingPeriodWork::class;
}
public function getRelatedEntityIdColumn(): string
{
return 'w.id';
}
public function getUserIdColumn(): string
{
return 'cpapwr.user_id';
}
public function getDateTimeColumn(): string
{
return 'cpapwr.startDate';
}
public function getMetadataColumn(): string
{
return "'{}'::jsonb";
}
public function getDiscriminator(): string
{
return 'accompanying_period_work_referrer_new';
}
public function getFromStatement(): string
{
return 'chill_person_accompanying_period_work w
JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id';
}
public function getWhereClause(): string
{
return '';
}
}

View File

@ -54,7 +54,7 @@ class AccompanyingPeriodWorkStartQueryPartForAccompanyingPeriodInfo implements A
public function getFromStatement(): string public function getFromStatement(): string
{ {
return 'chill_person_accompanying_period_work w return 'chill_person_accompanying_period_work w
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id'; LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> w.startDate';
} }
public function getWhereClause(): string public function getWhereClause(): string

View File

@ -24,6 +24,7 @@ use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Relationships\Relation; use Chill\PersonBundle\Entity\Relationships\Relation;
use Chill\PersonBundle\Entity\Relationships\Relationship; use Chill\PersonBundle\Entity\Relationships\Relationship;
use Chill\PersonBundle\Repository\Person\PersonCenterHistoryInterface;
use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\PersonRepository;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -44,6 +45,8 @@ class PersonMoveTest extends KernelTestCase
private CenterRepositoryInterface $centerRepository; private CenterRepositoryInterface $centerRepository;
private PersonCenterHistoryInterface $personCenterHistory;
/** /**
* @var list<array{0: class-string, 1: int}> * @var list<array{0: class-string, 1: int}>
*/ */
@ -56,6 +59,7 @@ class PersonMoveTest extends KernelTestCase
$this->personMoveManager = self::$container->get(PersonMoveManager::class); $this->personMoveManager = self::$container->get(PersonMoveManager::class);
$this->eventDispatcher = self::$container->get(EventDispatcherInterface::class); $this->eventDispatcher = self::$container->get(EventDispatcherInterface::class);
$this->centerRepository = self::$container->get(CenterRepositoryInterface::class); $this->centerRepository = self::$container->get(CenterRepositoryInterface::class);
$this->personCenterHistory = self::$container->get(PersonCenterHistoryInterface::class);
} }
public static function tearDownAfterClass(): void public static function tearDownAfterClass(): void
@ -146,18 +150,22 @@ class PersonMoveTest extends KernelTestCase
$personB = $this->em->find(Person::class, $personB->getId()); $personB = $this->em->find(Person::class, $personB->getId());
$message = 'Move persons with overlapping center histories'; $message = 'Move persons with overlapping center histories';
$this->em->refresh($personB);
self::assertCount(0, $personsByIdOfA); self::assertCount(0, $personsByIdOfA);
self::assertNotNull($personB?->getId(), $message); self::assertNotNull($personB?->getId(), $message);
$centerHistoriesB = $personB->getCenterHistory(); $centersHistories = $this->personCenterHistory->findBy(['person' => $personB]);
$oldestDate = new \DateTimeImmutable('2023-01-01');
$this->em->refresh($centerHistoriesB->first()); // compute the oldest center history
$oldestCenterHistory = null;
foreach ($centersHistories as $centerHistory) {
$this->em->refresh($centerHistory);
if (null === $oldestCenterHistory || ($oldestCenterHistory instanceof Person\PersonCenterHistory && $oldestCenterHistory->getStartDate() >= $centerHistory->getStartDate())) {
$oldestCenterHistory = $centerHistory;
}
}
self::assertCount(2, $centerHistoriesB); self::assertCount(2, $centersHistories);
self::assertEquals($oldestDate, $centerHistoriesB->first()->getStartDate()); self::assertEquals('2023-01-01', $oldestCenterHistory?->getStartDate()->format('Y-m-d'));
self::$entitiesToDelete[] = [Person::class, $personA]; self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB]; self::$entitiesToDelete[] = [Person::class, $personB];
@ -218,11 +226,11 @@ class PersonMoveTest extends KernelTestCase
$this->em->persist($memberA); $this->em->persist($memberA);
$this->em->persist($memberB); $this->em->persist($memberB);
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
self::$entitiesToDelete[] = [HouseholdMember::class, $memberA]; self::$entitiesToDelete[] = [HouseholdMember::class, $memberA];
self::$entitiesToDelete[] = [HouseholdMember::class, $memberB]; self::$entitiesToDelete[] = [HouseholdMember::class, $memberB];
self::$entitiesToDelete[] = [Household::class, $household]; self::$entitiesToDelete[] = [Household::class, $household];
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
yield [$personA, $personB, "move 2 people having the same household at the same time"]; yield [$personA, $personB, "move 2 people having the same household at the same time"];

View File

@ -52,16 +52,6 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase
private ?int $personId = null; private ?int $personId = null;
private KernelBrowser $client;
/**
* Setup before each test method (see phpunit doc).
*/
protected function setUp(): void
{
$this->client = $this->getClientAuthenticated();
}
protected function tearDown(): void protected function tearDown(): void
{ {
self::ensureKernelShutdown(); self::ensureKernelShutdown();
@ -427,8 +417,9 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase
*/ */
public function testAccompanyingCourseShow(int $personId, int $periodId) public function testAccompanyingCourseShow(int $personId, int $periodId)
{ {
$c = $this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $periodId)); $client = $this->getClientAuthenticated();
$response = $this->client->getResponse(); $client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $periodId));
$response = $client->getResponse();
$this->assertTrue(in_array($response->getStatusCode(), [200, 422], true)); $this->assertTrue(in_array($response->getStatusCode(), [200, 422], true));
@ -548,12 +539,13 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase
*/ */
public function testReferralAvailable(int $personId, int $periodId) public function testReferralAvailable(int $personId, int $periodId)
{ {
$this->client->request( $client = $this->getClientAuthenticated();
$client->request(
Request::METHOD_POST, Request::METHOD_POST,
sprintf('/api/1.0/person/accompanying-course/%d/referrers-suggested.json', $periodId) sprintf('/api/1.0/person/accompanying-course/%d/referrers-suggested.json', $periodId)
); );
$this->assertTrue(in_array($this->client->getResponse()->getStatusCode(), [200, 422], true)); $this->assertTrue(in_array($client->getResponse()->getStatusCode(), [200, 422], true));
} }
/** /**
@ -732,8 +724,9 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase
public function testShow404() public function testShow404()
{ {
$this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', 99999)); $client = $this->getClientAuthenticated();
$response = $this->client->getResponse(); $client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', 99999));
$response = $client->getResponse();
$this->assertEquals(404, $response->getStatusCode(), "Test that the response of rest api has a status code 'not found' (404)"); $this->assertEquals(404, $response->getStatusCode(), "Test that the response of rest api has a status code 'not found' (404)");
} }

View File

@ -137,20 +137,17 @@ final class HouseholdApiControllerTest extends WebTestCase
$qb = self::$container->get(EntityManagerInterface::class) $qb = self::$container->get(EntityManagerInterface::class)
->createQueryBuilder(); ->createQueryBuilder();
$period = $qb $personIds = $qb
->select('ap') ->select("p.id AS pid")
->from(AccompanyingPeriod::class, 'ap') ->from(Person::class, 'p')
->where( ->where(
$qb->expr()->gte('SIZE(ap.participations)', 2) $qb->expr()->gte('SIZE(p.accompanyingPeriodParticipations)', 2)
) )
->getQuery() ->getQuery()
->setMaxResults(1) ->setMaxResults(1)
->getSingleResult(); ->getSingleResult();
$person = $period->getParticipations() yield [$personIds['pid']];
->first()->getPerson();
yield [$person->getId()];
self::ensureKernelShutdown(); self::ensureKernelShutdown();
} }

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Entity\AccompanyingPeriod;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class AccompanyingPeriodWorkTest extends TestCase
{
public function testReferrerHistory(): void
{
$work = new AccompanyingPeriodWork();
$userA = new User();
$userB = new User();
$userC = new User();
self::assertCount(0, $work->getReferrers());
$work->addReferrer($userA);
self::assertCount(1, $work->getReferrers());
self::assertContains($userA, $work->getReferrers());
$work->addReferrer($userB);
self::assertCount(2, $work->getReferrers());
self::assertContains($userA, $work->getReferrers());
self::assertContains($userB, $work->getReferrers());
$work->addReferrer($userC);
$work->removeReferrer($userB);
self::assertCount(2, $work->getReferrers());
self::assertContains($userA, $work->getReferrers());
self::assertNotContains($userB, $work->getReferrers());
self::assertContains($userC, $work->getReferrers());
$work->removeReferrer($userA);
self::assertNotContains($userA, $work->getReferrers());
self::assertNotContains($userB, $work->getReferrers());
self::assertContains($userC, $work->getReferrers());
}
public function testReferrerHistoryOnDifferentDays(): void
{
$work = new AccompanyingPeriodWork();
$userA = new User();
$userB = new User();
$userC = new User();
$work->addReferrer($userA);
$historyA = $work->getReferrersHistory()->first();
$reflection = new \ReflectionClass($historyA);
$startDateReflection = $reflection->getProperty('startDate');
$startDateReflection->setAccessible(true);
$startDateReflection->setValue($historyA, new \DateTimeImmutable('1 year ago'));
$work->addReferrer($userB);
$work->addReferrer($userC);
$work->removeReferrer($userB);
$work->removeReferrer($userA);
self::assertCount(1, $work->getReferrers());
self::assertNotContains($userA, $work->getReferrers());
self::assertNotContains($userB, $work->getReferrers());
self::assertContains($userC, $work->getReferrers());
self::assertCount(2, $work->getReferrersHistory());
$historyA = $work->getReferrersHistory()
->filter(fn (AccompanyingPeriodWorkReferrerHistory $h) => $userA === $h->getUser())
->first();
self::assertNotFalse($historyA);
self::assertSame($userA, $historyA->getUser());
self::assertEquals((new \DateTimeImmutable())->format('Y-m-d'), $historyA->getEndDate()->format('Y-m-d'));
}
}

View File

@ -40,9 +40,7 @@ final class JobAggregatorTest extends AbstractAggregatorTest
public function getFormData(): array public function getFormData(): array
{ {
return [ return [
[ [],
'job_at' => new RollingDate(RollingDate::T_FIXED_DATE, \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01')),
],
]; ];
} }
@ -57,7 +55,6 @@ final class JobAggregatorTest extends AbstractAggregatorTest
->select('count(acp.id)') ->select('count(acp.id)')
->from(AccompanyingPeriod::class, 'acp') ->from(AccompanyingPeriod::class, 'acp')
->join('acp.works', 'acpw') ->join('acp.works', 'acpw')
->join('acpw.referrers', 'acpwuser'),
]; ];
} }
} }

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Tests\Export\Aggregator\SocialWorkAggregators; namespace Chill\PersonBundle\Tests\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ReferrerAggregator; use Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ReferrerAggregator;
@ -39,7 +40,10 @@ final class ReferrerAggregatorTest extends AbstractAggregatorTest
public function getFormData(): array public function getFormData(): array
{ {
return [ return [
[], [], // there are previous saved export which does not contains any data
[
'referrer_at' => new RollingDate(RollingDate::T_TODAY)
]
]; ];
} }

View File

@ -40,9 +40,7 @@ final class ScopeAggregatorTest extends AbstractAggregatorTest
public function getFormData(): array public function getFormData(): array
{ {
return [ return [
[ [],
'scope_at' => new RollingDate(RollingDate::T_FIXED_DATE, \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01')),
],
]; ];
} }
@ -57,7 +55,6 @@ final class ScopeAggregatorTest extends AbstractAggregatorTest
->select('count(acp.id)') ->select('count(acp.id)')
->from(AccompanyingPeriod::class, 'acp') ->from(AccompanyingPeriod::class, 'acp')
->join('acp.works', 'acpw') ->join('acp.works', 'acpw')
->join('acpw.referrers', 'acpwuser'),
]; ];
} }
} }

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Export;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Export\Formatter\SpreadsheetListFormatter;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\PersonBundle\Export\Export\ListAccompanyingPeriod;
use Doctrine\ORM\AbstractQuery;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class ListAccompanyingPeriodTest extends KernelTestCase
{
private ListAccompanyingPeriod $listAccompanyingPeriod;
private CenterRepositoryInterface $centerRepository;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->listAccompanyingPeriod = self::$container->get(ListAccompanyingPeriod::class);
$this->centerRepository = self::$container->get(CenterRepositoryInterface::class);
}
public function testQuery(): void
{
$centers = $this->centerRepository->findAll();
$query = $this->listAccompanyingPeriod->initiateQuery([], array_map(fn (Center $c) => ['center' => $c ], $centers), $exportOpts = ['calc_date' => new RollingDate(RollingDate::T_TODAY)]);
$query->setMaxResults(1);
$actual = $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
self::assertIsArray($actual);
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Export;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\PersonBundle\Export\Export\ListAccompanyingPeriodWork;
use Doctrine\ORM\AbstractQuery;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class ListAccompanyingPeriodWorkTest extends KernelTestCase
{
private ListAccompanyingPeriodWork $listAccompanyingPeriodWork;
private CenterRepositoryInterface $centerRepository;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->listAccompanyingPeriodWork = self::$container->get(ListAccompanyingPeriodWork::class);
$this->centerRepository = self::$container->get(CenterRepositoryInterface::class);
}
public function testQuery(): void
{
$centers = $this->centerRepository->findAll();
$query = $this->listAccompanyingPeriodWork->initiateQuery([], array_map(fn (Center $c) => ['center' => $c], $centers), ['calc_date' => new RollingDate(RollingDate::T_TODAY)]);
$query->setMaxResults(1);
self::assertIsArray($query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY));
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Export;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\PersonBundle\Export\Export\ListEvaluation;
use Doctrine\ORM\AbstractQuery;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class ListEvaluationTest extends KernelTestCase
{
private ListEvaluation $listEvaluation;
private CenterRepositoryInterface $centerRepository;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->listEvaluation = self::$container->get(ListEvaluation::class);
$this->centerRepository = self::$container->get(CenterRepositoryInterface::class);
}
public function testQuery(): void
{
$centers = $this->centerRepository->findAll();
$query = $this->listEvaluation->initiateQuery([], array_map(fn (Center $c) => ['center' => $c], $centers), ['calc_date' => new RollingDate(RollingDate::T_TODAY)]);
$query->setMaxResults(1);
self::assertIsArray($query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY));
}
}

View File

@ -51,7 +51,6 @@ final class JobFilterTest extends AbstractFilterTest
return [ return [
[ [
'job' => new ArrayCollection($jobs), 'job' => new ArrayCollection($jobs),
'job_at' => new RollingDate(RollingDate::T_FIXED_DATE, \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'))
] ]
]; ];
} }

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Tests\Export\Filter\SocialWorkFilters; namespace Chill\PersonBundle\Tests\Export\Filter\SocialWorkFilters;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Filter\SocialWorkFilters\ReferrerFilter; use Chill\PersonBundle\Export\Filter\SocialWorkFilters\ReferrerFilter;
@ -47,8 +48,13 @@ final class ReferrerFilterTest extends AbstractFilterTest
$data = []; $data = [];
foreach ($users as $u) { foreach ($users as $u) {
$data[] = [
'accepted_agents' => $u, // some saved export does not have the parameter "agent_at"
];
$data[] = [ $data[] = [
'accepted_agents' => $u, 'accepted_agents' => $u,
'agent_at' => new RollingDate(RollingDate::T_TODAY)
]; ];
} }

View File

@ -50,7 +50,6 @@ final class ScopeFilterTest extends AbstractFilterTest
return [ return [
[ [
'scope' => $scopes, 'scope' => $scopes,
'scope_at' => new RollingDate(RollingDate::T_FIXED_DATE, \DateTimeImmutable::createFromFormat('Y-m-d', '2020-01-01'))
] ]
]; ];
} }

View File

@ -518,7 +518,7 @@ class AccompanyingPeriodACLAwareRepositoryTest extends KernelTestCase
/** /**
* @param array<Scope> $scopes * @param array<Scope> $scopes
*/ */
private function buildPeriod(Person $person, array $scopes, User|null $creator, bool $confirm): AccompanyingPeriod private function buildPeriod(Person $person, array $scopes, null|User $creator, bool $confirm): AccompanyingPeriod
{ {
$period = new AccompanyingPeriod(); $period = new AccompanyingPeriod();
$period->addPerson($person); $period->addPerson($person);

Some files were not shown because too many files have changed in this diff Show More