diff --git a/CHANGELOG.md b/CHANGELOG.md
index df4dab422..b2f951479 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,8 @@ and this project adheres to
* [activity][export] DX/Feature: use of an `ActivityTypeRepositoryInterface` instead of the old-style EntityRepository
* [person][export] Fixed: some inconsistency with date filter on accompanying courses
* [person][export] Fixed: use left join for related entities in accompanying course aggregators
+* [workflow] Feature: allow user to copy and send manually the access link for the workflow
+* [workflow] Feature: show the email addresses that received an access link for the workflow
## Test releases
@@ -32,8 +34,12 @@ and this project adheres to
* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty
* [search]: Order of birthdate fields changed in advanced search to avoid confusion.
* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675)
+* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625)
* [household]: Reposition and cut button for enfant hors menage have been deleted (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/620)
* [admin]: Add crud for composition type in admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/611)
+* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625)
+
+## Test releases
### 2022-05-30
diff --git a/exports_alias_conventions.md b/exports_alias_conventions.md
index 579d004a5..62fc745d8 100644
--- a/exports_alias_conventions.md
+++ b/exports_alias_conventions.md
@@ -17,12 +17,13 @@ These are alias conventions :
| | Scope::class | acp.scopes | acpscope |
| | SocialIssue::class | acp.socialIssues | acpsocialissue |
| | User::class | acp.user | acpuser |
+| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories |
| 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 |
+| | Result::class | acpw.results | result |
| AccompanyingPeriodParticipation::class | | | acppart |
| | Person::class | acppart.person | partperson |
| AccompanyingPeriodWorkEvaluation::class | | | workeval |
@@ -47,7 +48,7 @@ These are alias conventions :
| | HouseholdComposition::class | household.compositions | composition |
| Activity::class | | | activity |
| | Person::class | activity.person | actperson |
-| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp |
+| | 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 |
@@ -59,6 +60,7 @@ These are alias conventions :
| | User::class | activity.users | actusers |
| | ActivityReason::class | activity.reasons | actreasons |
| | Center::class | actperson.center | actcenter |
+| | Person::class | activity.createdBy | actcreator |
| ActivityReason::class | | | actreasons |
| | ActivityReasonCategory::class | actreason.category | actreasoncat |
| Calendar::class | | | cal |
diff --git a/src/Bundle/ChillActivityBundle/Entity/Activity.php b/src/Bundle/ChillActivityBundle/Entity/Activity.php
index 98142149f..8fcae3e0b 100644
--- a/src/Bundle/ChillActivityBundle/Entity/Activity.php
+++ b/src/Bundle/ChillActivityBundle/Entity/Activity.php
@@ -13,6 +13,10 @@ namespace Chill\ActivityBundle\Entity;
use Chill\ActivityBundle\Validator\Constraints as ActivityValidator;
use Chill\DocStoreBundle\Entity\StoredObject;
+use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
+use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
+use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
+use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
@@ -55,8 +59,12 @@ use Symfony\Component\Validator\Constraints as Assert;
* getUserFunction="getUser",
* path="scope")
*/
-class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCentersInterface, HasScopesInterface
+class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCentersInterface, HasScopesInterface, TrackCreationInterface, TrackUpdateInterface
{
+ use TrackCreationTrait;
+
+ use TrackUpdateTrait;
+
public const SENTRECEIVED_RECEIVED = 'received';
public const SENTRECEIVED_SENT = 'sent';
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByUserAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php
similarity index 72%
rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByUserAggregator.php
rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php
index 337e38705..c09685e4e 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByUserAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php
@@ -13,20 +13,19 @@ namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
-use Chill\MainBundle\Repository\UserRepository;
+use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
-use function in_array;
-class ByUserAggregator implements AggregatorInterface
+class ByCreatorAggregator implements AggregatorInterface
{
private UserRender $userRender;
- private UserRepository $userRepository;
+ private UserRepositoryInterface $userRepository;
public function __construct(
- UserRepository $userRepository,
+ UserRepositoryInterface $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
@@ -40,12 +39,8 @@ class ByUserAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
- if (!in_array('actusers', $qb->getAllAliases(), true)) {
- $qb->leftJoin('activity.users', 'actusers');
- }
-
- $qb->addSelect('actusers.id AS users_aggregator');
- $qb->addGroupBy('users_aggregator');
+ $qb->addSelect('IDENTITY(activity.createdBy) AS creator_aggregator');
+ $qb->addGroupBy('creator_aggregator');
}
public function applyOn(): string
@@ -62,7 +57,7 @@ class ByUserAggregator implements AggregatorInterface
{
return function ($value): string {
if ('_header' === $value) {
- return 'Accepted users';
+ return 'Created by';
}
if (null === $value) {
@@ -77,11 +72,11 @@ class ByUserAggregator implements AggregatorInterface
public function getQueryKeys($data): array
{
- return ['users_aggregator'];
+ return ['creator_aggregator'];
}
public function getTitle(): string
{
- return 'Group activity by linked users';
+ return 'Group activity by creator';
}
}
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/UserScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php
similarity index 81%
rename from src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/UserScopeAggregator.php
rename to src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php
index 2c2cb50d2..2041fcbb4 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/UserScopeAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php
@@ -19,7 +19,7 @@ use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
-class UserScopeAggregator implements AggregatorInterface
+class CreatorScopeAggregator implements AggregatorInterface
{
private ScopeRepository $scopeRepository;
@@ -40,12 +40,12 @@ class UserScopeAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
- if (!in_array('actuser', $qb->getAllAliases(), true)) {
- $qb->leftJoin('activity.user', 'actuser');
+ if (!in_array('actcreator', $qb->getAllAliases(), true)) {
+ $qb->leftJoin('activity.createdBy', 'actcreator');
}
- $qb->addSelect('IDENTITY(actuser.mainScope) AS userscope_aggregator');
- $qb->addGroupBy('userscope_aggregator');
+ $qb->addSelect('IDENTITY(actcreator.mainScope) AS creatorscope_aggregator');
+ $qb->addGroupBy('creatorscope_aggregator');
}
public function applyOn(): string
@@ -79,11 +79,11 @@ class UserScopeAggregator implements AggregatorInterface
public function getQueryKeys($data): array
{
- return ['userscope_aggregator'];
+ return ['creatorscope_aggregator'];
}
public function getTitle(): string
{
- return 'Group activity by userscope';
+ return 'Group activity by creator scope';
}
}
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php
new file mode 100644
index 000000000..ccccc48a0
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php
@@ -0,0 +1,86 @@
+userRepository = $userRepository;
+ $this->userRender = $userRender;
+ }
+
+ public function addRole(): ?string
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ if (!in_array('actusers', $qb->getAllAliases(), true)) {
+ $qb->leftJoin('activity.users', 'actusers');
+ }
+
+ $qb
+ ->addSelect('actusers.id AS activity_users_aggregator')
+ ->addGroupBy('activity_users_aggregator');
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::ACTIVITY;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ // nothing to add on the form
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ return function ($value) {
+ if ('_header' === $value) {
+ return 'Activity users';
+ }
+
+ if (null === $value) {
+ return '';
+ }
+
+ $u = $this->userRepository->find($value);
+
+ return $this->userRender->renderString($u, []);
+ };
+ }
+
+ public function getQueryKeys($data)
+ {
+ return ['activity_users_aggregator'];
+ }
+
+ public function getTitle()
+ {
+ return 'Aggregate by activity users';
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php
new file mode 100644
index 000000000..a0a6a439b
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php
@@ -0,0 +1,87 @@
+userJobRepository = $userJobRepository;
+ $this->translatableStringHelper = $translatableStringHelper;
+ }
+
+ public function addRole(): ?string
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ if (!in_array('actusers', $qb->getAllAliases(), true)) {
+ $qb->leftJoin('activity.users', 'actusers');
+ }
+
+ $qb
+ ->addSelect('IDENTITY(actusers.userJob) AS activity_users_job_aggregator')
+ ->addGroupBy('activity_users_job_aggregator');
+ }
+
+ public function applyOn()
+ {
+ return Declarations::ACTIVITY;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ // nothing to add in the form
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ return function ($value): string {
+ if ('_header' === $value) {
+ return 'Users \'s job';
+ }
+
+ if (null === $value) {
+ return '';
+ }
+
+ $j = $this->userJobRepository->find($value);
+
+ return $this->translatableStringHelper->localize(
+ $j->getLabel()
+ );
+ };
+ }
+
+ public function getQueryKeys($data): array
+ {
+ return ['activity_users_job_aggregator'];
+ }
+
+ public function getTitle()
+ {
+ return 'Aggregate by users job';
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php
new file mode 100644
index 000000000..975c5df27
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php
@@ -0,0 +1,87 @@
+scopeRepository = $scopeRepository;
+ $this->translatableStringHelper = $translatableStringHelper;
+ }
+
+ public function addRole(): ?string
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ if (!in_array('actusers', $qb->getAllAliases(), true)) {
+ $qb->leftJoin('activity.users', 'actusers');
+ }
+
+ $qb
+ ->addSelect('IDENTITY(actusers.mainScope) AS activity_users_main_scope_aggregator')
+ ->addGroupBy('activity_users_main_scope_aggregator');
+ }
+
+ public function applyOn()
+ {
+ return Declarations::ACTIVITY;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ // nothing to add in the form
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ return function ($value): string {
+ if ('_header' === $value) {
+ return 'Users \'s scope';
+ }
+
+ if (null === $value) {
+ return '';
+ }
+
+ $s = $this->scopeRepository->find($value);
+
+ return $this->translatableStringHelper->localize(
+ $s->getName()
+ );
+ };
+ }
+
+ public function getQueryKeys($data): array
+ {
+ return ['activity_users_main_scope_aggregator'];
+ }
+
+ public function getTitle()
+ {
+ return 'Aggregate by users scope';
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByUserFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php
similarity index 67%
rename from src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByUserFilter.php
rename to src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php
index d276cdce8..ffabc5934 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByUserFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php
@@ -15,12 +15,10 @@ use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender;
-use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
-use function in_array;
-class ByUserFilter implements FilterInterface
+class ByCreatorFilter implements FilterInterface
{
private UserRender $userRender;
@@ -36,22 +34,11 @@ class ByUserFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
- $where = $qb->getDQLPart('where');
-
- if (!in_array('actusers', $qb->getAllAliases(), true)) {
- $qb->join('activity.users', 'actusers');
- }
-
- $clause = $qb->expr()->in('actusers.id', ':users');
-
- if ($where instanceof Andx) {
- $where->add($clause);
- } else {
- $where = $qb->expr()->andX($clause);
- }
-
- $qb->add('where', $where);
- $qb->setParameter('users', $data['accepted_users']);
+ $qb
+ ->andWhere(
+ $qb->expr()->in('activity.createdBy', ':users')
+ )
+ ->setParameter('users', $data['accepted_users']);
}
public function applyOn(): string
@@ -74,13 +61,13 @@ class ByUserFilter implements FilterInterface
$users[] = $this->userRender->renderString($u, []);
}
- return ['Filtered activity by linked users: only %users%', [
+ return ['Filtered activity by creator: only %users%', [
'%users%' => implode(', ou ', $users),
]];
}
public function getTitle(): string
{
- return 'Filter activity by linked users';
+ return 'Filter activity by creator';
}
}
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php
new file mode 100644
index 000000000..2f6cd8462
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php
@@ -0,0 +1,77 @@
+userRender = $userRender;
+ }
+
+ public function addRole(): ?string
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $orX = $qb->expr()->orX();
+
+ foreach ($data['accepted_users'] as $key => $user) {
+ $orX->add($qb->expr()->isMemberOf(':activity_users_filter_u' . $key, 'activity.users'));
+ $qb->setParameter('activity_users_filter_u' . $key, $user);
+ }
+
+ $qb->andWhere($orX);
+ }
+
+ public function applyOn()
+ {
+ return Declarations::ACTIVITY;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('accepted_users', PickUserDynamicType::class, [
+ 'multiple' => true,
+ 'label' => 'Users',
+ ]);
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ $users = [];
+
+ foreach ($data['accepted_users'] as $u) {
+ $users[] = $this->userRender->renderString($u, []);
+ }
+
+ return ['Filtered activity by users: only %users%', [
+ '%users%' => implode(', ', $users),
+ ]];
+ }
+
+ public function getTitle(): string
+ {
+ return 'Filter activity by users';
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php
index 78f3ee79d..ff4f42ec4 100644
--- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php
+++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/ByUserAggregatorTest.php
@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity;
-use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByUserAggregator;
+use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -22,7 +22,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/
final class ByUserAggregatorTest extends AbstractAggregatorTest
{
- private ByUserAggregator $aggregator;
+ private ByCreatorAggregator $aggregator;
protected function setUp(): void
{
diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php
index 1d33fedbe..1265804f9 100644
--- a/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php
+++ b/src/Bundle/ChillActivityBundle/Tests/Export/Aggregator/ACPAggregators/UserScopeAggregatorTest.php
@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity;
-use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\UserScopeAggregator;
+use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -22,7 +22,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/
final class UserScopeAggregatorTest extends AbstractAggregatorTest
{
- private UserScopeAggregator $aggregator;
+ private CreatorScopeAggregator $aggregator;
protected function setUp(): void
{
diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php
index d6e3a465e..47e76e25c 100644
--- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php
+++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ACPFilters/ByUserFilterTest.php
@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
-use Chill\ActivityBundle\Export\Filter\ACPFilters\ByUserFilter;
+use Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -23,7 +23,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/
final class ByUserFilterTest extends AbstractFilterTest
{
- private ByUserFilter $filter;
+ private ByCreatorFilter $filter;
protected function setUp(): void
{
diff --git a/src/Bundle/ChillActivityBundle/config/services/export.yaml b/src/Bundle/ChillActivityBundle/config/services/export.yaml
index bdaae8c8a..69565e29a 100644
--- a/src/Bundle/ChillActivityBundle/config/services/export.yaml
+++ b/src/Bundle/ChillActivityBundle/config/services/export.yaml
@@ -55,6 +55,10 @@ services:
tags:
- { name: chill.export_filter, alias: 'activity_date_filter' }
+ Chill\ActivityBundle\Export\Filter\ActivityUsersFilter:
+ tags:
+ - { name: chill.export_filter, alias: 'activity_users_filter' }
+
chill.activity.export.reason_filter:
class: Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter
tags:
@@ -77,10 +81,9 @@ services:
tags:
- { name: chill.export_filter, alias: 'activity_locationtype_filter' }
- chill.activity.export.byuser_filter: # TMS (M2M)
- class: Chill\ActivityBundle\Export\Filter\ACPFilters\ByUserFilter
+ Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter:
tags:
- - { name: chill.export_filter, alias: 'activity_byuser_filter' }
+ - { name: chill.export_filter, alias: 'activity_bycreator_filter' }
chill.activity.export.emergency_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter
@@ -138,10 +141,9 @@ services:
tags:
- { name: chill.export_aggregator, alias: activity_date_aggregator }
- chill.activity.export.byuser_aggregator:
- class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByUserAggregator
+ Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator:
tags:
- - { name: chill.export_aggregator, alias: activity_byuser_aggregator }
+ - { name: chill.export_aggregator, alias: activity_by_creator_aggregator }
chill.activity.export.bythirdparty_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator
@@ -158,7 +160,18 @@ services:
tags:
- { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator }
- chill.activity.export.userscope_aggregator:
- class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\UserScopeAggregator
+ Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator:
tags:
- - { name: chill.export_aggregator, alias: activity_userscope_aggregator }
+ - { name: chill.export_aggregator, alias: activity_creator_scope_aggregator }
+
+ Chill\ActivityBundle\Export\Aggregator\ActivityUsersAggregator:
+ tags:
+ - { name: chill.export_aggregator, alias: activity_users_aggregator }
+
+ Chill\ActivityBundle\Export\Aggregator\ActivityUsersScopeAggregator:
+ tags:
+ - { name: chill.export_aggregator, alias: activity_users_scope_aggregator }
+
+ Chill\ActivityBundle\Export\Aggregator\ActivityUsersJobAggregator:
+ tags:
+ - { name: chill.export_aggregator, alias: activity_users_job_aggregator }
diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20221014130554.php b/src/Bundle/ChillActivityBundle/migrations/Version20221014130554.php
new file mode 100644
index 000000000..4cbe47d26
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/migrations/Version20221014130554.php
@@ -0,0 +1,59 @@
+addSql('ALTER TABLE activity DROP updatedAt');
+ $this->addSql('ALTER TABLE activity DROP createdAt');
+ $this->addSql('ALTER TABLE activity DROP updatedBy_id');
+ $this->addSql('ALTER TABLE activity DROP createdBy_id');
+
+ // rename some indexes on activity
+ $this->addSql('ALTER INDEX idx_ac74095a217bbb47 RENAME TO idx_55026b0c217bbb47');
+ $this->addSql('ALTER INDEX idx_ac74095a682b5931 RENAME TO idx_55026b0c682b5931');
+ $this->addSql('ALTER INDEX idx_ac74095aa76ed395 RENAME TO idx_55026b0ca76ed395');
+ $this->addSql('ALTER INDEX idx_ac74095ac54c8c93 RENAME TO idx_55026b0cc54c8c93');
+ }
+
+ public function getDescription(): string
+ {
+ return 'Track update and create on activity';
+ }
+
+ public function up(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE activity ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
+ $this->addSql('ALTER TABLE activity ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
+ $this->addSql('ALTER TABLE activity ADD updatedBy_id INT DEFAULT NULL');
+ $this->addSql('ALTER TABLE activity ADD createdBy_id INT DEFAULT NULL');
+ $this->addSql('COMMENT ON COLUMN activity.updatedAt IS \'(DC2Type:datetime_immutable)\'');
+ $this->addSql('COMMENT ON COLUMN activity.createdAt IS \'(DC2Type:datetime_immutable)\'');
+ $this->addSql('ALTER TABLE activity ADD CONSTRAINT FK_AC74095A65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE activity ADD CONSTRAINT FK_AC74095A3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('CREATE INDEX IDX_AC74095A65FF1AEC ON activity (updatedBy_id)');
+ $this->addSql('CREATE INDEX IDX_AC74095A3174800F ON activity (createdBy_id)');
+
+ // rename some indexes on activity
+ $this->addSql('ALTER INDEX idx_55026b0cc54c8c93 RENAME TO IDX_AC74095AC54C8C93');
+ $this->addSql('ALTER INDEX idx_55026b0c217bbb47 RENAME TO IDX_AC74095A217BBB47');
+ $this->addSql('ALTER INDEX idx_55026b0c682b5931 RENAME TO IDX_AC74095A682B5931');
+ $this->addSql('ALTER INDEX idx_55026b0ca76ed395 RENAME TO IDX_AC74095AA76ED395');
+
+ $this->addSql('UPDATE activity SET updatedBy_id=user_id, createdBy_id=user_id, createdAt="date", updatedAt="date"');
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
index 9f8ed3d7c..3a6ff1f05 100644
--- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
@@ -252,8 +252,6 @@ Filter by activity type: Filtrer les activités par type
Filter activity by locationtype: Filtrer les activités par type de localisation
'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%"
Accepted locationtype: Types de localisation
-Filter activity by linked users: Filtrer les activités par TMS
-'Filtered activity by linked users: only %users%': "Filtré par TMS: uniquement %users%"
Accepted users: TMS(s)
Filter activity by emergency: Filtrer les activités par urgence
'Filtered activity by emergency: only %emergency%': "Filtré par urgence: uniquement si %emergency%"
@@ -269,7 +267,11 @@ Filter activity by linked socialaction: Filtrer les activités par action liée
Filter activity by linked socialissue: Filtrer les activités par problématique liée
'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%"
Filter activity by user: Filtrer les activités par créateur
-'Filtered activity by user: only %users%': "Filtré par créateur: uniquement %users%"
+Filter activity by users: Filtrer les activités par utilisateur participant
+Filter activity by creator: Filtrer les activités par créateur de l'échange
+'Filtered activity by user: only %users%': "Filtré par référent: uniquement %users%"
+'Filtered activity by users: only %users%': "Filtré par utilisateurs participants: uniquement %users%"
+'Filtered activity by creator: only %users%': "Filtré par créateur: uniquement %users%"
Creators: Créateurs
Filter activity by userscope: Filtrer les activités par service du créateur
'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%"
@@ -282,9 +284,14 @@ By reason: Par sujet
By category of reason: Par catégorie de sujet
Reason's level: Niveau du sujet
Group by reasons: Sujet d'activité
-Aggregate by activity user: Grouper les activités par utilisateur
+Aggregate by activity user: Grouper les activités par référent
+Aggregate by activity users: Grouper les activités par utilisateurs participants
Aggregate by activity type: Grouper les activités par type
Aggregate by activity reason: Grouper les activités par sujet
+Aggregate by users scope: Grouper les activités par service principal de l'utilisateur
+Users 's scope: Service principal des utilisateurs participants à l'activité
+Aggregate by users job: Grouper les activités par métier des utilisateurs participants
+Users 's job: Métier des utilisateurs participants à l'activité
Group activity by locationtype: Grouper les activités par type de localisation
Group activity by date: Grouper les activités par date
@@ -294,7 +301,8 @@ by week: Par semaine
for week: Semaine
by year: Par année
in year: En
-Group activity by linked users: Grouper les activités par TMS impliqué
+Group activity by creator: Grouper les activités par créateur de l'échange
+Group activity by creator scope: Grouper les activités par service du créateur de l'échange
Group activity by linked thirdparties: Grouper les activités par tiers impliqué
Accepted thirdparty: Tiers impliqué
Group activity by linked socialaction: Grouper les activités par action liée
diff --git a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php
index 437f0930c..8e679b228 100644
--- a/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php
+++ b/src/Bundle/ChillMainBundle/Controller/PostalCodeAPIController.php
@@ -14,7 +14,7 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\CountryRepository;
-use Chill\MainBundle\Repository\PostalCodeRepository;
+use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -30,11 +30,11 @@ final class PostalCodeAPIController extends ApiController
private PaginatorFactory $paginatorFactory;
- private PostalCodeRepository $postalCodeRepository;
+ private PostalCodeRepositoryInterface $postalCodeRepository;
public function __construct(
CountryRepository $countryRepository,
- PostalCodeRepository $postalCodeRepository,
+ PostalCodeRepositoryInterface $postalCodeRepository,
PaginatorFactory $paginatorFactory
) {
$this->countryRepository = $countryRepository;
diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
index 463a84e81..f1fe4c44d 100644
--- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
+++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php
@@ -22,6 +22,7 @@ use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Controller\UserJobApiController;
use Chill\MainBundle\Controller\UserJobController;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
+use Chill\MainBundle\Doctrine\DQL\Age;
use Chill\MainBundle\Doctrine\DQL\Extract;
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
@@ -243,6 +244,7 @@ class ChillMainExtension extends Extension implements
'datetime_functions' => [
'EXTRACT' => Extract::class,
'TO_CHAR' => ToChar::class,
+ 'AGE' => Age::class,
],
],
'hydrators' => [
diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Age.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Age.php
new file mode 100644
index 000000000..ad148fe99
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Age.php
@@ -0,0 +1,54 @@
+value2) {
+ return sprintf(
+ 'AGE(%s, %s)',
+ $this->value1->dispatch($sqlWalker),
+ $this->value2->dispatch($sqlWalker)
+ );
+ }
+
+ return sprintf(
+ 'AGE(%s)',
+ $this->value1->dispatch($sqlWalker),
+ );
+ }
+
+ public function parse(Parser $parser)
+ {
+ $parser->match(Lexer::T_IDENTIFIER);
+ $parser->match(Lexer::T_OPEN_PARENTHESIS);
+
+ $this->value1 = $parser->SimpleArithmeticExpression();
+
+ $parser->match(Lexer::T_COMMA);
+
+ $this->value2 = $parser->SimpleArithmeticExpression();
+
+ $parser->match(Lexer::T_CLOSE_PARENTHESIS);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Entity/Scope.php b/src/Bundle/ChillMainBundle/Entity/Scope.php
index b0cb9046b..df4f75e4c 100644
--- a/src/Bundle/ChillMainBundle/Entity/Scope.php
+++ b/src/Bundle/ChillMainBundle/Entity/Scope.php
@@ -28,6 +28,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
*/
class Scope
{
+ /**
+ * @ORM\Column(type="boolean", nullable=false, options={"default": true})
+ */
+ private bool $active = true;
+
/**
* @ORM\Id
* @ORM\Column(name="id", type="integer")
@@ -88,6 +93,18 @@ class Scope
return $this->roleScopes;
}
+ public function isActive(): bool
+ {
+ return $this->active;
+ }
+
+ public function setActive(bool $active): Scope
+ {
+ $this->active = $active;
+
+ return $this;
+ }
+
/**
* @param $name
*
diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php
new file mode 100644
index 000000000..ca488fe1a
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/PostalCodeToIdTransformer.php
@@ -0,0 +1,55 @@
+postalCodeRepository = $postalCodeRepository;
+ }
+
+ public function reverseTransform($value)
+ {
+ if (null === $value || trim('') === $value) {
+ return null;
+ }
+
+ if (!is_int((int) $value)) {
+ throw new TransformationFailedException('Cannot transform ' . gettype($value));
+ }
+
+ return $this->postalCodeRepository->find((int) $value);
+ }
+
+ public function transform($value)
+ {
+ if (null === $value) {
+ return null;
+ }
+
+ if ($value instanceof PostalCode) {
+ return $value->getId();
+ }
+
+ throw new TransformationFailedException('Could not reverseTransform ' . gettype($value));
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php b/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php
new file mode 100644
index 000000000..d1feacd6a
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Form/Type/PickPostalCodeType.php
@@ -0,0 +1,49 @@
+postalCodeToIdTransformer = $postalCodeToIdTransformer;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder->addViewTransformer($this->postalCodeToIdTransformer);
+ }
+
+ public function buildView(FormView $view, FormInterface $form, array $options)
+ {
+ $view->vars['uniqid'] = $view->vars['attr']['data-input-postal-code'] = uniqid('input_pick_postal_code_');
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver
+ ->setDefault('class', PostalCode::class)
+ ->setDefault('multiple', false)
+ ->setAllowedTypes('multiple', ['bool'])
+ ->setDefault('compound', false);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php
index 06f6019d7..1cba1ba58 100644
--- a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php
+++ b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php
@@ -15,9 +15,9 @@ use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
-use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
-use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
+use RuntimeException;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
@@ -26,11 +26,9 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
-use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
-use Symfony\Component\Security\Core\Security;
-use function array_map;
+use Symfony\Component\Security\Core\Security;
use function count;
/**
@@ -44,47 +42,37 @@ use function count;
*/
class ScopePickerType extends AbstractType
{
- protected AuthorizationHelperInterface $authorizationHelper;
+ private AuthorizationHelperInterface $authorizationHelper;
- /**
- * @var ScopeRepository
- */
- protected $scopeRepository;
+ private Security $security;
- protected Security $security;
-
- /**
- * @var TokenStorageInterface
- */
- protected $tokenStorage;
-
- /**
- * @var TranslatableStringHelper
- */
- protected $translatableStringHelper;
+ private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
AuthorizationHelperInterface $authorizationHelper,
- TokenStorageInterface $tokenStorage,
- ScopeRepository $scopeRepository,
Security $security,
- TranslatableStringHelper $translatableStringHelper
+ TranslatableStringHelperInterface $translatableStringHelper
) {
$this->authorizationHelper = $authorizationHelper;
- $this->tokenStorage = $tokenStorage;
- $this->scopeRepository = $scopeRepository;
$this->security = $security;
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
- $items = $this->authorizationHelper->getReachableScopes(
- $this->security->getUser(),
- $options['role'] instanceof Role ? $options['role']->getRole() : $options['role'],
- $options['center']
+ $items = array_filter(
+ $this->authorizationHelper->getReachableScopes(
+ $this->security->getUser(),
+ $options['role'] instanceof Role ? $options['role']->getRole() : $options['role'],
+ $options['center']
+ ),
+ static function (Scope $s) { return $s->isActive(); }
);
+ if (0 === count($items)) {
+ throw new RuntimeException('no scopes are reachable. This form should not be shown to user');
+ }
+
if (1 !== count($items)) {
$builder->add('scope', EntityType::class, [
'class' => Scope::class,
@@ -123,35 +111,4 @@ class ScopePickerType extends AbstractType
->setRequired('role')
->setAllowedTypes('role', ['string', Role::class]);
}
-
- /**
- * @param array|Center|Center[] $center
- * @param string $role
- *
- * @return \Doctrine\ORM\QueryBuilder
- */
- protected function buildAccessibleScopeQuery($center, $role)
- {
- $roles = $this->authorizationHelper->getParentRoles($role);
- $roles[] = $role;
- $centers = $center instanceof Center ? [$center] : $center;
-
- $qb = $this->scopeRepository->createQueryBuilder('s');
- $qb
- // jointure to center
- ->join('s.roleScopes', 'rs')
- ->join('rs.permissionsGroups', 'pg')
- ->join('pg.groupCenters', 'gc')
- // add center constraint
- ->where($qb->expr()->in('IDENTITY(gc.center)', ':centers'))
- ->setParameter('centers', array_map(static fn (Center $c) => $c->getId(), $centers))
- // role constraints
- ->andWhere($qb->expr()->in('rs.role', ':roles'))
- ->setParameter('roles', $roles)
- // user contraint
- ->andWhere(':user MEMBER OF gc.users')
- ->setParameter('user', $this->tokenStorage->getToken()->getUser());
-
- return $qb;
- }
}
diff --git a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php
index c53df01df..32c1322be 100644
--- a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php
+++ b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepository.php
@@ -18,10 +18,9 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
-use Doctrine\Persistence\ObjectRepository;
use RuntimeException;
-final class PostalCodeRepository implements ObjectRepository
+final class PostalCodeRepository implements PostalCodeRepositoryInterface
{
private EntityManagerInterface $entityManager;
@@ -29,7 +28,7 @@ final class PostalCodeRepository implements ObjectRepository
public function __construct(EntityManagerInterface $entityManager)
{
- $this->repository = $entityManager->getRepository(PostalCode::class);
+ $this->repository = $entityManager->getRepository($this->getClassName());
$this->entityManager = $entityManager;
}
@@ -51,20 +50,11 @@ final class PostalCodeRepository implements ObjectRepository
return $this->repository->find($id, $lockMode, $lockVersion);
}
- /**
- * @return PostalCode[]
- */
public function findAll(): array
{
return $this->repository->findAll();
}
- /**
- * @param mixed|null $limit
- * @param mixed|null $offset
- *
- * @return PostalCode[]
- */
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
@@ -95,7 +85,7 @@ final class PostalCodeRepository implements ObjectRepository
return $this->repository->findOneBy($criteria, $orderBy);
}
- public function getClassName()
+ public function getClassName(): string
{
return PostalCode::class;
}
diff --git a/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php
new file mode 100644
index 000000000..fe3dee195
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Repository/PostalCodeRepositoryInterface.php
@@ -0,0 +1,42 @@
+repository->findAll();
}
+ public function findAllActive(): array
+ {
+ $qb = $this->repository->createQueryBuilder('s');
+
+ $qb->where('s.active = \'TRUE\'');
+
+ return $qb->getQuery()->getResult();
+ }
+
/**
* @param mixed|null $limit
* @param mixed|null $offset
diff --git a/src/Bundle/ChillMainBundle/Repository/ScopeRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/ScopeRepositoryInterface.php
index 1093513f1..3cfb6042d 100644
--- a/src/Bundle/ChillMainBundle/Repository/ScopeRepositoryInterface.php
+++ b/src/Bundle/ChillMainBundle/Repository/ScopeRepositoryInterface.php
@@ -22,10 +22,15 @@ interface ScopeRepositoryInterface extends ObjectRepository
public function find($id, $lockMode = null, $lockVersion = null): ?Scope;
/**
- * @return Scope[]
+ * @return array|Scope[]
*/
public function findAll(): array;
+ /**
+ * @return array|Scope[]
+ */
+ public function findAllActive(): array;
+
/**
* @param null|mixed $limit
* @param null|mixed $offset
diff --git a/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php b/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php
index a137727cb..0be3e1b9c 100644
--- a/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php
+++ b/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php
@@ -14,9 +14,8 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\UserJob;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
-use Doctrine\Persistence\ObjectRepository;
-class UserJobRepository implements ObjectRepository
+class UserJobRepository implements UserJobRepositoryInterface
{
private EntityRepository $repository;
@@ -38,6 +37,11 @@ class UserJobRepository implements ObjectRepository
return $this->repository->findAll();
}
+ public function findAllActive(): array
+ {
+ return $this->repository->findBy(['active' => true]);
+ }
+
/**
* @param mixed|null $limit
* @param mixed|null $offset
@@ -49,12 +53,12 @@ class UserJobRepository implements ObjectRepository
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
- public function findOneBy(array $criteria)
+ public function findOneBy(array $criteria): ?UserJob
{
return $this->repository->findOneBy($criteria);
}
- public function getClassName()
+ public function getClassName(): string
{
return UserJob::class;
}
diff --git a/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php
new file mode 100644
index 000000000..a752f452b
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php
@@ -0,0 +1,42 @@
+getQuery()->getResult();
}
- public function getClassName()
+ public function getClassName(): string
{
return User::class;
}
diff --git a/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php
new file mode 100644
index 000000000..0201607fe
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Repository/UserRepositoryInterface.php
@@ -0,0 +1,64 @@
+',
+ components: {
+ PickPostalCode,
+ },
+ data() {
+ return {
+ city: city,
+ }
+ },
+ methods: {
+ onCitySelected(city) {
+ this.city = city;
+ input.value = city.id;
+ },
+ onCityRemoved(city) {
+ this.city = null;
+ input.value = '';
+ }
+ }
+ })
+ .use(i18n)
+ .mount(el);
+}
+
+function loadDynamicPickers(element) {
+
+ let apps = element.querySelectorAll('[data-module="pick-postal-code"]');
+
+ apps.forEach(function(el) {
+
+ const
+ uniqId = el.dataset.uniqid,
+ input = document.querySelector(`input[data-input-uniqid="${uniqId}"]`),
+ cityIdValue = input.value === '' ? null : input.value
+ ;
+
+ if (cityIdValue !== null) {
+ makeFetch('GET', `/api/1.0/main/postal-code/${cityIdValue}.json`).then(city => {
+ loadOnePicker(el, input, uniqId, city);
+ })
+ } else {
+ loadOnePicker(el, input, uniqId, null);
+ }
+ });
+}
+
+document.addEventListener('DOMContentLoaded', function(e) {
+ loadDynamicPickers(document)
+})
diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.md b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.md
new file mode 100644
index 000000000..501e96983
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.md
@@ -0,0 +1,29 @@
+# Pickpostalcode
+
+Allow to pick a postal code.
+
+In use with module `mod_pick_postal_code`, associated with `PickPostalCodeType` in php.
+
+## Usage
+
+ ``
+
+## Props
+
+* `picked`: the city picked. A javascript object (a city). Null if empty.
+* `country`: country to restraint search on picked. May be null.
+
+## Emits
+
+### `selectCity`
+
+When a city is onCitySelected.
+
+Argument: a js object, representing a city
+
+### `removeCity`
+
+When a city is removed.
+
+
+Argument: a js object, representing a city
diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.vue
new file mode 100644
index 000000000..d5e55dbbd
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/PickPostalCode.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/_PickPostalCode.scss b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/_PickPostalCode.scss
new file mode 100644
index 000000000..09f5fd539
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/_PickPostalCode.scss
@@ -0,0 +1,3 @@
+.PickPostalCode {
+
+}
diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/api.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/api.js
new file mode 100644
index 000000000..d31dcc3f4
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickPostalCode/api.js
@@ -0,0 +1,43 @@
+import {makeFetch, fetchResults} from 'ChillMainAssets/lib/api/apiMethods';
+
+/**
+ * Endpoint chill_api_single_postal_code__index
+ * method GET, get Cities Object
+ * @params {object} a country object
+ * @returns {Promise} a promise containing all Postal Code objects filtered with country
+ */
+const fetchCities = (country) => {
+ // warning: do not use fetchResults (in apiMethods): we need only a **part** of the results in the db
+ const params = new URLSearchParams({item_per_page: 100});
+
+ if (country !== null) {
+ params.append('country', country.id);
+ }
+
+ return makeFetch('GET', `/api/1.0/main/postal-code.json?${params.toString()}`).then(r => Promise.resolve(r.results));
+};
+
+/**
+ * Endpoint chill_main_postalcodeapi_search
+ * method GET, get Cities Object
+ * @params {string} search a search string
+ * @params {object} country a country object
+ * @params {AbortController} an abort controller
+ * @returns {Promise} a promise containing all Postal Code objects filtered with country and a search string
+ */
+const searchCities = (search, country, controller) => {
+ const url = '/api/1.0/main/postal-code/search.json?';
+ const params = new URLSearchParams({q: search});
+
+ if (country !== null) {
+ Object.assign('country', country.id);
+ }
+
+ return makeFetch('GET', url + params, null, {signal: controller.signal})
+ .then(result => Promise.resolve(result.results));
+};
+
+export {
+ fetchCities,
+ searchCities,
+};
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig
index 586b900aa..7ab8a4c85 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig
@@ -238,3 +238,9 @@
{% endblock %}
+
+{% block pick_postal_code_widget %}
+ {{ form_help(form)}}
+
+
+{% endblock %}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig
index bb0411371..bd9274739 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_decision.html.twig
@@ -95,6 +95,15 @@
{% endif %}
+ {% if entity_workflow.currentStep.destEmail|length > 0 %}
+ {{ 'workflow.An access key was also sent to those addresses'|trans }} :
+
+ {% for e in entity_workflow.currentStep.destEmail -%}
+ - {{ e }}
+ {%- endfor %}
+
+ {% endif %}
+
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :
@@ -103,6 +112,21 @@
{% endfor %}
{% endif %}
+
+ {% if is_granted('CHILL_MAIN_WORKFLOW_LINK_SHOW', entity_workflow) %}
+ {{ 'workflow.This link grant any user to apply a transition'|trans }} :
+
+ {% set link = absolute_url(path('chill_main_workflow_grant_access_by_key', {'id': entity_workflow.currentStep.id, 'accessKey': entity_workflow.currentStep.accessKey})) %}
+
+ {% endif %}
+
+
{% endif %}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig
index dda309da0..72aab397c 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/_history.html.twig
@@ -81,6 +81,15 @@
{% endif %}
+ {% if entity_workflow.currentStep.destEmail|length > 0 %}
+ {{ 'workflow.An access key was also sent to those addresses'|trans }} :
+
+ {% for e in entity_workflow.currentStep.destEmail -%}
+ - {{ e }}
+ {%- endfor %}
+
+ {% endif %}
+
{% if step.destUserByAccessKey|length > 0 %}
{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :
diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php
index 40dc1b13c..ffcc1bb95 100644
--- a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php
+++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php
@@ -27,6 +27,8 @@ class EntityWorkflowVoter extends Voter
public const SEE = 'CHILL_MAIN_WORKFLOW_SEE';
+ public const SHOW_ENTITY_LINK = 'CHILL_MAIN_WORKFLOW_LINK_SHOW';
+
private EntityWorkflowManager $manager;
private Security $security;
@@ -80,6 +82,19 @@ class EntityWorkflowVoter extends Voter
case self::DELETE:
return $subject->getStep() === 'initial';
+ case self::SHOW_ENTITY_LINK:
+ if ($subject->getStep() === 'initial') {
+ return false;
+ }
+
+ $currentStep = $subject->getCurrentStepChained();
+
+ if ($currentStep->isFinal()) {
+ return false;
+ }
+
+ return $currentStep->getPrevious()->getTransitionBy() === $this->security->getUser();
+
default:
throw new UnexpectedValueException("attribute {$attribute} not supported");
}
@@ -91,6 +106,7 @@ class EntityWorkflowVoter extends Voter
self::SEE,
self::CREATE,
self::DELETE,
+ self::SHOW_ENTITY_LINK,
];
}
}
diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php
index 115c364ad..ffb7e4380 100644
--- a/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php
+++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php
@@ -108,12 +108,9 @@ abstract class AbstractAggregatorTest extends KernelTestCase
abstract public function getQueryBuilders();
/**
- * Compare aliases array before and after that aggregator alter query
+ * Compare aliases array before and after that aggregator alter query.
*
* @dataProvider dataProviderAliasDidNotDisappears
- *
- * @param QueryBuilder $qb
- * @param array $data
* @return void
*/
public function testAliasDidNotDisappears(QueryBuilder $qb, array $data)
diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php
index 1525385aa..5632ee022 100644
--- a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php
+++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php
@@ -100,12 +100,9 @@ abstract class AbstractFilterTest extends KernelTestCase
abstract public function getQueryBuilders();
/**
- * Compare aliases array before and after that filter alter query
+ * Compare aliases array before and after that filter alter query.
*
* @dataProvider dataProviderAliasDidNotDisappears
- *
- * @param QueryBuilder $qb
- * @param array $data
* @return void
*/
public function testAliasDidNotDisappears(QueryBuilder $qb, array $data)
diff --git a/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php b/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php
index 7e602a7dc..57768bf6e 100644
--- a/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php
+++ b/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php
@@ -17,6 +17,7 @@ namespace Chill\MainBundle\Test;
* **Usage : ** You must set up trait with `setUpTrait` before use
* and use tearDownTrait after usage.
*
+ * @deprecated use @see{\Prophecy\PhpUnit\ProphecyTrait} instead
* @codeCoverageIgnore
*
* @deprecated use @class{Prophecy\PhpUnit\ProphecyTrait} instead
diff --git a/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/AgeTest.php b/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/AgeTest.php
new file mode 100644
index 000000000..90f6a24bf
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/AgeTest.php
@@ -0,0 +1,79 @@
+entityManager = self::$container->get(EntityManagerInterface::class);
+ }
+
+ public function generateQueries(): iterable
+ {
+ yield [
+ 'SELECT AGE(a.validFrom, a.validTo) FROM ' . Address::class . ' a',
+ [],
+ ];
+
+ yield [
+ 'SELECT AGE(:date0, :date1) FROM ' . Address::class . ' a',
+ [
+ 'date0' => new DateTimeImmutable('now'),
+ 'date1' => new DateTimeImmutable('2020-01-01'),
+ ],
+ ];
+
+ yield [
+ 'SELECT AGE(a.validFrom, :date1) FROM ' . Address::class . ' a',
+ [
+ 'date1' => new DateTimeImmutable('now'),
+ ],
+ ];
+
+ yield [
+ 'SELECT AGE(:date0, a.validFrom) FROM ' . Address::class . ' a',
+ [
+ 'date0' => new DateTimeImmutable('now'),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider generateQueries
+ */
+ public function testWorking(string $dql, array $args)
+ {
+ $dql = $this->entityManager->createQuery($dql)->setMaxResults(3);
+
+ foreach ($args as $key => $value) {
+ $dql->setParameter($key, $value);
+ }
+
+ $results = $dql->getResult();
+
+ $this->assertIsArray($results);
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Tests/Form/Type/PickPostalCodeTypeTest.php b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickPostalCodeTypeTest.php
new file mode 100644
index 000000000..126130275
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Tests/Form/Type/PickPostalCodeTypeTest.php
@@ -0,0 +1,70 @@
+factory->createBuilder(FormType::class, ['postal_code' => null]);
+ $builder->add('postal_code', PickPostalCodeType::class);
+ $form = $builder->getForm();
+
+ $form->submit(['postal_code' => '1']);
+
+ $this->assertTrue($form->isSynchronized());
+
+ $this->assertEquals(1, $form['postal_code']->getData()->getId());
+ }
+
+ protected function getExtensions()
+ {
+ $postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class);
+ $postalCodeRepository->find(Argument::any())
+ ->will(static function ($args) {
+ $postalCode = new PostalCode();
+ $reflectionClass = new ReflectionClass($postalCode);
+ $id = $reflectionClass->getProperty('id');
+ $id->setAccessible(true);
+ $id->setValue($postalCode, (int) $args[0]);
+
+ return $postalCode;
+ });
+
+ $type = new PickPostalCodeType(
+ new PostalCodeToIdTransformer(
+ $postalCodeRepository->reveal()
+ )
+ );
+
+ return [
+ new PreloadedExtension([$type], []),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Tests/Form/Type/ScopePickerTypeTest.php b/src/Bundle/ChillMainBundle/Tests/Form/Type/ScopePickerTypeTest.php
new file mode 100644
index 000000000..d64f68951
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Tests/Form/Type/ScopePickerTypeTest.php
@@ -0,0 +1,127 @@
+factory->create(ScopePickerType::class, null, [
+ 'center' => new Center(),
+ 'role' => 'ONE_SCOPE',
+ ]);
+
+ $view = $form->createView();
+
+ $this->assertContains('hidden', $view['scope']->vars['block_prefixes']);
+ }
+
+ public function testBuildThreeScopesIsSuccessful()
+ {
+ $form = $this->factory->create(ScopePickerType::class, null, [
+ 'center' => new Center(),
+ 'role' => 'THREE_SCOPE',
+ ]);
+
+ $view = $form->createView();
+
+ $this->assertContains('entity', $view['scope']->vars['block_prefixes']);
+ }
+
+ public function testBuildTwoScopesIsSuccessful()
+ {
+ $form = $this->factory->create(ScopePickerType::class, null, [
+ 'center' => new Center(),
+ 'role' => 'TWO_SCOPE',
+ ]);
+
+ $view = $form->createView();
+
+ $this->assertContains('entity', $view['scope']->vars['block_prefixes']);
+ }
+
+ protected function getExtensions()
+ {
+ $user = new User();
+ $role1Scope = 'ONE_SCOPE';
+ $role2Scope = 'TWO_SCOPE';
+ $role3Scope = 'THREE_SCOPE';
+ $scopeA = (new Scope())->setName(['fr' => 'scope a']);
+ $scopeB = (new Scope())->setName(['fr' => 'scope b']);
+ $scopeC = (new Scope())->setName(['fr' => 'scope b'])->setActive(false);
+
+ $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
+ $authorizationHelper->getReachableScopes($user, $role1Scope, Argument::any())
+ ->willReturn([$scopeA]);
+ $authorizationHelper->getReachableScopes($user, $role2Scope, Argument::any())
+ ->willReturn([$scopeA, $scopeB]);
+ $authorizationHelper->getReachableScopes($user, $role3Scope, Argument::any())
+ ->willReturn([$scopeA, $scopeB, $scopeC]);
+
+ $security = $this->prophesize(Security::class);
+ $security->getUser()->willReturn($user);
+
+ $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
+ $translatableStringHelper->localize(Argument::type('array'))->will(
+ static function ($args) { return $args[0]['fr']; }
+ );
+
+ $type = new ScopePickerType(
+ $authorizationHelper->reveal(),
+ $security->reveal(),
+ $translatableStringHelper->reveal()
+ );
+
+ // add the mocks for creating EntityType
+ $entityManager = DoctrineTestHelper::createTestEntityManager();
+ $em = $this->prophesize(EntityManagerInterface::class);
+ $em->getClassMetadata(Scope::class)->willReturn($entityManager->getClassMetadata(Scope::class));
+ $em->contains(Argument::type(Scope::class))->willReturn(true);
+ $em->initializeObject(Argument::type(Scope::class))->will(static fn ($o) => $o);
+ $emRevealed = $em->reveal();
+ $managerRegistry = $this->prophesize(ManagerRegistry::class);
+ $managerRegistry->getManager(Argument::any())->willReturn($emRevealed);
+ $managerRegistry->getManagerForClass(Scope::class)->willReturn($emRevealed);
+
+ $entityType = $this->prophesize(EntityType::class);
+ $entityType->getParent()->willReturn(ChoiceType::class);
+
+ return [
+ new PreloadedExtension([$type], []),
+ new DoctrineOrmExtension($managerRegistry->reveal()),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js
index 628f04ba5..73f539739 100644
--- a/src/Bundle/ChillMainBundle/chill.webpack.config.js
+++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js
@@ -70,6 +70,7 @@ module.exports = function(encore, entries)
encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js');
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
encore.addEntry('mod_wopi_link', __dirname + '/Resources/public/module/wopi-link/index.js');
+ encore.addEntry('mod_pick_postal_code', __dirname + '/Resources/public/module/pick-postal-code/index.js');
// Vue entrypoints
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
diff --git a/src/Bundle/ChillMainBundle/migrations/Version20221010142417.php b/src/Bundle/ChillMainBundle/migrations/Version20221010142417.php
new file mode 100644
index 000000000..117dd4d22
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/migrations/Version20221010142417.php
@@ -0,0 +1,33 @@
+addSql('ALTER TABLE scopes DROP active');
+ }
+
+ public function getDescription(): string
+ {
+ return 'Allow a scope to be desactivated';
+ }
+
+ public function up(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE scopes ADD active BOOLEAN DEFAULT true NOT NULL');
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
index 76dc27b39..d14b4a7c1 100644
--- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml
@@ -455,7 +455,6 @@ workflow:
Delete workflow: Supprimer le workflow
Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ?
You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow.
- Those users are also granted to apply a transition by using an access key: Ces utilisateurs peuvent également valider cette étape, grâce à un lien d'accès
dest by email: Liens d'autorisation par email
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Ce lien d'accès permettra à l'utilisateur de valider cette étape.
Add an email: Ajouter une adresse email
@@ -467,6 +466,11 @@ workflow:
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
+ An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces addresses
+ Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtennu l'accès grâce au lien reçu par email
+ Access link copied: Lien d'accès copié
+ This link grant any user to apply a transition: Le lien d'accès suivant permet d'appliquer une transition
+ The workflow may be accssed through this link: Une transition peut être appliquée sur ce workflow grâce au lien d'accès suivant
Subscribe final: Recevoir une notification à l'étape finale
diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php
index 9ab72bb2c..8d7249928 100644
--- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php
+++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php
@@ -62,6 +62,7 @@ class UserRefEventSubscriber implements EventSubscriberInterface
&& $period->getUser() !== $this->security->getUser()
&& null !== $period->getUser()
&& $period->getStep() !== AccompanyingPeriod::STEP_DRAFT
+ && !$period->isPreventUserIsChangedNotification()
) {
$this->generateNotificationToUser($period);
}
diff --git a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php
index 181677709..91b834891 100644
--- a/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php
+++ b/src/Bundle/ChillPersonBundle/Controller/ReassignAccompanyingPeriodController.php
@@ -11,7 +11,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
+use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Entity\User;
+use Chill\MainBundle\Form\Type\PickPostalCodeType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\UserRepository;
@@ -92,12 +94,14 @@ class ReassignAccompanyingPeriodController extends AbstractController
$form->handleRequest($request);
$userFrom = $form['user']->getData();
+ $postalCodes = $form['postal_code']->getData() instanceof PostalCode ? [$form['postal_code']->getData()] : [];
$total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod($userFrom);
$paginator = $this->paginatorFactory->create($total);
$periods = $this->accompanyingPeriodACLAwareRepository
- ->findByUserOpenedAccompanyingPeriod(
+ ->findByUserAndPostalCodesOpenedAccompanyingPeriod(
$userFrom,
+ $postalCodes,
['openingDate' => 'ASC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
@@ -123,7 +127,7 @@ class ReassignAccompanyingPeriodController extends AbstractController
$period = $this->courseRepository->find($periodId);
if ($period->getUser() === $userFrom) {
- $period->setUser($userTo);
+ $period->setUser($userTo, true);
}
}
@@ -148,7 +152,9 @@ class ReassignAccompanyingPeriodController extends AbstractController
{
$data = [
'user' => null,
+ 'postal_code' => null,
];
+
$builder = $this->formFactory->createBuilder(FormType::class, $data, [
'method' => 'get', 'csrf_protection' => false, ]);
@@ -158,12 +164,17 @@ class ReassignAccompanyingPeriodController extends AbstractController
'label' => 'reassign.Current user',
'required' => false,
'help' => 'reassign.Choose a user and click on "Filter" to apply',
+ ])
+ ->add('postal_code', PickPostalCodeType::class, [
+ 'label' => 'reassign.Filter by postal code',
+ 'required' => false,
+ 'help' => 'reassign.Filter course which are located inside a postal code',
]);
return $builder->getForm();
}
- private function buildReassignForm(array $periodIds, ?User $userFrom): FormInterface
+ private function buildReassignForm(array $periodIds, ?User $userFrom = null): FormInterface
{
$defaultData = [
'userFrom' => $userFrom,
diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php
index 41ca55d0e..bd12ec8c2 100644
--- a/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php
+++ b/src/Bundle/ChillPersonBundle/Controller/SocialWorkEvaluationApiController.php
@@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection;
+use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -39,9 +40,11 @@ class SocialWorkEvaluationApiController extends AbstractController
*/
public function listEvaluationBySocialAction(SocialAction $action): Response
{
- $pagination = $this->paginatorFactory->create($action->getEvaluations()->count());
+ $evaluations = $action->getEvaluations()->filter(static fn (Evaluation $eval) => $eval->isActive());
- $evaluations = $action->getEvaluations()->slice(
+ $pagination = $this->paginatorFactory->create($evaluations->count());
+
+ $evaluations = $evaluations->slice(
$pagination->getCurrentPageFirstItemNumber(),
$pagination->getItemsPerPage()
);
diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
index 168be56b9..d3310fe15 100644
--- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
+++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
@@ -22,6 +22,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodLocationHistory;
+use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment;
@@ -265,6 +266,8 @@ class AccompanyingPeriod implements
*/
private ?Comment $pinnedComment = null;
+ private bool $preventUserIsChangedNotification = false;
+
/**
* @ORM\Column(type="text")
* @Groups({"read", "write"})
@@ -334,6 +337,12 @@ class AccompanyingPeriod implements
*/
private string $step = self::STEP_DRAFT;
+ /**
+ * @ORM\OneToMany(targetEntity=AccompanyingPeriodStepHistory::class,
+ * mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true)
+ */
+ private Collection $stepHistories;
+
/**
* @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
*/
@@ -388,7 +397,6 @@ class AccompanyingPeriod implements
*/
public function __construct(?DateTime $dateOpening = null)
{
- $this->setOpeningDate($dateOpening ?? new DateTime('now'));
$this->participations = new ArrayCollection();
$this->scopes = new ArrayCollection();
$this->socialIssues = new ArrayCollection();
@@ -397,6 +405,8 @@ class AccompanyingPeriod implements
$this->resources = new ArrayCollection();
$this->userHistories = new ArrayCollection();
$this->locationHistories = new ArrayCollection();
+ $this->stepHistories = new ArrayCollection();
+ $this->setOpeningDate($dateOpening ?? new DateTime('now'));
}
/**
@@ -964,6 +974,11 @@ class AccompanyingPeriod implements
return $this->step;
}
+ public function getStepHistories(): Collection
+ {
+ return $this->stepHistories;
+ }
+
public function getUser(): ?User
{
return $this->user;
@@ -1048,6 +1063,11 @@ class AccompanyingPeriod implements
return false;
}
+ public function isPreventUserIsChangedNotification(): bool
+ {
+ return $this->preventUserIsChangedNotification;
+ }
+
public function isRequestorAnonymous(): bool
{
return $this->requestorAnonymous;
@@ -1227,7 +1247,11 @@ class AccompanyingPeriod implements
*/
public function setOpeningDate($openingDate)
{
- $this->openingDate = $openingDate;
+ if ($this->openingDate !== $openingDate) {
+ $this->openingDate = $openingDate;
+
+ $this->ensureStepContinuity();
+ }
return $this;
}
@@ -1326,6 +1350,14 @@ class AccompanyingPeriod implements
$this->bootPeriod();
}
+ if (self::STEP_DRAFT !== $this->step && $previous !== $step) {
+ // we create a new history
+ $history = new AccompanyingPeriodStepHistory();
+ $history->setStep($this->step)->setStartDate(new DateTimeImmutable('now'));
+
+ $this->addStepHistory($history);
+ }
+
return $this;
}
@@ -1343,11 +1375,12 @@ class AccompanyingPeriod implements
return $this;
}
- public function setUser(?User $user): self
+ public function setUser(?User $user, bool $preventNotification = false): self
{
if ($this->user !== $user) {
$this->userPrevious = $this->user;
$this->userIsChanged = true;
+ $this->preventUserIsChangedNotification = $preventNotification;
foreach ($this->userHistories as $history) {
if (null === $history->getEndDate()) {
@@ -1365,6 +1398,17 @@ class AccompanyingPeriod implements
return $this;
}
+ private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory): self
+ {
+ if (!$this->stepHistories->contains($stepHistory)) {
+ $this->stepHistories[] = $stepHistory;
+ $stepHistory->setPeriod($this);
+ $this->ensureStepContinuity();
+ }
+
+ return $this;
+ }
+
private function bootPeriod(): void
{
// first location history
@@ -1376,6 +1420,43 @@ class AccompanyingPeriod implements
$this->addLocationHistory($locationHistory);
}
+ private function ensureStepContinuity(): void
+ {
+ // ensure continuity of histories
+ $criteria = new Criteria();
+ $criteria->orderBy(['startDate' => Criteria::ASC, 'id' => Criteria::ASC]);
+
+ /** @var Iterator $steps */
+ $steps = $this->getStepHistories()->matching($criteria)->getIterator();
+ $steps->rewind();
+
+ // we set the start date of the first step as the opening date, only if it is
+ // not greater than the end date
+ /** @var AccompanyingPeriodStepHistory $current */
+ $current = $steps->current();
+
+ if (null === $current) {
+ return;
+ }
+
+ if ($this->getOpeningDate()->format('Y-m-d') !== $current->getStartDate()->format('Y-m-d')
+ && ($this->getOpeningDate() <= $current->getEndDate() || null === $current->getEndDate())) {
+ $current->setStartDate(DateTimeImmutable::createFromMutable($this->getOpeningDate()));
+ }
+
+ // then we set all the end date to the start date of the next one
+ do {
+ /** @var AccompanyingPeriodStepHistory $current */
+ $current = $steps->current();
+ $steps->next();
+
+ if ($steps->valid()) {
+ $next = $steps->current();
+ $current->setEndDate($next->getStartDate());
+ }
+ } while ($steps->valid());
+ }
+
private function setRequestorPerson(?Person $requestorPerson = null): self
{
$this->requestorPerson = $requestorPerson;
diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodStepHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodStepHistory.php
new file mode 100644
index 000000000..f9baffa35
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodStepHistory.php
@@ -0,0 +1,115 @@
+endDate;
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getPeriod(): AccompanyingPeriod
+ {
+ return $this->period;
+ }
+
+ public function getStartDate(): ?DateTimeImmutable
+ {
+ return $this->startDate;
+ }
+
+ public function getStep(): string
+ {
+ return $this->step;
+ }
+
+ public function setEndDate(?DateTimeImmutable $endDate): self
+ {
+ $this->endDate = $endDate;
+
+ return $this;
+ }
+
+ /**
+ * @internal use AccompanyingPeriod::addLocationHistory
+ */
+ public function setPeriod(AccompanyingPeriod $period): self
+ {
+ $this->period = $period;
+
+ return $this;
+ }
+
+ public function setStartDate(?DateTimeImmutable $startDate): self
+ {
+ $this->startDate = $startDate;
+
+ return $this;
+ }
+
+ public function setStep(string $step): AccompanyingPeriodStepHistory
+ {
+ $this->step = $step;
+
+ return $this;
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php
index 9054aba5f..592c833d3 100644
--- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php
+++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php
@@ -36,7 +36,7 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
* inversedBy="comments")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
- private ?AccompanyingPeriod $accompanyingPeriod;
+ private ?AccompanyingPeriod $accompanyingPeriod = null;
/**
* @ORM\Column(type="text")
diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php
index 5c05fe683..08dc0f425 100644
--- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php
+++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php
@@ -26,6 +26,11 @@ use Symfony\Component\Serializer\Annotation as Serializer;
*/
class Evaluation
{
+ /**
+ * @ORM\Column(type="boolean", nullable=false, options={"default": true})
+ */
+ private bool $active = true;
+
/**
* @ORM\Column(type="dateinterval", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
@@ -72,6 +77,9 @@ class Evaluation
$this->socialActions = new ArrayCollection();
}
+ /**
+ * @internal do use @see{SocialAction::addEvaluation}
+ */
public function addSocialAction(SocialAction $socialAction): self
{
if (!$this->socialActions->contains($socialAction)) {
@@ -111,6 +119,16 @@ class Evaluation
return $this->url;
}
+ public function isActive(): bool
+ {
+ return $this->active;
+ }
+
+ /**
+ * @return $this
+ *
+ * @internal do use @see{SocialAction::removeEvaluation}
+ */
public function removeSocialAction(SocialAction $socialAction): self
{
if ($this->socialActions->contains($socialAction)) {
@@ -120,6 +138,13 @@ class Evaluation
return $this;
}
+ public function setActive(bool $active): Evaluation
+ {
+ $this->active = $active;
+
+ return $this;
+ }
+
public function setDelay(?DateInterval $delay): self
{
$this->delay = $delay;
diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php
index bb393a3b5..08fc4bf9e 100644
--- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php
+++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php
@@ -112,6 +112,7 @@ class SocialAction
{
if (!$this->evaluations->contains($evaluation)) {
$this->evaluations[] = $evaluation;
+ $evaluation->addSocialAction($this);
}
return $this;
@@ -332,6 +333,7 @@ class SocialAction
public function removeEvaluation(Evaluation $evaluation): self
{
$this->evaluations->removeElement($evaluation);
+ $evaluation->removeSocialAction($this);
return $this;
}
diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php
index 2f8686da0..0dc66e81e 100644
--- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php
+++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php
@@ -13,12 +13,21 @@ namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
+use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder;
+use LogicException;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class DurationAggregator implements AggregatorInterface
{
+ private const CHOICES = [
+ 'month',
+ 'week',
+ 'day',
+ ];
+
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
@@ -33,19 +42,31 @@ final class DurationAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
- $qb->addSelect(
- '
- (acp.closingDate - acp.openingDate +15) *12/365
- AS duration_aggregator'
- );
+ switch ($data['precision']) {
+ case 'day':
+ $qb->addSelect('(COALESCE(acp.closingDate, :now) - acp.openingDate) AS duration_aggregator');
- // TODO Pour avoir un interval plus précis (nécessaire ?):
- // adapter la fonction extract pour pouvoir l'utiliser avec des intervals: extract(month from interval)
- // et ajouter une fonction custom qui calcule plus précisément les intervals, comme doctrineum/date-interval
- // https://packagist.org/packages/doctrineum/date-interval#3.1.0 (mais composer fait un conflit de dépendance)
+ break;
- $qb->addGroupBy('duration_aggregator');
- $qb->addOrderBy('duration_aggregator');
+ case 'week':
+ $qb->addSelect('(COALESCE(acp.closingDate, :now) - acp.openingDate) / 7 AS duration_aggregator');
+
+ break;
+
+ case 'month':
+ $qb->addSelect('(EXTRACT (MONTH FROM AGE(COALESCE(acp.closingDate, :now), acp.openingDate)) * 12 +
+ EXTRACT (MONTH FROM AGE(COALESCE(acp.closingDate, :now), acp.openingDate))) AS duration_aggregator');
+
+ break;
+
+ default:
+ throw new LogicException('precision not supported: ' . $data['precision']);
+ }
+
+ $qb
+ ->setParameter('now', new DateTimeImmutable('now'))
+ ->addGroupBy('duration_aggregator')
+ ->addOrderBy('duration_aggregator');
}
public function applyOn(): string
@@ -55,25 +76,27 @@ final class DurationAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
- // no form
+ $builder->add('precision', ChoiceType::class, [
+ 'choices' => array_combine(self::CHOICES, self::CHOICES),
+ 'label' => 'export.aggregator.course.duration.Precision',
+ 'choice_label' => static fn (string $c) => 'export.aggregator.course.duration.' . $c,
+ 'multiple' => false,
+ 'expanded' => true,
+ ]);
}
public function getLabels($key, array $values, $data)
{
- return function ($value): ?string {
+ return static function ($value) use ($data) {
if ('_header' === $value) {
- return 'Rounded month duration';
+ return 'export.aggregator.course.duration.' . $data['precision'];
}
if (null === $value) {
- return $this->translator->trans('current duration'); // when closingDate is null
+ return 0;
}
- if (0 === $value) {
- return $this->translator->trans('duration 0 month');
- }
-
- return $value . $this->translator->trans(' months');
+ return $value;
};
}
diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php
index 1498fc3a1..327b76a65 100644
--- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php
+++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php
@@ -12,15 +12,20 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
+use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Export\Declarations;
+use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
-use function in_array;
final class ReferrerAggregator implements AggregatorInterface
{
+ private const A = 'acp_ref_agg_uhistory';
+
+ private const P = 'acp_ref_agg_date';
+
private UserRender $userRender;
private UserRepository $userRepository;
@@ -40,12 +45,23 @@ final class ReferrerAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
- if (!in_array('acpuser', $qb->getAllAliases(), true)) {
- $qb->leftJoin('acp.user', 'acpuser');
- }
-
- $qb->addSelect('acpuser.id AS referrer_aggregator');
- $qb->addGroupBy('referrer_aggregator');
+ $qb
+ ->addSelect('IDENTITY(' . self::A . '.user) AS referrer_aggregator')
+ ->addGroupBy('referrer_aggregator')
+ ->leftJoin('acp.userHistories', self::A)
+ ->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->isNull(self::A),
+ $qb->expr()->andX(
+ $qb->expr()->lte(self::A . '.startDate', ':' . self::P),
+ $qb->expr()->orX(
+ $qb->expr()->isNull(self::A . '.endDate'),
+ $qb->expr()->gt(self::A . '.endDate', ':' . self::P)
+ )
+ )
+ )
+ )
+ ->setParameter(self::P, $data['date_calc']);
}
public function applyOn(): string
@@ -55,7 +71,13 @@ final class ReferrerAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
- // no form
+ $builder
+ ->add('date_calc', ChillDateType::class, [
+ 'input' => 'datetime_immutable',
+ 'data' => new DateTimeImmutable('now'),
+ 'label' => 'export.aggregator.course.by_referrer.Computation date for referrer',
+ 'required' => true,
+ ]);
}
public function getLabels($key, array $values, $data)
diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php
index 79fed3160..fc2fcc7b2 100644
--- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php
+++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php
@@ -12,20 +12,21 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
-//use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
-use Doctrine\DBAL\Types\Types;
-use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
-use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
+use function in_array;
-final class StepAggregator implements AggregatorInterface //, FilterInterface
+final class StepAggregator implements AggregatorInterface
{
+ private const A = 'acpstephistories';
+
+ private const P = 'acp_step_agg_date';
+
private TranslatorInterface $translator;
public function __construct(
@@ -41,30 +42,26 @@ final class StepAggregator implements AggregatorInterface //, FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
- $qb->addSelect('acp.step AS step_aggregator');
- $qb->addGroupBy('step_aggregator');
-
- /*
- // add date in where clause
- $where = $qb->getDQLPart('where');
-
- $clause = $qb->expr()->andX(
- $qb->expr()->lte('acp.openingDate', ':ondate'),
- $qb->expr()->orX(
- $qb->expr()->gt('acp.closingDate', ':ondate'),
- $qb->expr()->isNull('acp.closingDate')
- )
- );
-
- if ($where instanceof Andx) {
- $where->add($clause);
- } else {
- $where = $qb->expr()->andX($clause);
+ if (!in_array(self::A, $qb->getAllAliases(), true)) {
+ $qb->leftJoin('acp.stepHistories', self::A);
}
- $qb->add('where', $where);
- $qb->setParameter('ondate', $data['on_date'], Types::DATE_MUTABLE);
- */
+ $qb
+ ->addSelect(self::A . '.step AS step_aggregator')
+ ->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->isNull(self::A . '.step'),
+ $qb->expr()->andX(
+ $qb->expr()->lte(self::A . '.startDate', ':' . self::P),
+ $qb->expr()->orX(
+ $qb->expr()->isNull(self::A . '.endDate'),
+ $qb->expr()->lt(self::A . '.endDate', ':' . self::P)
+ )
+ )
+ )
+ )
+ ->setParameter(self::P, $data['on_date'])
+ ->addGroupBy('step_aggregator');
}
public function applyOn(): string
@@ -95,8 +92,11 @@ final class StepAggregator implements AggregatorInterface //, FilterInterface
case '_header':
return 'Step';
+ case null:
+ return '';
+
default:
- throw new LogicException(sprintf('The value %s is not valid', $value));
+ return $value;
}
};
}
diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php
index 1ba9d9f06..395070a8a 100644
--- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php
+++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php
@@ -23,6 +23,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
+use function in_array;
final class CountryOfBirthAggregator implements AggregatorInterface, ExportElementValidatedInterface
{
@@ -83,7 +84,9 @@ final class CountryOfBirthAggregator implements AggregatorInterface, ExportEleme
. ' is not known.');
}
- $qb->leftJoin('person.countryOfBirth', 'countryOfBirth');
+ if (!in_array('countryOfBirth', $qb->getAllAliases(), true)) {
+ $qb->leftJoin('person.countryOfBirth', 'countryOfBirth');
+ }
// add group by
$qb->addGroupBy('country_of_birth_aggregator');
diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php
index 7e5a01f3d..8c8744dfd 100644
--- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php
+++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php
@@ -99,6 +99,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
$qb = $this->repository->createQueryBuilder('acp');
$qb
+ ->andWhere('acp.step != :count_acp_step')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
@@ -107,6 +108,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
'
)
)
+ ->setParameter('count_acp_step', AccompanyingPeriod::STEP_DRAFT)
->setParameter('authorized_centers', $centers);
$qb->select('COUNT(DISTINCT acp.id) AS export_result');
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php
index 19f1a6090..52f875a5e 100644
--- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php
+++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php
@@ -50,9 +50,6 @@ class AdministrativeLocationFilter implements FilterInterface
{
$builder->add('accepted_locations', PickUserLocationType::class, [
'label' => 'Accepted locations',
- 'label_attr' => [
- //'class' => 'd-none'
- ],
'multiple' => true,
]);
}
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserJobFilter.php
deleted file mode 100644
index 9a522449c..000000000
--- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserJobFilter.php
+++ /dev/null
@@ -1,90 +0,0 @@
-translatableStringHelper = $translatableStringHelper;
- $this->security = $security;
- }
-
- public function addRole(): ?string
- {
- return null;
- }
-
- public function alterQuery(QueryBuilder $qb, $data)
- {
- $where = $qb->getDQLPart('where');
- $clause = $qb->expr()->eq('acp.job', ':userjob');
-
- if ($where instanceof Andx) {
- $where->add($clause);
- } else {
- $where = $qb->expr()->andX($clause);
- }
-
- $qb->add('where', $where);
- $qb->setParameter('userjob', $this->getUserJob());
- }
-
- public function applyOn()
- {
- return Declarations::ACP_TYPE;
- }
-
- public function buildForm(FormBuilderInterface $builder)
- {
- }
-
- public function describeAction($data, $format = 'string')
- {
- return [
- 'Filtered by user job: only %job%', [
- '%job%' => $this->translatableStringHelper->localize(
- $this->getUserJob()->getLabel()
- ),
- ],
- ];
- }
-
- public function getTitle()
- {
- return 'Filter by user job';
- }
-
- private function getUserJob(): UserJob
- {
- /** @var User $user */
- $user = $this->security->getUser();
-
- return $user->getUserJob();
- }
-}
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserScopeFilter.php
deleted file mode 100644
index 3accacf2c..000000000
--- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CurrentUserScopeFilter.php
+++ /dev/null
@@ -1,95 +0,0 @@
-translatableStringHelper = $translatableStringHelper;
- $this->security = $security;
- }
-
- public function addRole(): ?string
- {
- return null;
- }
-
- public function alterQuery(QueryBuilder $qb, $data)
- {
- if (!in_array('acpscope', $qb->getAllAliases(), true)) {
- $qb->join('acp.scopes', 'acpscope');
- }
-
- $where = $qb->getDQLPart('where');
- $clause = $qb->expr()->eq('acpscope.id', ':userscope');
-
- if ($where instanceof Andx) {
- $where->add($clause);
- } else {
- $where = $qb->expr()->andX($clause);
- }
-
- $qb->add('where', $where);
- $qb->setParameter('userscope', $this->getUserMainScope());
- }
-
- public function applyOn()
- {
- return Declarations::ACP_TYPE;
- }
-
- public function buildForm(FormBuilderInterface $builder)
- {
- }
-
- public function describeAction($data, $format = 'string')
- {
- return [
- 'Filtered by user main scope: only %scope%', [
- '%scope%' => $this->translatableStringHelper->localize(
- $this->getUserMainScope()->getName()
- ),
- ],
- ];
- }
-
- public function getTitle()
- {
- return 'Filter by user scope';
- }
-
- private function getUserMainScope(): Scope
- {
- /** @var User $user */
- $user = $this->security->getUser();
-
- return $user->getMainScope();
- }
-}
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php
index ec4a5abe7..996ff36e3 100644
--- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php
+++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php
@@ -15,7 +15,7 @@ use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Export\Declarations;
-use Doctrine\ORM\Query\Expr\Andx;
+use Chill\PersonBundle\Repository\SocialWork\EvaluationRepositoryInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -23,11 +23,15 @@ use function in_array;
class EvaluationFilter implements FilterInterface
{
+ private EvaluationRepositoryInterface $evaluationRepository;
+
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
+ EvaluationRepositoryInterface $evaluationRepository,
TranslatableStringHelper $translatableStringHelper
) {
+ $this->evaluationRepository = $evaluationRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
@@ -50,16 +54,8 @@ class EvaluationFilter implements FilterInterface
$qb->join('workeval.evaluation', 'eval');
}
- $where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('eval.id', ':evaluations');
-
- if ($where instanceof Andx) {
- $where->add($clause);
- } else {
- $where = $qb->expr()->andX($clause);
- }
-
- $qb->add('where', $where);
+ $qb->andWhere($clause);
$qb->setParameter('evaluations', $data['accepted_evaluations']);
}
@@ -72,11 +68,13 @@ class EvaluationFilter implements FilterInterface
{
$builder->add('accepted_evaluations', EntityType::class, [
'class' => Evaluation::class,
+ 'choices' => $this->evaluationRepository->findAllActive(),
'choice_label' => function (Evaluation $ev) {
return $this->translatableStringHelper->localize($ev->getTitle());
},
'multiple' => true,
- 'expanded' => true,
+ 'expanded' => false,
+ 'attr' => ['class' => 'select2'],
]);
}
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php
index 53ab10564..8781f6cad 100644
--- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php
+++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php
@@ -11,17 +11,23 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
-use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
+use Chill\MainBundle\Form\Type\ChillDateType;
+use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Export\Declarations;
-use Doctrine\ORM\Query\Expr\Andx;
+use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder;
-use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class ReferrerFilter implements FilterInterface
{
+ private const A = 'acp_referrer_filter_uhistory';
+
+ private const P = 'acp_referrer_filter_date';
+
+ private const PU = 'acp_referrer_filter_users';
+
private UserRender $userRender;
public function __construct(UserRender $userRender)
@@ -36,17 +42,22 @@ class ReferrerFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
- $where = $qb->getDQLPart('where');
- $clause = $qb->expr()->in('acp.user', ':referrers');
-
- if ($where instanceof Andx) {
- $where->add($clause);
- } else {
- $where = $qb->expr()->andX($clause);
- }
-
- $qb->add('where', $where);
- $qb->setParameter('referrers', $data['accepted_referrers']);
+ $qb
+ ->join('acp.userHistories', self::A)
+ ->andWhere(
+ $qb->expr()->andX(
+ $qb->expr()->lte(self::A . '.startDate', ':' . self::P),
+ $qb->expr()->orX(
+ $qb->expr()->isNull(self::A . '.endDate'),
+ $qb->expr()->gt(self::A . '.endDate', ':' . self::P)
+ )
+ )
+ )
+ ->andWhere(
+ $qb->expr()->in(self::A . '.user', ':' . self::PU)
+ )
+ ->setParameter(self::PU, $data['accepted_referrers'])
+ ->setParameter(self::P, $data['date_calc']);
}
public function applyOn(): string
@@ -56,14 +67,16 @@ class ReferrerFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
- $builder->add('accepted_referrers', EntityType::class, [
- 'class' => User::class,
- 'choice_label' => function (User $u) {
- return $this->userRender->renderString($u, []);
- },
- 'multiple' => true,
- 'expanded' => true,
- ]);
+ $builder
+ ->add('accepted_referrers', PickUserDynamicType::class, [
+ 'multiple' => true,
+ ])
+ ->add('date_calc', ChillDateType::class, [
+ 'input' => 'datetime_immutable',
+ 'data' => new DateTimeImmutable('now'),
+ 'label' => 'export.filter.course.by_referrer.Computation date for referrer',
+ 'required' => true,
+ ]);
}
public function describeAction($data, $format = 'string'): array
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php
new file mode 100644
index 000000000..a18442c2a
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php
@@ -0,0 +1,130 @@
+security = $security;
+ $this->translatableStringHelper = $translatableStringHelper;
+ $this->userJobRepository = $userJobRepository;
+ }
+
+ public function addRole(): ?string
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $qb
+ ->join('acp.userHistories', self::A)
+ ->andWhere(
+ $qb->expr()->andX(
+ $qb->expr()->lte(self::A . '.startDate', ':' . self::P),
+ $qb->expr()->orX(
+ $qb->expr()->isNull(self::A . '.endDate'),
+ $qb->expr()->gt(self::A . '.endDate', ':' . self::P)
+ )
+ )
+ )
+ ->setParameter(self::P, $data['date_calc'])
+ ->join(self::A . '.user', self::AU)
+ ->andWhere(
+ $qb->expr()->in(self::AU . '.userJob', ':' . self::PJ)
+ )
+ ->setParameter(self::PJ, $data['jobs']);
+ }
+
+ public function applyOn()
+ {
+ return Declarations::ACP_TYPE;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder
+ ->add('jobs', EntityType::class, [
+ 'class' => UserJob::class,
+ 'choices' => $this->userJobRepository->findAllActive(),
+ 'multiple' => true,
+ 'expanded' => true,
+ 'choice_label' => fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
+ 'label' => 'Job',
+ ])
+ ->add('date_calc', ChillDateType::class, [
+ 'input' => 'datetime_immutable',
+ 'data' => new DateTimeImmutable('now'),
+ 'label' => 'export.filter.course.by_user_scope.Computation date for referrer',
+ 'required' => true,
+ ]);
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ return [
+ 'Filtered by user job: only %job%', [
+ '%job%' => implode(
+ ', ',
+ array_map(
+ fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
+ $data['jobs']->toArray()
+ )
+ ),
+ ],
+ ];
+ }
+
+ public function getTitle()
+ {
+ return 'Filter by user job';
+ }
+
+ private function getUserJob(): UserJob
+ {
+ /** @var User $user */
+ $user = $this->security->getUser();
+
+ return $user->getUserJob();
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php
new file mode 100644
index 000000000..48a97f9f7
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php
@@ -0,0 +1,129 @@
+scopeRepository = $scopeRepository;
+ $this->security = $security;
+ $this->translatableStringHelper = $translatableStringHelper;
+ }
+
+ public function addRole(): ?string
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $qb
+ ->join('acp.userHistories', self::A)
+ ->andWhere(
+ $qb->expr()->andX(
+ $qb->expr()->lte(self::A . '.startDate', ':' . self::P),
+ $qb->expr()->orX(
+ $qb->expr()->isNull(self::A . '.endDate'),
+ $qb->expr()->gt(self::A . '.endDate', ':' . self::P)
+ )
+ )
+ )
+ ->setParameter(self::P, $data['date_calc'])
+ ->join(self::A . '.user', self::AU)
+ ->andWhere(
+ $qb->expr()->in(self::AU . '.mainScope', ':' . self::PS)
+ )
+ ->setParameter(self::PS, $data['scopes']);
+ }
+
+ public function applyOn()
+ {
+ return Declarations::ACP_TYPE;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder
+ ->add('scopes', EntityType::class, [
+ 'class' => Scope::class,
+ 'choices' => $this->scopeRepository->findAllActive(),
+ 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
+ 'multiple' => true,
+ 'expanded' => true,
+ ])
+ ->add('date_calc', ChillDateType::class, [
+ 'input' => 'datetime_immutable',
+ 'data' => new DateTimeImmutable('now'),
+ 'label' => 'export.filter.course.by_user_scope.Computation date for referrer',
+ 'required' => true,
+ ]);
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ return [
+ 'Filtered by user main scope: only %scope%', [
+ '%scope%' => implode(
+ ', ',
+ array_map(
+ fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
+ $data['scopes']->toArray()
+ )
+ ),
+ ],
+ ];
+ }
+
+ public function getTitle()
+ {
+ return 'Filter by user scope';
+ }
+
+ private function getUserMainScope(): Scope
+ {
+ /** @var User $user */
+ $user = $this->security->getUser();
+
+ return $user->getMainScope();
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php
index 1c6b362d4..218ae483e 100644
--- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php
+++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php
@@ -35,7 +35,7 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface
$where = $qb->getDQLPart('where');
$min = null !== $data['min_age'] ? $data['min_age'] : 0;
- $max = null !== $data['max_age'] ? $data['max_age'] : 3000;
+ $max = null !== $data['max_age'] ? $data['max_age'] : 150;
$calc = $data['date_calc'];
$minDate = $calc->sub(new DateInterval('P' . $max . 'Y'));
diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php
index a3d2260e3..aad98a394 100644
--- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php
+++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php
@@ -12,12 +12,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Export\FilterInterface;
-use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\MaritalStatus;
use Chill\PersonBundle\Export\Declarations;
-use DateTime;
-use Doctrine\ORM\Query\Expr\Andx;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class MaritalStatusFilter implements FilterInterface
@@ -37,25 +34,10 @@ class MaritalStatusFilter implements FilterInterface
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
{
- $where = $qb->getDQLPart('where');
-
- $clause = $qb->expr()->andX(
- $qb->expr()->in('person.maritalStatus', ':maritalStatus'),
- $qb->expr()->orX(
- $qb->expr()->eq('person.maritalStatusDate', ':calc_date'),
- $qb->expr()->isNull('person.maritalStatusDate')
- )
+ $qb->andWhere(
+ $qb->expr()->in('person.maritalStatus', ':maritalStatus')
);
-
- if ($where instanceof Andx) {
- $where->add($clause);
- } else {
- $where = $qb->expr()->andX($clause);
- }
-
- $qb->add('where', $where);
$qb->setParameter('maritalStatus', $data['maritalStatus']);
- $qb->setParameter('calc_date', $data['calc_date']);
}
public function applyOn()
@@ -75,11 +57,6 @@ class MaritalStatusFilter implements FilterInterface
'multiple' => true,
'expanded' => true,
]);
-
- $builder->add('calc_date', ChillDateType::class, [
- 'label' => 'Marital status at this time',
- 'data' => new DateTime(),
- ]);
}
public function describeAction($data, $format = 'string')
diff --git a/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php b/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php
index 685915203..668a00276 100644
--- a/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php
+++ b/src/Bundle/ChillPersonBundle/Form/SocialWork/EvaluationType.php
@@ -16,6 +16,7 @@ use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -52,6 +53,14 @@ class EvaluationType extends AbstractType
->add('notificationDelay', DateIntervalType::class, [
'label' => 'evaluation.notificationDelay',
'required' => false,
+ ])
+ ->add('active', ChoiceType::class, [
+ 'label' => 'active',
+ 'choices' => [
+ 'active' => true,
+ 'inactive' => false,
+ ],
+ 'required' => true,
]);
}
diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php
index f5217bfa7..071f7b3eb 100644
--- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php
+++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php
@@ -11,7 +11,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Repository;
+use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Location;
+use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
@@ -19,10 +21,14 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
+use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use DateTime;
+use DateTimeImmutable;
+use Doctrine\DBAL\Types\Types;
+use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
use function count;
@@ -49,7 +55,12 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
$this->centerResolverDispatcher = $centerResolverDispatcher;
}
- public function buildQueryOpenedAccompanyingCourseByUser(?User $user)
+ /**
+ * @param array|PostalCode[]
+ *
+ * @return QueryBuilder
+ */
+ public function buildQueryOpenedAccompanyingCourseByUser(?User $user, array $postalCodes = [])
{
$qb = $this->accompanyingPeriodRepository->createQueryBuilder('ap');
@@ -65,6 +76,37 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
->setParameter('now', new DateTime('now'))
->setParameter('draft', AccompanyingPeriod::STEP_DRAFT);
+ if ([] !== $postalCodes) {
+ $qb->join('ap.locationHistories', 'location_history')
+ ->leftJoin(PersonHouseholdAddress::class, 'person_address', Join::WITH, 'IDENTITY(location_history.personLocation) = IDENTITY(person_address.person)')
+ ->join(
+ Address::class,
+ 'address',
+ Join::WITH,
+ 'COALESCE(IDENTITY(location_history.addressLocation), IDENTITY(person_address.address)) = address.id'
+ )
+ ->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->isNull('person_address'),
+ $qb->expr()->andX(
+ $qb->expr()->lte('person_address.validFrom', ':now'),
+ $qb->expr()->orX(
+ $qb->expr()->isNull('person_address.validTo'),
+ $qb->expr()->lt('person_address.validTo', ':now')
+ )
+ )
+ )
+ )
+ ->andWhere(
+ $qb->expr()->isNull('location_history.endDate')
+ )
+ ->andWhere(
+ $qb->expr()->in('address.postcode', ':postal_codes')
+ )
+ ->setParameter('now', new DateTimeImmutable('now'), Types::DATE_IMMUTABLE)
+ ->setParameter('postal_codes', $postalCodes);
+ }
+
return $qb;
}
@@ -77,6 +119,18 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
return $qb->getQuery()->getSingleScalarResult();
}
+ public function countByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes): int
+ {
+ if (null === $user) {
+ return 0;
+ }
+
+ return $this->buildQueryOpenedAccompanyingCourseByUser($user, $postalCodes)
+ ->select('COUNT(ap)')
+ ->getQuery()
+ ->getSingleScalarResult();
+ }
+
public function countByUserOpenedAccompanyingPeriod(?User $user): int
{
if (null === $user) {
@@ -158,6 +212,24 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
return $qb->getQuery()->getResult();
}
+ public function findByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes, array $orderBy = [], int $limit = 0, int $offset = 50): array
+ {
+ if (null === $user) {
+ return [];
+ }
+
+ $qb = $this->buildQueryOpenedAccompanyingCourseByUser($user);
+
+ $qb->setFirstResult($offset)
+ ->setMaxResults($limit);
+
+ foreach ($orderBy as $field => $direction) {
+ $qb->addOrderBy('ap.' . $field, $direction);
+ }
+
+ return $qb->getQuery()->getResult();
+ }
+
/**
* @return array|AccompanyingPeriod[]
*/
diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php
index 6dd44b290..0cca1a5f4 100644
--- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php
+++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php
@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Repository;
+use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
@@ -25,6 +26,11 @@ interface AccompanyingPeriodACLAwareRepositoryInterface
*/
public function countByUnDispatched(array $jobs, array $services, array $administrativeLocations): int;
+ /**
+ * @param array|PostalCode[] $postalCodes
+ */
+ public function countByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes): int;
+
public function countByUserOpenedAccompanyingPeriod(?User $user): int;
public function findByPerson(
@@ -43,5 +49,10 @@ interface AccompanyingPeriodACLAwareRepositoryInterface
*/
public function findByUnDispatched(array $jobs, array $services, array $administrativeLocations, ?int $limit = null, ?int $offset = null): array;
+ /**
+ * @param array|PostalCode[] $postalCodes
+ */
+ public function findByUserAndPostalCodesOpenedAccompanyingPeriod(?User $user, array $postalCodes, array $orderBy = [], int $limit = 0, int $offset = 50): array;
+
public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array;
}
diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php
index b02fef8ba..925d4598b 100644
--- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php
+++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php
@@ -14,9 +14,8 @@ namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
-use Doctrine\Persistence\ObjectRepository;
-final class EvaluationRepository implements ObjectRepository
+final class EvaluationRepository implements EvaluationRepositoryInterface
{
private EntityRepository $repository;
@@ -38,6 +37,11 @@ final class EvaluationRepository implements ObjectRepository
return $this->repository->findAll();
}
+ public function findAllActive(): array
+ {
+ return $this->findBy(['active' => true]);
+ }
+
/**
* @param mixed|null $limit
* @param mixed|null $offset
diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepositoryInterface.php
new file mode 100644
index 000000000..9ca390ff9
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepositoryInterface.php
@@ -0,0 +1,45 @@
+
+ */
+ public function findAll(): array;
+
+ /**
+ * @return array
+ */
+ public function findAllActive(): array;
+
+ /**
+ * @param mixed|null $limit
+ * @param mixed|null $offset
+ *
+ * @return array
+ */
+ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
+
+ public function findOneBy(array $criteria, ?array $orderBy = null): ?Evaluation;
+
+ /**
+ * @return class-string
+ */
+ public function getClassName(): string;
+}
diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php
index f65467387..fd24b7b9d 100644
--- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php
+++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php
@@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
+use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
@@ -62,17 +63,21 @@ final class GoalRepository implements ObjectRepository
}
/**
- * @param mixed|null $orderBy
- * @param mixed|null $limit
- * @param mixed|null $offset
- *
* @return Goal[]
*/
- public function findBySocialActionWithDescendants(SocialAction $action, $orderBy = null, $limit = null, $offset = null): array
+ public function findBySocialActionWithDescendants(SocialAction $action, array $orderBy = [], ?int $limit = null, ?int $offset = null): array
{
$qb = $this->buildQueryBySocialActionWithDescendants($action);
$qb->select('g');
+ $qb->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->isNull('g.desactivationDate'),
+ $qb->expr()->gt('g.desactivationDate', ':now')
+ )
+ )
+ ->setParameter('now', new DateTime('now'));
+
foreach ($orderBy as $sort => $order) {
$qb->addOrderBy('g.' . $sort, $order);
}
diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php
index df73bde21..e751f88cb 100644
--- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php
+++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php
@@ -96,13 +96,9 @@ final class ResultRepository implements ObjectRepository
}
/**
- * @param mixed|null $orderBy
- * @param mixed|null $limit
- * @param mixed|null $offset
- *
* @return Result[]
*/
- public function findBySocialActionWithDescendants(SocialAction $action, $orderBy = null, $limit = null, $offset = null): array
+ public function findBySocialActionWithDescendants(SocialAction $action, array $orderBy = [], ?int $limit = null, ?int $offset = null): array
{
$qb = $this->buildQueryBySocialActionWithDescendants($action);
$qb->select('r');
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/dispatch_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/dispatch_list.html.twig
index 88bdf80c3..8cb5ab59f 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/dispatch_list.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/dispatch_list.html.twig
@@ -76,7 +76,7 @@
{% for period in periods %}
{% include '@ChillPerson/AccompanyingPeriod/_list_item.html.twig' with {'period': period,
- 'recordAction': m.period_actions(period), 'itemMeta': m.period_meta(period) } %}
+ 'recordAction': m.period_actions(period), 'itemMeta': m.period_meta(period), 'show_address': true } %}
{% endfor %}
{% endif %}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig
index cfa616988..6c7fbf7c0 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig
@@ -113,6 +113,16 @@
{% endif %}
+ {% if show_address|default(false) and period.location is not null %}
+
+
{{ 'Accompanying course location'|trans }}
+
+
+ {{ period.location|chill_entity_render_string }}
+
+
+
+ {% endif %}
{% endif %}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig
index 63e25efeb..c1144512d 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/reassign_list.html.twig
@@ -5,11 +5,13 @@
{% block js %}
{{ encore_entry_script_tags('mod_set_referrer') }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
+ {{ encore_entry_script_tags('mod_pick_postal_code') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_set_referrer') }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
+ {{ encore_entry_link_tags('mod_pick_postal_code') }}
{% endblock %}
{% macro period_meta(period) %}
@@ -48,6 +50,8 @@
{{ form_start(form) }}
{{ form_label(form.user ) }}
{{ form_widget(form.user, {'attr': {'class': 'select2'}}) }}
+ {{ form_label(form.postal_code) }}
+ {{ form_widget(form.postal_code) }}
-