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 }} :

+ + {% endif %} + {% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}

{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :

{% 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 }} :

+ + {% endif %} + {% if step.destUserByAccessKey|length > 0 %}

{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :