Merge branch '111_exports_suite' into calendar/finalization

This commit is contained in:
Julien Fastré 2022-10-24 11:10:44 +02:00
commit aae1fbf7e6
158 changed files with 3064 additions and 1522 deletions

View File

@ -37,6 +37,9 @@ and this project adheres to
* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625) * [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) * [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) * [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 ### 2022-05-30

View File

@ -18,7 +18,6 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class CountPerson implements ExportInterface class CountPerson implements ExportInterface
{ {
@ -73,9 +72,9 @@ class CountPerson implements ExportInterface
return ['export_result']; return ['export_result'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle() public function getTitle()
@ -106,9 +105,9 @@ class CountPerson implements ExportInterface
return $qb; return $qb;
} }
public function requiredRole() public function requiredRole(): string
{ {
return new Role(PersonVoter::STATS); return PersonVoter::STATS;
} }
public function supportsModifiers() public function supportsModifiers()

View File

@ -265,7 +265,7 @@ Tests reside inside the installed bundles. You must `cd` into that directory, do
**Note**: some bundle require the fixture to be executed. See the dedicated _how-tos_. **Note**: some bundle require the fixture to be executed. See the dedicated _how-tos_.
Exemple, for running test inside `main` bundle: Exemple, for running unit test inside `main` bundle:
.. code-block:: bash .. code-block:: bash
@ -280,6 +280,30 @@ Exemple, for running test inside `main` bundle:
# run tests # run tests
bin/phpunit src/Bundle/path/to/your/test bin/phpunit src/Bundle/path/to/your/test
Or for running tests to check code style and php conventions with csfixer and phpstan:
.. code-block:: bash
# run code style fixer
bin/grumphp run --tasks=phpcsfixer
# run phpstan
bin/grumphp run --tasks=phpstan
.. note::
To avoid phpstan block your commits:
.. code-block:: bash
git commit -n ...
To avoid phpstan block your commits permanently:
.. code-block:: bash
./bin/grumphp git:deinit
How to run webpack interactively How to run webpack interactively
================================ ================================

View File

@ -17,6 +17,7 @@ These are alias conventions :
| | Scope::class | acp.scopes | acpscope | | | Scope::class | acp.scopes | acpscope |
| | SocialIssue::class | acp.socialIssues | acpsocialissue | | | SocialIssue::class | acp.socialIssues | acpsocialissue |
| | User::class | acp.user | acpuser | | | User::class | acp.user | acpuser |
| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories |
| AccompanyingPeriodWork::class | | | acpw | | AccompanyingPeriodWork::class | | | acpw |
| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | | | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval |
| | User::class | acpw.referrers | acpwuser | | | User::class | acpw.referrers | acpwuser |
@ -59,6 +60,7 @@ These are alias conventions :
| | User::class | activity.users | actusers | | | User::class | activity.users | actusers |
| | ActivityReason::class | activity.reasons | actreasons | | | ActivityReason::class | activity.reasons | actreasons |
| | Center::class | actperson.center | actcenter | | | Center::class | actperson.center | actcenter |
| | Person::class | activity.createdBy | actcreator |
| ActivityReason::class | | | actreasons | | ActivityReason::class | | | actreasons |
| | ActivityReasonCategory::class | actreason.category | actreasoncat | | | ActivityReasonCategory::class | actreason.category | actreasoncat |
| Calendar::class | | | cal | | Calendar::class | | | cal |

View File

@ -23,150 +23,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php path: src/Bundle/ChillActivityBundle/Entity/ActivityReasonCategory.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Aggregator\\\\ActivityReasonAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Aggregator\\\\ActivityTypeAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Aggregator\\\\ActivityUserAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/CountActivity.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\CountActivity\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/CountActivity.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/ListActivity.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\ListActivity\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/ListActivity.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Export\\\\StatActivityDuration\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Export/StatActivityDuration.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\ActivityDateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityReasonFilter.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\ActivityReasonFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityReasonFilter.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\ActivityTypeFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php
-
message: """
#^Return type of method Chill\\\\ActivityBundle\\\\Export\\\\Filter\\\\PersonHavingActivityBetweenDateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillActivityBundle/Export/Filter/PersonHavingActivityBetweenDateFilter.php
- -
message: """ message: """
#^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: #^Fetching class constant class of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
@ -359,31 +215,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillMainBundle/Entity/User.php path: src/Bundle/ChillMainBundle/Entity/User.php
-
message: """
#^Return type of method Chill\\\\MainBundle\\\\Export\\\\DirectExportInterface\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillMainBundle/Export/DirectExportInterface.php
-
message: """
#^Return type of method Chill\\\\MainBundle\\\\Export\\\\ExportInterface\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillMainBundle/Export/ExportInterface.php
-
message: """
#^Return type of method Chill\\\\MainBundle\\\\Export\\\\ModifierInterface\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillMainBundle/Export/ModifierInterface.php
- -
message: """ message: """
#^Class Chill\\\\MainBundle\\\\Form\\\\Event\\\\CustomizeFormEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\: #^Class Chill\\\\MainBundle\\\\Form\\\\Event\\\\CustomizeFormEvent extends deprecated class Symfony\\\\Component\\\\EventDispatcher\\\\Event\\:
@ -543,142 +374,6 @@ parameters:
count: 2 count: 2
path: src/Bundle/ChillPersonBundle/Entity/Person.php path: src/Bundle/ChillPersonBundle/Entity/Person.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\AgeAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Aggregator/AgeAggregator.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\CountryOfBirthAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Aggregator/CountryOfBirthAggregator.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\GenderAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Aggregator/GenderAggregator.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Aggregator\\\\NationalityAggregator\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Aggregator/NationalityAggregator.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Export\\\\CountPerson\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Export\\\\ListPerson\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php
-
message: """
#^Call to deprecated method execute\\(\\) of class Doctrine\\\\DBAL\\\\Statement\\:
Statement\\:\\:execute\\(\\) is deprecated, use Statement\\:\\:executeQuery\\(\\) or executeStatement\\(\\) instead$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Export\\\\ListPersonDuplicate\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingPeriodClosingFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodClosingFilter.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingPeriodFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodFilter.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\AccompanyingPeriodOpeningFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodOpeningFilter.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\BirthdateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Filter/BirthdateFilter.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\GenderFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Filter/GenderFilter.php
-
message: """
#^Return type of method Chill\\\\PersonBundle\\\\Export\\\\Filter\\\\NationalityFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Export/Filter/NationalityFilter.php
- -
message: """ message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
@ -740,29 +435,6 @@ parameters:
count: 3 count: 3
path: src/Bundle/ChillReportBundle/Controller/ReportController.php path: src/Bundle/ChillReportBundle/Controller/ReportController.php
-
message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php
-
message: """
#^Return type of method Chill\\\\ReportBundle\\\\Export\\\\Export\\\\ReportList\\:\\:requiredRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillReportBundle/Export/Export/ReportList.php
-
message: """
#^Return type of method Chill\\\\ReportBundle\\\\Export\\\\Filter\\\\ReportDateFilter\\:\\:addRole\\(\\) has typehint with deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php
- -
message: """ message: """
@ -845,14 +517,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillTaskBundle/Form/SingleTaskType.php path: src/Bundle/ChillTaskBundle/Form/SingleTaskType.php
-
message: """
#^Parameter \\$centerResolverDispatcher of method Chill\\\\TaskBundle\\\\Repository\\\\SingleTaskAclAwareRepository\\:\\:__construct\\(\\) has typehint with deprecated interface Chill\\\\MainBundle\\\\Security\\\\Resolver\\\\CenterResolverDispatcherInterface\\:
Use CenterResolverManager and its interface CenterResolverManagerInterface$#
"""
count: 1
path: src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php
- -
message: """ message: """
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\: #^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:

View File

@ -245,11 +245,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillMainBundle/Entity/User.php path: src/Bundle/ChillMainBundle/Entity/User.php
-
message: "#^Only booleans are allowed in a ternary operator condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php
- -
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1 count: 1

View File

@ -13,6 +13,10 @@ namespace Chill\ActivityBundle\Entity;
use Chill\ActivityBundle\Validator\Constraints as ActivityValidator; use Chill\ActivityBundle\Validator\Constraints as ActivityValidator;
use Chill\DocStoreBundle\Entity\StoredObject; 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\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
@ -55,8 +59,12 @@ use Symfony\Component\Validator\Constraints as Assert;
* getUserFunction="getUser", * getUserFunction="getUser",
* path="scope") * 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_RECEIVED = 'received';
public const SENTRECEIVED_SENT = 'sent'; public const SENTRECEIVED_SENT = 'sent';

View File

@ -13,20 +13,19 @@ namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByUserAggregator implements AggregatorInterface class ByCreatorAggregator implements AggregatorInterface
{ {
private UserRender $userRender; private UserRender $userRender;
private UserRepository $userRepository; private UserRepositoryInterface $userRepository;
public function __construct( public function __construct(
UserRepository $userRepository, UserRepositoryInterface $userRepository,
UserRender $userRender UserRender $userRender
) { ) {
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
@ -40,12 +39,8 @@ class ByUserAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
if (!in_array('actusers', $qb->getAllAliases(), true)) { $qb->addSelect('IDENTITY(activity.createdBy) AS creator_aggregator');
$qb->leftJoin('activity.users', 'actusers'); $qb->addGroupBy('creator_aggregator');
}
$qb->addSelect('actusers.id AS users_aggregator');
$qb->addGroupBy('users_aggregator');
} }
public function applyOn(): string public function applyOn(): string
@ -62,7 +57,7 @@ class ByUserAggregator implements AggregatorInterface
{ {
return function ($value): string { return function ($value): string {
if ('_header' === $value) { if ('_header' === $value) {
return 'Accepted users'; return 'Created by';
} }
if (null === $value) { if (null === $value) {
@ -77,11 +72,11 @@ class ByUserAggregator implements AggregatorInterface
public function getQueryKeys($data): array public function getQueryKeys($data): array
{ {
return ['users_aggregator']; return ['creator_aggregator'];
} }
public function getTitle(): string public function getTitle(): string
{ {
return 'Group activity by linked users'; return 'Group activity by creator';
} }
} }

View File

@ -19,7 +19,7 @@ use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
class UserScopeAggregator implements AggregatorInterface class CreatorScopeAggregator implements AggregatorInterface
{ {
private ScopeRepository $scopeRepository; private ScopeRepository $scopeRepository;
@ -40,12 +40,12 @@ class UserScopeAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
if (!in_array('actuser', $qb->getAllAliases(), true)) { if (!in_array('actcreator', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.user', 'actuser'); $qb->leftJoin('activity.createdBy', 'actcreator');
} }
$qb->addSelect('IDENTITY(actuser.mainScope) AS userscope_aggregator'); $qb->addSelect('IDENTITY(actcreator.mainScope) AS creatorscope_aggregator');
$qb->addGroupBy('userscope_aggregator'); $qb->addGroupBy('creatorscope_aggregator');
} }
public function applyOn(): string public function applyOn(): string
@ -79,11 +79,11 @@ class UserScopeAggregator implements AggregatorInterface
public function getQueryKeys($data): array public function getQueryKeys($data): array
{ {
return ['userscope_aggregator']; return ['creatorscope_aggregator'];
} }
public function getTitle(): string public function getTitle(): string
{ {
return 'Group activity by userscope'; return 'Group activity by creator scope';
} }
} }

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
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 ActivityUsersAggregator implements AggregatorInterface
{
private UserRender $userRender;
private UserRepositoryInterface $userRepository;
public function __construct(UserRepositoryInterface $userRepository, UserRender $userRender)
{
$this->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';
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorInterface
{
private TranslatableStringHelperInterface $translatableStringHelper;
private UserJobRepositoryInterface $userJobRepository;
public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->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';
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\AggregatorInterface
{
private ScopeRepositoryInterface $scopeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->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';
}
}

View File

@ -69,9 +69,9 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
return ['export_avg_activity_duration']; return ['export_avg_activity_duration'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -70,9 +70,9 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac
return ['export_avg_activity_visit_duration']; return ['export_avg_activity_visit_duration'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -69,9 +69,9 @@ class CountActivity implements ExportInterface, GroupedExportInterface
return ['export_count_activity']; return ['export_count_activity'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -70,9 +70,9 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface
return ['export_sum_activity_duration']; return ['export_sum_activity_duration'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -70,9 +70,9 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac
return ['export_sum_activity_visit_duration']; return ['export_sum_activity_visit_duration'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -65,9 +65,9 @@ class CountActivity implements ExportInterface, GroupedExportInterface
return ['export_count_activity']; return ['export_count_activity'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle() public function getTitle()

View File

@ -64,6 +64,8 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
if (self::SUM === $this->action) { if (self::SUM === $this->action) {
return 'Sum activities linked to a person duration by various parameters.'; return 'Sum activities linked to a person duration by various parameters.';
} }
throw new LogicException('this action is not supported: ' . $this->action);
} }
public function getGroup(): string public function getGroup(): string
@ -87,9 +89,9 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
return ['export_stat_activity']; return ['export_stat_activity'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle() public function getTitle()
@ -97,6 +99,8 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
if (self::SUM === $this->action) { if (self::SUM === $this->action) {
return 'Sum activity linked to a person duration'; return 'Sum activity linked to a person duration';
} }
throw new LogicException('This action is not supported: ' . $this->action);
} }
public function getType(): string public function getType(): string

View File

@ -12,16 +12,13 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters; namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByUserFilter implements FilterInterface class ByCreatorFilter implements FilterInterface
{ {
private UserRender $userRender; private UserRender $userRender;
@ -37,22 +34,11 @@ class ByUserFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$where = $qb->getDQLPart('where'); $qb
->andWhere(
if (!in_array('actusers', $qb->getAllAliases(), true)) { $qb->expr()->in('activity.createdBy', ':users')
$qb->join('activity.users', 'actusers'); )
} ->setParameter('users', $data['accepted_users']);
$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']);
} }
public function applyOn(): string public function applyOn(): string
@ -62,13 +48,8 @@ class ByUserFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_users', EntityType::class, [ $builder->add('accepted_users', PickUserDynamicType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
]); ]);
} }
@ -80,13 +61,13 @@ class ByUserFilter implements FilterInterface
$users[] = $this->userRender->renderString($u, []); $users[] = $this->userRender->renderString($u, []);
} }
return ['Filtered activity by linked users: only %users%', [ return ['Filtered activity by creator: only %users%', [
'%users%' => implode(', ou ', $users), '%users%' => implode(', ', $users),
]]; ]];
} }
public function getTitle(): string public function getTitle(): string
{ {
return 'Filter activity by linked users'; return 'Filter activity by creator';
} }
} }

View File

@ -14,10 +14,9 @@ namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Form\Type\PickSocialActionType;
use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
@ -37,22 +36,17 @@ class BySocialActionFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$where = $qb->getDQLPart('where');
if (!in_array('actsocialaction', $qb->getAllAliases(), true)) { if (!in_array('actsocialaction', $qb->getAllAliases(), true)) {
$qb->join('activity.socialActions', 'actsocialaction'); $qb->join('activity.socialActions', 'actsocialaction');
} }
$clause = $qb->expr()->in('actsocialaction.id', ':socialactions'); $clause = $qb->expr()->in('actsocialaction.id', ':socialactions');
if ($where instanceof Andx) { $qb->andWhere($clause)
$where->add($clause); ->setParameter(
} else { 'socialactions',
$where = $qb->expr()->andX($clause); SocialAction::getDescendantsWithThisForActions($data['accepted_socialactions'])
} );
$qb->add('where', $where);
$qb->setParameter('socialactions', $data['accepted_socialactions']);
} }
public function applyOn(): string public function applyOn(): string
@ -62,13 +56,8 @@ class BySocialActionFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_socialactions', EntityType::class, [ $builder->add('accepted_socialactions', PickSocialActionType::class, [
'class' => SocialAction::class,
'choice_label' => function (SocialAction $sa) {
return $this->actionRender->renderString($sa, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
]); ]);
} }
@ -76,12 +65,14 @@ class BySocialActionFilter implements FilterInterface
{ {
$actions = []; $actions = [];
foreach ($data['accepted_socialactions'] as $sa) { foreach ($data['accepted_socialactions'] as $action) {
$actions[] = $this->actionRender->renderString($sa, []); $actions[] = $this->actionRender->renderString($action, [
'show_and_children' => true,
]);
} }
return ['Filtered activity by linked socialaction: only %actions%', [ return ['Filtered activity by linked socialaction: only %actions%', [
'%actions%' => implode(', ou ', $actions), '%actions%' => implode(', ', $actions),
]]; ]];
} }

View File

@ -14,10 +14,9 @@ namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Form\Type\PickSocialIssueType;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
@ -37,22 +36,17 @@ class BySocialIssueFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$where = $qb->getDQLPart('where');
if (!in_array('actsocialissue', $qb->getAllAliases(), true)) { if (!in_array('actsocialissue', $qb->getAllAliases(), true)) {
$qb->join('activity.socialIssues', 'actsocialissue'); $qb->join('activity.socialIssues', 'actsocialissue');
} }
$clause = $qb->expr()->in('actsocialissue.id', ':socialissues'); $clause = $qb->expr()->in('actsocialissue.id', ':socialissues');
if ($where instanceof Andx) { $qb->andWhere($clause)
$where->add($clause); ->setParameter(
} else { 'socialissues',
$where = $qb->expr()->andX($clause); SocialIssue::getDescendantsWithThisForIssues($data['accepted_socialissues'])
} );
$qb->add('where', $where);
$qb->setParameter('socialissues', $data['accepted_socialissues']);
} }
public function applyOn(): string public function applyOn(): string
@ -62,13 +56,8 @@ class BySocialIssueFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_socialissues', EntityType::class, [ $builder->add('accepted_socialissues', PickSocialIssueType::class, [
'class' => SocialIssue::class,
'choice_label' => function (SocialIssue $si) {
return $this->issueRender->renderString($si, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
]); ]);
} }
@ -76,12 +65,14 @@ class BySocialIssueFilter implements FilterInterface
{ {
$issues = []; $issues = [];
foreach ($data['accepted_socialissues'] as $si) { foreach ($data['accepted_socialissues'] as $issue) {
$issues[] = $this->issueRender->renderString($si, []); $issues[] = $this->issueRender->renderString($issue, [
'show_and_children' => true,
]);
} }
return ['Filtered activity by linked socialissue: only %issues%', [ return ['Filtered activity by linked socialissue: only %issues%', [
'%issues%' => implode(', ou ', $issues), '%issues%' => implode(', ', $issues),
]]; ]];
} }

View File

@ -74,15 +74,13 @@ class EmergencyFilter implements FilterInterface
public function describeAction($data, $format = 'string'): array public function describeAction($data, $format = 'string'): array
{ {
foreach (self::CHOICES as $k => $v) { return [
if ($v === $data['accepted_emergency']) { 'Filtered by emergency: only %emergency%', [
$choice = $k; '%emergency%' => $this->translator->trans(
} $data['accepted_emergency'] ? 'is emergency' : 'is not emergency'
} ),
],
return ['Filtered activity by emergency: only %emergency%', [ ];
'%emergency%' => $this->translator->trans($choice),
]];
} }
public function getTitle(): string public function getTitle(): string

View File

@ -12,12 +12,11 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters; namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickLocationTypeType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array; use function in_array;
@ -61,13 +60,9 @@ class LocationTypeFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_locationtype', EntityType::class, [ $builder->add('accepted_locationtype', PickLocationTypeType::class, [
'class' => LocationType::class,
'choice_label' => function (LocationType $type) {
return $this->translatableStringHelper->localize($type->getTitle());
},
'multiple' => true, 'multiple' => true,
'expanded' => true, //'label' => false,
]); ]);
} }
@ -82,7 +77,7 @@ class LocationTypeFilter implements FilterInterface
} }
return ['Filtered activity by locationtype: only %types%', [ return ['Filtered activity by locationtype: only %types%', [
'%types%' => implode(', ou ', $types), '%types%' => implode(', ', $types),
]]; ]];
} }

View File

@ -12,12 +12,11 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters; namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class UserFilter implements FilterInterface class UserFilter implements FilterInterface
@ -57,13 +56,8 @@ class UserFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_users', EntityType::class, [ $builder->add('accepted_users', PickUserDynamicType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true, 'multiple' => true,
'expanded' => true,
'label' => 'Creators', 'label' => 'Creators',
]); ]);
} }
@ -77,7 +71,7 @@ class UserFilter implements FilterInterface
} }
return ['Filtered activity by user: only %users%', [ return ['Filtered activity by user: only %users%', [
'%users%' => implode(', ou ', $users), '%users%' => implode(', ', $users),
]]; ]];
} }

View File

@ -85,7 +85,7 @@ class UserScopeFilter implements FilterInterface
} }
return ['Filtered activity by userscope: only %scopes%', [ return ['Filtered activity by userscope: only %scopes%', [
'%scopes%' => implode(', ou ', $scopes), '%scopes%' => implode(', ', $scopes),
]]; ]];
} }

View File

@ -91,7 +91,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
); );
return ['Filtered by activity type: only %list%', [ return ['Filtered by activity type: only %list%', [
'%list%' => implode(', ou ', $reasonsNames), '%list%' => implode(', ', $reasonsNames),
]]; ]];
} }

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
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\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class ActivityUsersFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
{
$this->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';
}
}

View File

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class UsersJobFilter implements FilterInterface
{
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(TranslatableStringHelperInterface $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . Activity::class . ' activity_users_job_filter_act
JOIN activity_users_job_filter_act.users users WHERE users.userJob IN (:activity_users_job_filter_jobs) AND activity_users_job_filter_act = activity '
)
)
->setParameter('activity_users_job_filter_jobs', $data['jobs']);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('jobs', EntityType::class, [
'class' => UserJob::class,
'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()),
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string')
{
return ['export.filter.activity.by_usersjob.Filtered activity by users job: only %jobs%', [
'%jobs%' => implode(
', ',
array_map(
fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
$data['jobs']->toArray()
)
),
]];
}
public function getTitle()
{
return 'export.filter.activity.by_usersjob.Filter by users job';
}
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class UsersScopeFilter implements FilterInterface
{
private ScopeRepositoryInterface $scopeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ScopeRepositoryInterface $scopeRepository,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . Activity::class . ' activity_users_scope_filter_act
JOIN activity_users_scope_filter_act.users users WHERE users.mainScope IN (:activity_users_scope_filter_scopes) AND activity_users_scope_filter_act = activity '
)
)
->setParameter('activity_users_scope_filter_scopes', $data['scopes']);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
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,
]);
}
public function describeAction($data, $format = 'string')
{
return ['export.filter.activity.by_usersscope.Filtered activity by users scope: only %scopes%', [
'%scopes%' => implode(
', ',
array_map(
fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
$data['scopes']->toArray()
)
),
]];
}
public function getTitle()
{
return 'export.filter.activity.by_usersscope.Filter by users scope';
}
}

View File

@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators; namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity; 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 Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -22,7 +22,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/ */
final class ByUserAggregatorTest extends AbstractAggregatorTest final class ByUserAggregatorTest extends AbstractAggregatorTest
{ {
private ByUserAggregator $aggregator; private ByCreatorAggregator $aggregator;
protected function setUp(): void protected function setUp(): void
{ {

View File

@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators; namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity; 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 Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -22,7 +22,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/ */
final class UserScopeAggregatorTest extends AbstractAggregatorTest final class UserScopeAggregatorTest extends AbstractAggregatorTest
{ {
private UserScopeAggregator $aggregator; private CreatorScopeAggregator $aggregator;
protected function setUp(): void protected function setUp(): void
{ {

View File

@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters; namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity; 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\Entity\User;
use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -23,7 +23,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/ */
final class ByUserFilterTest extends AbstractFilterTest final class ByUserFilterTest extends AbstractFilterTest
{ {
private ByUserFilter $filter; private ByCreatorFilter $filter;
protected function setUp(): void protected function setUp(): void
{ {

View File

@ -4,18 +4,15 @@ services:
autoconfigure: true autoconfigure: true
## Indicators ## Indicators
chill.activity.export.count_activity_linked_to_person: Chill\ActivityBundle\Export\Export\LinkedToPerson\CountActivity:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\CountActivity
tags: tags:
- { name: chill.export, alias: 'count_activity_linked_to_person' } - { name: chill.export, alias: 'count_activity_linked_to_person' }
chill.activity.export.sum_activity_duration_linked_to_person: Chill\ActivityBundle\Export\Export\LinkedToPerson\StatActivityDuration:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\StatActivityDuration
tags: tags:
- { name: chill.export, alias: 'sum_activity_duration_linked_to_person' } - { name: chill.export, alias: 'sum_activity_duration_linked_to_person' }
chill.activity.export.list_activity_linked_to_person: Chill\ActivityBundle\Export\Export\LinkedToPerson\ListActivity:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\ListActivity
tags: tags:
- { name: chill.export, alias: 'list_activity_linked_to_person' } - { name: chill.export, alias: 'list_activity_linked_to_person' }
@ -55,6 +52,10 @@ services:
tags: tags:
- { name: chill.export_filter, alias: 'activity_date_filter' } - { 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: chill.activity.export.reason_filter:
class: Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter class: Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter
tags: tags:
@ -77,10 +78,9 @@ services:
tags: tags:
- { name: chill.export_filter, alias: 'activity_locationtype_filter' } - { name: chill.export_filter, alias: 'activity_locationtype_filter' }
chill.activity.export.byuser_filter: # TMS (M2M) Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\ByUserFilter
tags: tags:
- { name: chill.export_filter, alias: 'activity_byuser_filter' } - { name: chill.export_filter, alias: 'activity_bycreator_filter' }
chill.activity.export.emergency_filter: chill.activity.export.emergency_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter
@ -112,9 +112,16 @@ services:
tags: tags:
- { name: chill.export_filter, alias: 'activity_userscope_filter' } - { name: chill.export_filter, alias: 'activity_userscope_filter' }
Chill\ActivityBundle\Export\Filter\UsersJobFilter:
tags:
- { name: chill.export_filter, alias: 'activity_usersjob_filter' }
Chill\ActivityBundle\Export\Filter\UsersScopeFilter:
tags:
- { name: chill.export_filter, alias: 'activity_usersscope_filter' }
## Aggregators ## Aggregators
chill.activity.export.reason_aggregator: Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator:
class: Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator
tags: tags:
- { name: chill.export_aggregator, alias: activity_reason_aggregator } - { name: chill.export_aggregator, alias: activity_reason_aggregator }
@ -138,10 +145,9 @@ services:
tags: tags:
- { name: chill.export_aggregator, alias: activity_date_aggregator } - { name: chill.export_aggregator, alias: activity_date_aggregator }
chill.activity.export.byuser_aggregator: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByUserAggregator
tags: tags:
- { name: chill.export_aggregator, alias: activity_byuser_aggregator } - { name: chill.export_aggregator, alias: activity_by_creator_aggregator }
chill.activity.export.bythirdparty_aggregator: chill.activity.export.bythirdparty_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator
@ -158,7 +164,18 @@ services:
tags: tags:
- { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator } - { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator }
chill.activity.export.userscope_aggregator: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\UserScopeAggregator
tags: 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 }

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Activity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20221014130554 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->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"');
}
}

View File

@ -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 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%" 'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%"
Accepted locationtype: Types de localisation 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) Accepted users: TMS(s)
Filter activity by emergency: Filtrer les activités par urgence Filter activity by emergency: Filtrer les activités par urgence
'Filtered activity by emergency: only %emergency%': "Filtré par urgence: uniquement si %emergency%" '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 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%" '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 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 Creators: Créateurs
Filter activity by userscope: Filtrer les activités par service du créateur 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%" '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 By category of reason: Par catégorie de sujet
Reason's level: Niveau du sujet Reason's level: Niveau du sujet
Group by reasons: Sujet d'activité 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 type: Grouper les activités par type
Aggregate by activity reason: Grouper les activités par sujet 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 locationtype: Grouper les activités par type de localisation
Group activity by date: Grouper les activités par date Group activity by date: Grouper les activités par date
@ -294,7 +301,8 @@ by week: Par semaine
for week: Semaine for week: Semaine
by year: Par année by year: Par année
in year: En 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é Group activity by linked thirdparties: Grouper les activités par tiers impliqué
Accepted thirdparty: Tiers impliqué Accepted thirdparty: Tiers impliqué
Group activity by linked socialaction: Grouper les activités par action liée Group activity by linked socialaction: Grouper les activités par action liée
@ -314,3 +322,13 @@ docgen:
A basic context for activity: Contexte pour les échanges A basic context for activity: Contexte pour les échanges
Accompanying period with a list of activities: Parcours d'accompagnement avec liste des échanges Accompanying period with a list of activities: Parcours d'accompagnement avec liste des échanges
Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les échanges pour un parcours. Les échanges ne sont pas filtrés. Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les échanges pour un parcours. Les échanges ne sont pas filtrés.
export:
filter:
activity:
by_usersjob:
Filter by users job: Filtrer les activités par métier d'au moins un utilisateur participant
'Filtered activity by users job: only %jobs%': 'Filtré par métier d''au moins un utilisateur participant: seulement %jobs%'
by_usersscope:
Filter by users scope: Filtrer les activités par services d'au moins un utilisateur participant
'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%'

View File

@ -46,14 +46,7 @@ final class AgentAggregator implements AggregatorInterface
} }
$qb->addSelect('caluser.id AS agent_aggregator'); $qb->addSelect('caluser.id AS agent_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('agent_aggregator'); $qb->addGroupBy('agent_aggregator');
} else {
$qb->groupBy('agent_aggregator');
}
} }
public function applyOn(): string public function applyOn(): string

View File

@ -47,14 +47,7 @@ class CancelReasonAggregator implements AggregatorInterface
} }
$qb->addSelect('IDENTITY(cal.cancelReason) as cancel_reason_aggregator'); $qb->addSelect('IDENTITY(cal.cancelReason) as cancel_reason_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('cancel_reason_aggregator'); $qb->addGroupBy('cancel_reason_aggregator');
} else {
$qb->groupBy('cancel_reason_aggregator');
}
} }
public function applyOn(): string public function applyOn(): string

View File

@ -46,14 +46,7 @@ final class JobAggregator implements AggregatorInterface
} }
$qb->addSelect('IDENTITY(caluser.userJob) as job_aggregator'); $qb->addSelect('IDENTITY(caluser.userJob) as job_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('job_aggregator'); $qb->addGroupBy('job_aggregator');
} else {
$qb->groupBy('job_aggregator');
}
} }
public function applyOn(): string public function applyOn(): string

View File

@ -40,14 +40,7 @@ final class LocationAggregator implements AggregatorInterface
$qb->join('cal.location', 'calloc'); $qb->join('cal.location', 'calloc');
} }
$qb->addSelect('IDENTITY(cal.location) as location_aggregator'); $qb->addSelect('IDENTITY(cal.location) as location_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('location_aggregator'); $qb->addGroupBy('location_aggregator');
} else {
$qb->groupBy('location_aggregator');
}
} }
public function applyOn(): string public function applyOn(): string

View File

@ -46,14 +46,7 @@ final class LocationTypeAggregator implements AggregatorInterface
} }
$qb->addSelect('IDENTITY(calloc.locationType) as location_type_aggregator'); $qb->addSelect('IDENTITY(calloc.locationType) as location_type_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('location_type_aggregator'); $qb->addGroupBy('location_type_aggregator');
} else {
$qb->groupBy('location_type_aggregator');
}
} }
public function applyOn(): string public function applyOn(): string

View File

@ -28,13 +28,7 @@ class MonthYearAggregator implements AggregatorInterface
{ {
$qb->addSelect("to_char(cal.startDate, 'MM-YYYY') AS month_year_aggregator"); $qb->addSelect("to_char(cal.startDate, 'MM-YYYY') AS month_year_aggregator");
// $qb->addSelect("extract(month from age(cal.startDate, cal.endDate)) AS month_aggregator"); // $qb->addSelect("extract(month from age(cal.startDate, cal.endDate)) AS month_aggregator");
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('month_year_aggregator'); $qb->addGroupBy('month_year_aggregator');
} else {
$qb->groupBy('month_year_aggregator');
}
} }
public function applyOn(): string public function applyOn(): string
@ -54,10 +48,7 @@ class MonthYearAggregator implements AggregatorInterface
return 'by month and year'; return 'by month and year';
} }
$month = (int) substr($value, 0, 2); return $value;
$year = (int) substr($value, 3, 4);
return strftime('%B %G', mktime(0, 0, 0, $month, 1, $year));
}; };
} }

View File

@ -46,14 +46,7 @@ final class ScopeAggregator implements AggregatorInterface
} }
$qb->addSelect('IDENTITY(caluser.mainScope) as scope_aggregator'); $qb->addSelect('IDENTITY(caluser.mainScope) as scope_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('scope_aggregator'); $qb->addGroupBy('scope_aggregator');
} else {
$qb->groupBy('scope_aggregator');
}
} }
public function applyOn(): string public function applyOn(): string

View File

@ -71,9 +71,9 @@ class CountCalendars implements ExportInterface, GroupedExportInterface
return ['export_result']; return ['export_result'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -71,9 +71,9 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface
return ['export_result']; return ['export_result'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -71,9 +71,9 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface
return ['export_result']; return ['export_result'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string

View File

@ -76,7 +76,7 @@ class AgentFilter implements FilterInterface
return [ return [
'Filtered by agent: only %agents%', [ 'Filtered by agent: only %agents%', [
'%agents' => implode(', ou ', $users), '%agents' => implode(', ', $users),
], ]; ], ];
} }

View File

@ -90,7 +90,7 @@ class JobFilter implements FilterInterface
} }
return ['Filtered by agent job: only %jobs%', [ return ['Filtered by agent job: only %jobs%', [
'%jobs%' => implode(', ou ', $userJobs), '%jobs%' => implode(', ', $userJobs),
]]; ]];
} }

View File

@ -90,7 +90,7 @@ class ScopeFilter implements FilterInterface
} }
return ['Filtered by agent scope: only %scopes%', [ return ['Filtered by agent scope: only %scopes%', [
'%scopes%' => implode(', ou ', $scopes), '%scopes%' => implode(', ', $scopes),
]]; ]];
} }

View File

@ -42,6 +42,9 @@ class BaseContextData
$data['createdAt'] = $this->normalizer->normalize(new DateTimeImmutable(), 'docgen', [ $data['createdAt'] = $this->normalizer->normalize(new DateTimeImmutable(), 'docgen', [
'docgen:expects' => DateTimeImmutable::class, 'groups' => ['docgen:read'], 'docgen:expects' => DateTimeImmutable::class, 'groups' => ['docgen:read'],
]); ]);
$data['createdAtDate'] = $this->normalizer->normalize(new DateTimeImmutable('today'), 'docgen', [
'docgen:expects' => DateTimeImmutable::class, 'groups' => ['docgen:read'],
]);
$data['location'] = $this->normalizer->normalize( $data['location'] = $this->normalizer->normalize(
$user instanceof User ? $user->getCurrentLocation() : null, $user instanceof User ? $user->getCurrentLocation() : null,
'docgen', 'docgen',

View File

@ -34,7 +34,7 @@ class AccompanyingCourseDocument extends Document implements HasScopesInterface
public function getScopes(): iterable public function getScopes(): iterable
{ {
if (null !== $this->course) { if (null === $this->course) {
return []; return [];
} }

View File

@ -22,6 +22,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface;
@ -191,18 +192,12 @@ class ExportController extends AbstractController
case 'export': case 'export':
return $this->exportFormStep($request, $export, $alias); return $this->exportFormStep($request, $export, $alias);
break;
case 'formatter': case 'formatter':
return $this->formatterFormStep($request, $export, $alias); return $this->formatterFormStep($request, $export, $alias);
break;
case 'generate': case 'generate':
return $this->forwardToGenerate($request, $export, $alias); return $this->forwardToGenerate($request, $export, $alias);
break;
default: default:
throw $this->createNotFoundException("The given step '{$step}' is invalid"); throw $this->createNotFoundException("The given step '{$step}' is invalid");
} }
@ -214,10 +209,8 @@ class ExportController extends AbstractController
* @param string $alias * @param string $alias
* @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter' * @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter'
* @param mixed $step * @param mixed $step
*
* @return \Symfony\Component\Form\Form
*/ */
protected function createCreateFormExport($alias, $step, $data = []) protected function createCreateFormExport($alias, $step, $data = []): FormInterface
{ {
/** @var \Chill\MainBundle\Export\ExportManager $exportManager */ /** @var \Chill\MainBundle\Export\ExportManager $exportManager */
$exportManager = $this->exportManager; $exportManager = $this->exportManager;
@ -475,8 +468,6 @@ class ExportController extends AbstractController
* @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export * @param \Chill\MainBundle\Export\DirectExportInterface|\Chill\MainBundle\Export\ExportInterface $export
* @param string $alias * @param string $alias
* *
* @throws type
*
* @return Response * @return Response
*/ */
protected function selectCentersStep(Request $request, $export, $alias) protected function selectCentersStep(Request $request, $export, $alias)
@ -543,6 +534,8 @@ class ExportController extends AbstractController
} }
} }
} }
return '';
} }
/** /**

View File

@ -22,6 +22,7 @@ use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Controller\UserJobApiController; use Chill\MainBundle\Controller\UserJobApiController;
use Chill\MainBundle\Controller\UserJobController; use Chill\MainBundle\Controller\UserJobController;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\Doctrine\DQL\Age;
use Chill\MainBundle\Doctrine\DQL\Extract; use Chill\MainBundle\Doctrine\DQL\Extract;
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey; use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
use Chill\MainBundle\Doctrine\DQL\JsonAggregate; use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
@ -248,6 +249,7 @@ class ChillMainExtension extends Extension implements
'datetime_functions' => [ 'datetime_functions' => [
'EXTRACT' => Extract::class, 'EXTRACT' => Extract::class,
'TO_CHAR' => ToChar::class, 'TO_CHAR' => ToChar::class,
'AGE' => Age::class,
], ],
], ],
'hydrators' => [ 'hydrators' => [

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\DependencyInjection\CompilerPass; namespace Chill\MainBundle\DependencyInjection\CompilerPass;
use Chill\MainBundle\Export\ExportManager;
use LogicException; use LogicException;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -30,53 +31,19 @@ class ExportsCompilerPass implements CompilerPassInterface
{ {
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
if (!$container->has('Chill\MainBundle\Export\ExportManager')) { if (!$container->has(ExportManager::class)) {
throw new LogicException('service Chill\MainBundle\Export\ExportManager ' throw new LogicException('service ' . ExportManager::class . ' '
. 'is not defined. It is required by ExportsCompilerPass'); . 'is not defined. It is required by ExportsCompilerPass');
} }
$chillManagerDefinition = $container->findDefinition( $chillManagerDefinition = $container->findDefinition(
'Chill\MainBundle\Export\ExportManager' ExportManager::class
); );
$this->compileExports($chillManagerDefinition, $container);
$this->compileFilters($chillManagerDefinition, $container);
$this->compileAggregators($chillManagerDefinition, $container);
$this->compileFormatters($chillManagerDefinition, $container); $this->compileFormatters($chillManagerDefinition, $container);
$this->compileExportElementsProvider($chillManagerDefinition, $container); $this->compileExportElementsProvider($chillManagerDefinition, $container);
} }
private function compileAggregators(
Definition $chillManagerDefinition,
ContainerBuilder $container
) {
$taggedServices = $container->findTaggedServiceIds(
'chill.export_aggregator'
);
$knownAliases = [];
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes['alias'])) {
throw new LogicException("the 'alias' attribute is missing in your " .
"service '{$id}' definition");
}
if (array_search($attributes['alias'], $knownAliases, true)) {
throw new LogicException('There is already a chill.export_aggregator service with alias '
. $attributes['alias'] . '. Choose another alias.');
}
$knownAliases[] = $attributes['alias'];
$chillManagerDefinition->addMethodCall(
'addAggregator',
[new Reference($id), $attributes['alias']]
);
}
}
}
private function compileExportElementsProvider( private function compileExportElementsProvider(
Definition $chillManagerDefinition, Definition $chillManagerDefinition,
ContainerBuilder $container ContainerBuilder $container
@ -108,68 +75,6 @@ class ExportsCompilerPass implements CompilerPassInterface
} }
} }
private function compileExports(
Definition $chillManagerDefinition,
ContainerBuilder $container
) {
$taggedServices = $container->findTaggedServiceIds(
'chill.export'
);
$knownAliases = [];
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes['alias'])) {
throw new LogicException("the 'alias' attribute is missing in your " .
"service '{$id}' definition");
}
if (array_search($attributes['alias'], $knownAliases, true)) {
throw new LogicException('There is already a chill.export service with alias '
. $attributes['alias'] . '. Choose another alias.');
}
$knownAliases[] = $attributes['alias'];
$chillManagerDefinition->addMethodCall(
'addExport',
[new Reference($id), $attributes['alias']]
);
}
}
}
private function compileFilters(
Definition $chillManagerDefinition,
ContainerBuilder $container
) {
$taggedServices = $container->findTaggedServiceIds(
'chill.export_filter'
);
$knownAliases = [];
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
if (!isset($attributes['alias'])) {
throw new LogicException("the 'alias' attribute is missing in your " .
"service '{$id}' definition");
}
if (array_search($attributes['alias'], $knownAliases, true)) {
throw new LogicException('There is already a chill.export_filter service with alias '
. $attributes['alias'] . '. Choose another alias.');
}
$knownAliases[] = $attributes['alias'];
$chillManagerDefinition->addMethodCall(
'addFilter',
[new Reference($id), $attributes['alias']]
);
}
}
}
private function compileFormatters( private function compileFormatters(
Definition $chillManagerDefinition, Definition $chillManagerDefinition,
ContainerBuilder $container ContainerBuilder $container

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
class Age extends FunctionNode
{
private $value1;
private $value2;
public function getSql(SqlWalker $sqlWalker)
{
if (null !== $this->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);
}
}

View File

@ -27,7 +27,7 @@ class JsonbExistsInArray extends FunctionNode
return sprintf( return sprintf(
'%s ?? %s', '%s ?? %s',
$this->expr1->dispatch($sqlWalker), $this->expr1->dispatch($sqlWalker),
$sqlWalker->walkInputParameter($this->expr2) $this->expr2->dispatch($sqlWalker)
); );
} }

View File

@ -17,6 +17,7 @@ use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateIntervalType; use Doctrine\DBAL\Types\DateIntervalType;
use Exception; use Exception;
use LogicException;
use function count; use function count;
use function current; use function current;
use function preg_match; use function preg_match;
@ -40,7 +41,7 @@ class NativeDateIntervalType extends DateIntervalType
return $value->format(self::FORMAT); return $value->format(self::FORMAT);
} }
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']); throw ConversionException::conversionFailedInvalidType($value, 'string', ['null', 'DateInterval']);
} }
public function convertToPHPValue($value, AbstractPlatform $platform) public function convertToPHPValue($value, AbstractPlatform $platform)
@ -80,7 +81,7 @@ class NativeDateIntervalType extends DateIntervalType
protected function createConversionException($value, $exception = null) protected function createConversionException($value, $exception = null)
{ {
return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception); return ConversionException::conversionFailedFormat($value, 'string', 'xx year xx mons xx days 01:02:03', $exception);
} }
private function convertEntry(&$strings) private function convertEntry(&$strings)
@ -125,5 +126,7 @@ class NativeDateIntervalType extends DateIntervalType
return $intervalSpec; return $intervalSpec;
} }
throw new LogicException();
} }
} }

View File

@ -28,6 +28,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
*/ */
class Scope class Scope
{ {
/**
* @ORM\Column(type="boolean", nullable=false, options={"default": true})
*/
private bool $active = true;
/** /**
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
@ -88,6 +93,18 @@ class Scope
return $this->roleScopes; return $this->roleScopes;
} }
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active): Scope
{
$this->active = $active;
return $this;
}
/** /**
* @param $name * @param $name
* *

View File

@ -11,28 +11,22 @@ declare(strict_types=1);
namespace Chill\MainBundle\Export; namespace Chill\MainBundle\Export;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\HttpFoundation\Response;
interface DirectExportInterface extends ExportElementInterface interface DirectExportInterface extends ExportElementInterface
{ {
/** /**
* Generate the export. * Generate the export.
*
* @return \Symfony\Component\HttpFoundation\Response
*/ */
public function generate(array $acl, array $data = []); public function generate(array $acl, array $data = []): Response;
/** /**
* get a description, which will be used in UI (and translated). * get a description, which will be used in UI (and translated).
*
* @return string
*/ */
public function getDescription(); public function getDescription(): string;
/** /**
* authorized role. * authorized role.
*
* @return \Symfony\Component\Security\Core\Role\Role
*/ */
public function requiredRole(); public function requiredRole(): string;
} }

View File

@ -14,10 +14,8 @@ namespace Chill\MainBundle\Export;
use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Form\Type\Export\PickCenterType; use Chill\MainBundle\Form\Type\Export\PickCenterType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Generator; use Generator;
use InvalidArgumentException;
use LogicException; use LogicException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException; use RuntimeException;
@ -50,8 +48,6 @@ class ExportManager
private AuthorizationHelperInterface $authorizationHelper; private AuthorizationHelperInterface $authorizationHelper;
private EntityManagerInterface $em;
/** /**
* Collected Exports, injected by DI. * Collected Exports, injected by DI.
* *
@ -82,16 +78,28 @@ class ExportManager
public function __construct( public function __construct(
LoggerInterface $logger, LoggerInterface $logger,
EntityManagerInterface $em,
AuthorizationCheckerInterface $authorizationChecker, AuthorizationCheckerInterface $authorizationChecker,
AuthorizationHelperInterface $authorizationHelper, AuthorizationHelperInterface $authorizationHelper,
TokenStorageInterface $tokenStorage TokenStorageInterface $tokenStorage,
iterable $exports,
iterable $aggregators,
iterable $filters
//iterable $formatters,
//iterable $exportElementProvider
) { ) {
$this->logger = $logger; $this->logger = $logger;
$this->em = $em;
$this->authorizationChecker = $authorizationChecker; $this->authorizationChecker = $authorizationChecker;
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->user = $tokenStorage->getToken()->getUser(); $this->user = $tokenStorage->getToken()->getUser();
$this->exports = iterator_to_array($exports);
$this->aggregators = iterator_to_array($aggregators);
$this->filters = iterator_to_array($filters);
// NOTE: PHP crashes on the next line (exit error code 11). This is desactivated until further investigation
//$this->formatters = iterator_to_array($formatters);
//foreach ($exportElementProvider as $prefix => $provider) {
// $this->addExportElementsProvider($provider, $prefix);
//}
} }
/** /**
@ -141,52 +149,17 @@ class ExportManager
} }
} }
/**
* add an aggregator.
*
* @internal used by DI
*
* @param string $alias
*/
public function addAggregator(AggregatorInterface $aggregator, $alias)
{
$this->aggregators[$alias] = $aggregator;
}
/**
* add an export.
*
* @internal used by DI
*
* @param DirectExportInterface|ExportInterface $export
* @param type $alias
*/
public function addExport($export, $alias)
{
if ($export instanceof ExportInterface || $export instanceof DirectExportInterface) {
$this->exports[$alias] = $export;
} else {
throw new InvalidArgumentException(sprintf(
'The export with alias %s '
. 'does not implements %s or %s.',
$alias,
ExportInterface::class,
DirectExportInterface::class
));
}
}
public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix) public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix)
{ {
foreach ($provider->getExportElements() as $suffix => $element) { foreach ($provider->getExportElements() as $suffix => $element) {
$alias = $prefix . '_' . $suffix; $alias = $prefix . '_' . $suffix;
if ($element instanceof ExportInterface) { if ($element instanceof ExportInterface) {
$this->addExport($element, $alias); $this->exports[$alias] = $element;
} elseif ($element instanceof FilterInterface) { } elseif ($element instanceof FilterInterface) {
$this->addFilter($element, $alias); $this->filters[$alias] = $element;
} elseif ($element instanceof AggregatorInterface) { } elseif ($element instanceof AggregatorInterface) {
$this->addAggregator($element, $alias); $this->aggregators[$alias] = $element;
} elseif ($element instanceof FormatterInterface) { } elseif ($element instanceof FormatterInterface) {
$this->addFormatter($element, $alias); $this->addFormatter($element, $alias);
} else { } else {
@ -196,24 +169,12 @@ class ExportManager
} }
} }
/**
* add a Filter.
*
* @internal Normally used by the dependency injection
*
* @param string $alias
*/
public function addFilter(FilterInterface $filter, $alias)
{
$this->filters[$alias] = $filter;
}
/** /**
* add a formatter. * add a formatter.
* *
* @internal used by DI * @internal used by DI
* *
* @param type $alias * @param string $alias
*/ */
public function addFormatter(FormatterInterface $formatter, $alias) public function addFormatter(FormatterInterface $formatter, $alias)
{ {
@ -231,7 +192,6 @@ class ExportManager
public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData) public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData)
{ {
$export = $this->getExport($exportAlias); $export = $this->getExport($exportAlias);
//$qb = $this->em->createQueryBuilder();
$centers = $this->getPickedCenters($pickedCentersData); $centers = $this->getPickedCenters($pickedCentersData);
if ($export instanceof DirectExportInterface) { if ($export instanceof DirectExportInterface) {

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PickLocationTypeType extends AbstractType
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'class' => LocationType::class,
'choice_label' => function (LocationType $type) {
return $this->translatableStringHelper->localize($type->getTitle());
},
'placeholder' => 'Pick a location type',
'required' => false,
'attr' => ['class' => 'select2'],
'label' => 'Location type',
'multiple' => false,
])
->setAllowedTypes('multiple', ['bool']);
}
public function getParent(): string
{
return EntityType::class;
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PickUserLocationType extends AbstractType
{
private LocationRepository $locationRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper, LocationRepository $locationRepository)
{
$this->translatableStringHelper = $translatableStringHelper;
$this->locationRepository = $locationRepository;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'class' => Location::class,
'choices' => $this->locationRepository->findByPublicLocations(),
'choice_label' => function (Location $entity) {
return $entity->getName() ?
$entity->getName() . ' (' . $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()) . ')' :
$this->translatableStringHelper->localize($entity->getLocationType()->getTitle());
},
'placeholder' => 'Pick a location',
'required' => false,
'attr' => ['class' => 'select2'],
'label' => 'Current location',
'multiple' => false,
])
->setAllowedTypes('multiple', ['bool']);
}
public function getParent(): string
{
return EntityType::class;
}
}

View File

@ -15,9 +15,9 @@ use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; 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\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType; 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\Form\FormView;
use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver; 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\Role\Role;
use Symfony\Component\Security\Core\Security;
use function array_map; use Symfony\Component\Security\Core\Security;
use function count; use function count;
/** /**
@ -44,47 +42,37 @@ use function count;
*/ */
class ScopePickerType extends AbstractType class ScopePickerType extends AbstractType
{ {
protected AuthorizationHelperInterface $authorizationHelper; private AuthorizationHelperInterface $authorizationHelper;
/** private Security $security;
* @var ScopeRepository
*/
protected $scopeRepository;
protected Security $security; private TranslatableStringHelperInterface $translatableStringHelper;
/**
* @var TokenStorageInterface
*/
protected $tokenStorage;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
public function __construct( public function __construct(
AuthorizationHelperInterface $authorizationHelper, AuthorizationHelperInterface $authorizationHelper,
TokenStorageInterface $tokenStorage,
ScopeRepository $scopeRepository,
Security $security, Security $security,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelperInterface $translatableStringHelper
) { ) {
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage;
$this->scopeRepository = $scopeRepository;
$this->security = $security; $this->security = $security;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$items = $this->authorizationHelper->getReachableScopes( $items = array_filter(
$this->authorizationHelper->getReachableScopes(
$this->security->getUser(), $this->security->getUser(),
$options['role'] instanceof Role ? $options['role']->getRole() : $options['role'], $options['role'] instanceof Role ? $options['role']->getRole() : $options['role'],
$options['center'] $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)) { if (1 !== count($items)) {
$builder->add('scope', EntityType::class, [ $builder->add('scope', EntityType::class, [
'class' => Scope::class, 'class' => Scope::class,
@ -123,35 +111,4 @@ class ScopePickerType extends AbstractType
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', ['string', Role::class]); ->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;
}
} }

View File

@ -11,39 +11,14 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form; namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Form\Type\PickUserLocationType;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class UserCurrentLocationType extends AbstractType class UserCurrentLocationType extends AbstractType
{ {
private LocationRepository $locationRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper, LocationRepository $locationRepository)
{
$this->translatableStringHelper = $translatableStringHelper;
$this->locationRepository = $locationRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder $builder->add('currentLocation', PickUserLocationType::class);
->add('currentLocation', EntityType::class, [
'class' => Location::class,
'choices' => $this->locationRepository->findByPublicLocations(),
'choice_label' => function (Location $entity) {
return $entity->getName() ?
$entity->getName() . ' (' . $this->translatableStringHelper->localize($entity->getLocationType()->getTitle()) . ')' :
$this->translatableStringHelper->localize($entity->getLocationType()->getTitle());
},
'placeholder' => 'Pick a location',
'required' => false,
'attr' => ['class' => 'select2'],
]);
} }
} }

View File

@ -43,6 +43,15 @@ final class ScopeRepository implements ScopeRepositoryInterface
return $this->repository->findAll(); return $this->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 $limit
* @param mixed|null $offset * @param mixed|null $offset

View File

@ -22,10 +22,15 @@ interface ScopeRepositoryInterface extends ObjectRepository
public function find($id, $lockMode = null, $lockVersion = null): ?Scope; public function find($id, $lockMode = null, $lockVersion = null): ?Scope;
/** /**
* @return Scope[] * @return array|Scope[]
*/ */
public function findAll(): array; public function findAll(): array;
/**
* @return array|Scope[]
*/
public function findAllActive(): array;
/** /**
* @param null|mixed $limit * @param null|mixed $limit
* @param null|mixed $offset * @param null|mixed $offset

View File

@ -14,9 +14,8 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
class UserJobRepository implements ObjectRepository class UserJobRepository implements UserJobRepositoryInterface
{ {
private EntityRepository $repository; private EntityRepository $repository;
@ -38,6 +37,11 @@ class UserJobRepository implements ObjectRepository
return $this->repository->findAll(); return $this->repository->findAll();
} }
public function findAllActive(): array
{
return $this->repository->findBy(['active' => true]);
}
/** /**
* @param mixed|null $limit * @param mixed|null $limit
* @param mixed|null $offset * @param mixed|null $offset
@ -49,12 +53,12 @@ class UserJobRepository implements ObjectRepository
return $this->repository->findBy($criteria, $orderBy, $limit, $offset); return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
} }
public function findOneBy(array $criteria) public function findOneBy(array $criteria): ?UserJob
{ {
return $this->repository->findOneBy($criteria); return $this->repository->findOneBy($criteria);
} }
public function getClassName() public function getClassName(): string
{ {
return UserJob::class; return UserJob::class;
} }

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\UserJob;
use Doctrine\Persistence\ObjectRepository;
interface UserJobRepositoryInterface extends ObjectRepository
{
public function find($id): ?UserJob;
/**
* @return array|UserJob[]
*/
public function findAll(): array;
/**
* @return array|UserJob[]
*/
public function findAllActive(): array;
/**
* @param mixed|null $limit
* @param mixed|null $offset
*
* @return array|object[]|UserJob[]
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null);
public function findOneBy(array $criteria): ?UserJob;
public function getClassName(): string;
}

View File

@ -1,12 +1,5 @@
<?php <?php
/**
* Chill is a software for social workers.
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1); declare(strict_types=1);
/* /*

View File

@ -119,7 +119,3 @@ document.addEventListener('DOMContentLoaded', function(e) {
loadDynamicPicker(document) loadDynamicPicker(document)
}) })

View File

@ -20,7 +20,12 @@
{% block title %}{{ export.title|trans }}{% endblock %} {% block title %}{{ export.title|trans }}{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('page_export') }} {{ encore_entry_script_tags('page_export') }}
{% if export_alias == 'count_social_work_actions' %} {% if export_alias == 'count_social_work_actions' %}
{{ encore_entry_script_tags('vue_export_action_goal_result') }} {{ encore_entry_script_tags('vue_export_action_goal_result') }}

View File

@ -29,6 +29,18 @@ use function is_string;
*/ */
abstract class AbstractAggregatorTest extends KernelTestCase abstract class AbstractAggregatorTest extends KernelTestCase
{ {
/**
* provide data for `testAliasDidNotDisappears`.
*/
public function dataProviderAliasDidNotDisappears()
{
foreach ($this->getQueryBuilders() as $qb) {
foreach ($this->getFormData() as $data) {
yield [clone $qb, $data];
}
}
}
/** /**
* provide data for `testAlterQuery`. * provide data for `testAlterQuery`.
*/ */
@ -95,6 +107,28 @@ abstract class AbstractAggregatorTest extends KernelTestCase
*/ */
abstract public function getQueryBuilders(); abstract public function getQueryBuilders();
/**
* Compare aliases array before and after that aggregator alter query.
*
* @dataProvider dataProviderAliasDidNotDisappears
*
* @return void
*/
public function testAliasDidNotDisappears(QueryBuilder $qb, array $data)
{
$aliases = $qb->getAllAliases();
$this->getAggregator()->alterQuery($qb, $data);
$alteredQuery = $qb->getAllAliases();
$this->assertGreaterThanOrEqual(count($aliases), count($alteredQuery));
foreach ($aliases as $alias) {
$this->assertContains($alias, $alteredQuery);
}
}
/** /**
* test the alteration of query by the filter. * test the alteration of query by the filter.
* *

View File

@ -40,6 +40,18 @@ abstract class AbstractFilterTest extends KernelTestCase
$this->prophet = $this->getProphet(); $this->prophet = $this->getProphet();
} }
/**
* provide data for `testAliasDidNotDisappears`.
*/
public function dataProviderAliasDidNotDisappears()
{
foreach ($this->getQueryBuilders() as $qb) {
foreach ($this->getFormData() as $data) {
yield [clone $qb, $data];
}
}
}
public function dataProviderAlterQuery() public function dataProviderAlterQuery()
{ {
foreach ($this->getQueryBuilders() as $qb) { foreach ($this->getQueryBuilders() as $qb) {
@ -87,6 +99,28 @@ abstract class AbstractFilterTest extends KernelTestCase
*/ */
abstract public function getQueryBuilders(); abstract public function getQueryBuilders();
/**
* Compare aliases array before and after that filter alter query.
*
* @dataProvider dataProviderAliasDidNotDisappears
*
* @return void
*/
public function testAliasDidNotDisappears(QueryBuilder $qb, array $data)
{
$aliases = $qb->getAllAliases();
$this->getFilter()->alterQuery($qb, $data);
$alteredQuery = $qb->getAllAliases();
$this->assertGreaterThanOrEqual(count($aliases), count($alteredQuery));
foreach ($aliases as $alias) {
$this->assertContains($alias, $alteredQuery);
}
}
/** /**
* test the alteration of query by the filter. * test the alteration of query by the filter.
* *

View File

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Doctrine\DQL;
use Chill\MainBundle\Entity\Address;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
final class AgeTest extends KernelTestCase
{
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
$this->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);
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Doctrine\DQL;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
final class JsonbExistsInArrayTest extends KernelTestCase
{
private EntityManagerInterface $em;
protected function setUp(): void
{
self::bootKernel();
$this->em = self::$container->get(EntityManagerInterface::class);
}
public function testDQLFunctionWorks()
{
$result = $this->em
->createQuery('SELECT JSONB_EXISTS_IN_ARRAY(u.attributes, :param) FROM ' . User::class . ' u')
->setParameter('param', 'fr')
->getResult();
$this->assertIsArray($result);
}
}

View File

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Form\Type;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Security\Core\Security;
/**
* @internal
* @coversNothing
*/
final class ScopePickerTypeTest extends TypeTestCase
{
use ProphecyTrait;
public function testBuildOneScopeIsSuccessful()
{
$form = $this->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()),
];
}
}

View File

@ -91,6 +91,14 @@ services:
Chill\MainBundle\Export\ExportManager: Chill\MainBundle\Export\ExportManager:
autoconfigure: true autoconfigure: true
autowire: true autowire: true
arguments:
$exports: !tagged_iterator { tag: chill.export, index_by: alias }
$aggregators: !tagged_iterator { tag: chill.export_aggregator, index_by: alias }
$filters: !tagged_iterator { tag: chill.export_filter, index_by: alias }
# for an unknown reason, iterator_to_array($formatter) cause a segmentation fault error (php-fpm code 11). removed temporarily
# $formatters: !tagged_iterator { tag: chill.export_formatter, index_by: alias }
# remove until we can properly test it
# $exportElementProvider: !tagged_iterator { tag: chill.export_elements_provider, index_by: prefix }
Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher' Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher'

View File

@ -122,8 +122,6 @@ services:
Chill\MainBundle\Form\Type\PickAddressType: ~ Chill\MainBundle\Form\Type\PickAddressType: ~
Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer: ~
Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer: Chill\MainBundle\Form\DataTransform\AddressToIdDataTransformer:
autoconfigure: true autoconfigure: true
autowire: true autowire: true
@ -132,10 +130,6 @@ services:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Chill\MainBundle\Form\UserCurrentLocationType:
autowire: true
autoconfigure: true
Chill\MainBundle\Form\Type\LocationFormType: ~ Chill\MainBundle\Form\Type\LocationFormType: ~
Chill\MainBundle\Form\WorkflowStepType: ~ Chill\MainBundle\Form\WorkflowStepType: ~

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20221010142417 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->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');
}
}

View File

@ -189,6 +189,7 @@ Main scope: Cercle
Main center: Centre Main center: Centre
user job: Métier de l'utilisateur user job: Métier de l'utilisateur
Job: Métier Job: Métier
Jobs: Métiers
Choose a main center: Choisir un centre Choose a main center: Choisir un centre
Choose a main scope: Choisir un cercle Choose a main scope: Choisir un cercle
choose a job: Choisir un métier choose a job: Choisir un métier
@ -227,6 +228,7 @@ never: jamais
Create a new location: Créer une nouvelle localisation Create a new location: Créer une nouvelle localisation
Location list: Liste des localisations Location list: Liste des localisations
Location type: Type de localisation Location type: Type de localisation
Pick a location type: Choisir un type de localisation
Phonenumber1: Numéro de téléphone Phonenumber1: Numéro de téléphone
Phonenumber2: Autre numéro de téléphone Phonenumber2: Autre numéro de téléphone
Location configuration: Configuration des localisations Location configuration: Configuration des localisations

View File

@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -39,9 +40,11 @@ class SocialWorkEvaluationApiController extends AbstractController
*/ */
public function listEvaluationBySocialAction(SocialAction $action): Response 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->getCurrentPageFirstItemNumber(),
$pagination->getItemsPerPage() $pagination->getItemsPerPage()
); );

View File

@ -22,6 +22,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodLocationHistory; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodLocationHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment;
@ -341,6 +342,12 @@ class AccompanyingPeriod implements
*/ */
private string $step = self::STEP_DRAFT; 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}) * @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
*/ */
@ -395,8 +402,7 @@ class AccompanyingPeriod implements
*/ */
public function __construct(?DateTime $dateOpening = null) public function __construct(?DateTime $dateOpening = null)
{ {
$this->setOpeningDate($dateOpening ?? new DateTime('now')); $this->calendars = new ArrayCollection(); // TODO we cannot add a dependency between AccompanyingPeriod and calendars
$this->calendars = new ArrayCollection();
$this->participations = new ArrayCollection(); $this->participations = new ArrayCollection();
$this->scopes = new ArrayCollection(); $this->scopes = new ArrayCollection();
$this->socialIssues = new ArrayCollection(); $this->socialIssues = new ArrayCollection();
@ -405,6 +411,8 @@ class AccompanyingPeriod implements
$this->resources = new ArrayCollection(); $this->resources = new ArrayCollection();
$this->userHistories = new ArrayCollection(); $this->userHistories = new ArrayCollection();
$this->locationHistories = new ArrayCollection(); $this->locationHistories = new ArrayCollection();
$this->stepHistories = new ArrayCollection();
$this->setOpeningDate($dateOpening ?? new DateTime('now'));
} }
/** /**
@ -995,6 +1003,11 @@ class AccompanyingPeriod implements
return $this->step; return $this->step;
} }
public function getStepHistories(): Collection
{
return $this->stepHistories;
}
public function getUser(): ?User public function getUser(): ?User
{ {
return $this->user; return $this->user;
@ -1263,8 +1276,12 @@ class AccompanyingPeriod implements
*/ */
public function setOpeningDate($openingDate) public function setOpeningDate($openingDate)
{ {
if ($this->openingDate !== $openingDate) {
$this->openingDate = $openingDate; $this->openingDate = $openingDate;
$this->ensureStepContinuity();
}
return $this; return $this;
} }
@ -1362,6 +1379,14 @@ class AccompanyingPeriod implements
$this->bootPeriod(); $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; return $this;
} }
@ -1402,6 +1427,17 @@ class AccompanyingPeriod implements
return $this; 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 private function bootPeriod(): void
{ {
// first location history // first location history
@ -1413,6 +1449,43 @@ class AccompanyingPeriod implements
$this->addLocationHistory($locationHistory); $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 private function setRequestorPerson(?Person $requestorPerson = null): self
{ {
$this->requestorPerson = $requestorPerson; $this->requestorPerson = $requestorPerson;

View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
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\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("chill_person_accompanying_period_step_history")
*/
class AccompanyingPeriodStepHistory implements TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
*/
private ?DateTimeImmutable $endDate = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
*/
private AccompanyingPeriod $period;
/**
* @ORM\Column(type="date_immutable")
*/
private ?DateTimeImmutable $startDate = null;
/**
* @ORM\Column(type="text", nullable=false)
*/
private string $step;
public function getEndDate(): ?DateTimeImmutable
{
return $this->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;
}
}

View File

@ -36,7 +36,7 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
* inversedBy="comments") * inversedBy="comments")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE") * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/ */
private ?AccompanyingPeriod $accompanyingPeriod; private ?AccompanyingPeriod $accompanyingPeriod = null;
/** /**
* @ORM\Column(type="text") * @ORM\Column(type="text")

View File

@ -1488,7 +1488,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
$participation = $this->participationsContainAccompanyingPeriod($accompanyingPeriod); $participation = $this->participationsContainAccompanyingPeriod($accompanyingPeriod);
if (!null === $participation) { if (!null === $participation) {
$participation->setEndDate(DateTimeImmutable::class); $participation->setEndDate(new DateTime());
$this->accompanyingPeriodParticipations->removeElement($participation); $this->accompanyingPeriodParticipations->removeElement($participation);
} }
} }
@ -1569,8 +1569,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*/ */
public function setCenter(Center $center): self public function setCenter(Center $center): self
{ {
$this->center = $center;
$modification = new DateTimeImmutable('now'); $modification = new DateTimeImmutable('now');
foreach ($this->centerHistory as $centerHistory) { foreach ($this->centerHistory as $centerHistory) {

View File

@ -26,6 +26,11 @@ use Symfony\Component\Serializer\Annotation as Serializer;
*/ */
class Evaluation class Evaluation
{ {
/**
* @ORM\Column(type="boolean", nullable=false, options={"default": true})
*/
private bool $active = true;
/** /**
* @ORM\Column(type="dateinterval", nullable=true, options={"default": null}) * @ORM\Column(type="dateinterval", nullable=true, options={"default": null})
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
@ -114,6 +119,11 @@ class Evaluation
return $this->url; return $this->url;
} }
public function isActive(): bool
{
return $this->active;
}
/** /**
* @return $this * @return $this
* *
@ -128,6 +138,13 @@ class Evaluation
return $this; return $this;
} }
public function setActive(bool $active): Evaluation
{
$this->active = $active;
return $this;
}
public function setDelay(?DateInterval $delay): self public function setDelay(?DateInterval $delay): self
{ {
$this->delay = $delay; $this->delay = $delay;

View File

@ -228,6 +228,22 @@ class SocialAction
return $descendants; return $descendants;
} }
/**
* @param Collection|SocialAction[] $socialActions
*/
public static function getDescendantsWithThisForActions($socialActions): Collection
{
$unique = [];
foreach ($socialActions as $action) {
foreach ($action->getDescendantsWithThis() as $child) {
$unique[spl_object_hash($child)] = $child;
}
}
return new ArrayCollection(array_values($unique));
}
public function getEvaluations(): Collection public function getEvaluations(): Collection
{ {
return $this->evaluations; return $this->evaluations;
@ -274,6 +290,11 @@ class SocialAction
return $this->title; return $this->title;
} }
public function hasChildren(): bool
{
return 0 < $this->getChildren()->count();
}
public function hasParent(): bool public function hasParent(): bool
{ {
return $this->getParent() instanceof self; return $this->getParent() instanceof self;
@ -369,6 +390,8 @@ class SocialAction
{ {
$this->parent = $parent; $this->parent = $parent;
$parent->addChild($this);
return $this; return $this;
} }

View File

@ -71,11 +71,17 @@ class SocialIssue
$this->socialActions = new ArrayCollection(); $this->socialActions = new ArrayCollection();
} }
/**
* @internal use @see{SocialIssue::setParent} instead
*
* @param SocialIssue $child
*
* @return $this
*/
public function addChild(self $child): self public function addChild(self $child): self
{ {
if (!$this->children->contains($child)) { if (!$this->children->contains($child)) {
$this->children[] = $child; $this->children[] = $child;
$child->setParent($this);
} }
return $this; return $this;
@ -215,6 +221,22 @@ class SocialIssue
return $descendants; return $descendants;
} }
/**
* @param array|SocialIssue[] $socialIssues
*/
public static function getDescendantsWithThisForIssues(array $socialIssues): Collection
{
$unique = [];
foreach ($socialIssues as $issue) {
foreach ($issue->getDescendantsWithThis() as $child) {
$unique[spl_object_hash($child)] = $child;
}
}
return new ArrayCollection(array_values($unique));
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -262,6 +284,11 @@ class SocialIssue
return $this->title; return $this->title;
} }
public function hasChildren(): bool
{
return 0 < $this->getChildren()->count();
}
public function hasParent(): bool public function hasParent(): bool
{ {
return null !== $this->parent; return null !== $this->parent;
@ -329,6 +356,8 @@ class SocialIssue
{ {
$this->parent = $parent; $this->parent = $parent;
$parent->addChild($this);
return $this; return $this;
} }

View File

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\Household\HouseholdCompositionTypeRepositoryInterface;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByHouseholdCompositionAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_by_household_compo_agg';
private HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->householdCompositionTypeRepository = $householdCompositionTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
if (!in_array('acppart', $qb->getAllAliases(), true)) {
$qb->leftJoin('acp.participations', 'acppart');
}
$qb
->leftJoin(
HouseholdMember::class,
"{$p}_hm",
Join::WITH,
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_hm"),
$qb->expr()->andX(
$qb->expr()->lte("{$p}_hm.startDate", ":{$p}_date"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_hm.endDate"),
$qb->expr()->gt("{$p}_hm.endDate", ":{$p}_date")
)
)
)
)
->leftJoin(
HouseholdComposition::class,
"{$p}_compo",
Join::WITH,
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_compo"),
$qb->expr()->andX(
$qb->expr()->lte("{$p}_compo.startDate", ":{$p}_date"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_compo.endDate"),
$qb->expr()->gt("{$p}_compo.endDate", ":{$p}_date")
)
)
)
)
->addSelect("IDENTITY({$p}_compo.householdCompositionType) AS {$p}_select")
->setParameter("{$p}_date", $data['date_calc'])
->addGroupBy("{$p}_select");
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_calc', ChillDateType::class, [
'label' => 'export.aggregator.course.by_household_composition.Calc date',
'input_format' => 'datetime_immutable',
'data' => new \DateTimeImmutable('now'),
]);
}
public function getLabels($key, array $values, $data)
{
return function ($value) {
if ('_header' === $value) {
return 'export.aggregator.course.by_household_composition.Household composition';
}
if (null === $value) {
return '';
}
if (null === $o = $this->householdCompositionTypeRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($o->getLabel());
};
}
public function getQueryKeys($data)
{
return [self::PREFIX . '_select'];
}
public function getTitle()
{
return 'export.aggregator.course.by_household_composition.Group course by household composition';
}
}

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
@ -65,8 +66,6 @@ class ConfidentialAggregator implements AggregatorInterface
default: default:
throw new LogicException(sprintf('The value %s is not valid', $value)); throw new LogicException(sprintf('The value %s is not valid', $value));
} }
return $value;
}; };
} }

View File

@ -13,12 +13,21 @@ namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
final class DurationAggregator implements AggregatorInterface final class DurationAggregator implements AggregatorInterface
{ {
private const CHOICES = [
'month',
'week',
'day',
];
private TranslatorInterface $translator; private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator) public function __construct(TranslatorInterface $translator)
@ -33,19 +42,31 @@ final class DurationAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$qb->addSelect( switch ($data['precision']) {
' case 'day':
(acp.closingDate - acp.openingDate +15) *12/365 $qb->addSelect('(COALESCE(acp.closingDate, :now) - acp.openingDate) AS duration_aggregator');
AS duration_aggregator'
);
// TODO Pour avoir un interval plus précis (nécessaire ?): break;
// 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)
$qb->addGroupBy('duration_aggregator'); case 'week':
$qb->addOrderBy('duration_aggregator'); $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 public function applyOn(): string
@ -55,25 +76,27 @@ final class DurationAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder) 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) public function getLabels($key, array $values, $data)
{ {
return function ($value): ?string { return static function ($value) use ($data) {
if ('_header' === $value) { if ('_header' === $value) {
return 'Rounded month duration'; return 'export.aggregator.course.duration.' . $data['precision'];
} }
if (null === $value) { if (null === $value) {
return $this->translator->trans('current duration'); // when closingDate is null return 0;
} }
if (0 === $value) { return $value;
return $this->translator->trans('duration 0 month');
}
return $value . $this->translator->trans(' months');
}; };
} }

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
@ -65,8 +66,6 @@ class EmergencyAggregator implements AggregatorInterface
default: default:
throw new LogicException(sprintf('The value %s is not valid', $value)); throw new LogicException(sprintf('The value %s is not valid', $value));
} }
return $value;
}; };
} }

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;

View File

@ -12,15 +12,20 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators; namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
final class ReferrerAggregator implements AggregatorInterface final class ReferrerAggregator implements AggregatorInterface
{ {
private const A = 'acp_ref_agg_uhistory';
private const P = 'acp_ref_agg_date';
private UserRender $userRender; private UserRender $userRender;
private UserRepository $userRepository; private UserRepository $userRepository;
@ -40,12 +45,23 @@ final class ReferrerAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
if (!in_array('acpuser', $qb->getAllAliases(), true)) { $qb
$qb->leftJoin('acp.user', 'acpuser'); ->addSelect('IDENTITY(' . self::A . '.user) AS referrer_aggregator')
} ->addGroupBy('referrer_aggregator')
->leftJoin('acp.userHistories', self::A)
$qb->addSelect('acpuser.id AS referrer_aggregator'); ->andWhere(
$qb->addGroupBy('referrer_aggregator'); $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 public function applyOn(): string
@ -55,7 +71,13 @@ final class ReferrerAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder) 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) public function getLabels($key, array $values, $data)

View File

@ -12,19 +12,21 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators; namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
//use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use DateTime; use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface; 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; private TranslatorInterface $translator;
public function __construct( public function __construct(
@ -40,30 +42,26 @@ final class StepAggregator implements AggregatorInterface //, FilterInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$qb->addSelect('acp.step AS step_aggregator'); if (!in_array(self::A, $qb->getAllAliases(), true)) {
$qb->addGroupBy('step_aggregator'); $qb->leftJoin('acp.stepHistories', self::A);
/*
// 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);
} }
$qb->add('where', $where); $qb
$qb->setParameter('ondate', $data['on_date'], Types::DATE_MUTABLE); ->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 public function applyOn(): string
@ -94,8 +92,11 @@ final class StepAggregator implements AggregatorInterface //, FilterInterface
case '_header': case '_header':
return 'Step'; return 'Step';
case null:
return '';
default: default:
throw new LogicException(sprintf('The value %s is not valid', $value)); return $value;
} }
}; };
} }

View File

@ -23,6 +23,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
final class CountryOfBirthAggregator implements AggregatorInterface, ExportElementValidatedInterface final class CountryOfBirthAggregator implements AggregatorInterface, ExportElementValidatedInterface
{ {
@ -83,16 +84,12 @@ final class CountryOfBirthAggregator implements AggregatorInterface, ExportEleme
. ' is not known.'); . ' is not known.');
} }
if (!in_array('countryOfBirth', $qb->getAllAliases(), true)) {
$qb->leftJoin('person.countryOfBirth', 'countryOfBirth'); $qb->leftJoin('person.countryOfBirth', 'countryOfBirth');
}
// add group by // add group by
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('country_of_birth_aggregator'); $qb->addGroupBy('country_of_birth_aggregator');
} else {
$qb->groupBy('country_of_birth_aggregator');
}
} }
public function applyOn() public function applyOn()

View File

@ -70,14 +70,7 @@ final class HouseholdPositionAggregator implements AggregatorInterface, ExportEl
$qb->setParameter('date', $data['date_position']); $qb->setParameter('date', $data['date_position']);
$qb->addSelect('IDENTITY(householdmember.position) AS household_position_aggregator'); $qb->addSelect('IDENTITY(householdmember.position) AS household_position_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('household_position_aggregator'); $qb->addGroupBy('household_position_aggregator');
} else {
$qb->groupBy('household_position_aggregator');
}
} }
public function applyOn() public function applyOn()

View File

@ -43,14 +43,7 @@ final class MaritalStatusAggregator implements AggregatorInterface
} }
$qb->addSelect('personmarital.id as marital_status_aggregator'); $qb->addSelect('personmarital.id as marital_status_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('marital_status_aggregator'); $qb->addGroupBy('marital_status_aggregator');
} else {
$qb->groupBy('marital_status_aggregator');
}
} }
public function applyOn() public function applyOn()

View File

@ -83,15 +83,7 @@ final class NationalityAggregator implements AggregatorInterface, ExportElementV
} }
$qb->leftJoin('person.nationality', 'nationality'); $qb->leftJoin('person.nationality', 'nationality');
// add group by
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('nationality_aggregator'); $qb->addGroupBy('nationality_aggregator');
} else {
$qb->groupBy('nationality_aggregator');
}
} }
public function applyOn() public function applyOn()

View File

@ -75,9 +75,9 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
return ['export_result']; return ['export_result'];
} }
public function getResult($qb, $data) public function getResult($query, $data)
{ {
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
} }
public function getTitle(): string public function getTitle(): string
@ -99,6 +99,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
$qb = $this->repository->createQueryBuilder('acp'); $qb = $this->repository->createQueryBuilder('acp');
$qb $qb
->andWhere('acp.step != :count_acp_step')
->andWhere( ->andWhere(
$qb->expr()->exists( $qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part '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); ->setParameter('authorized_centers', $centers);
$qb->select('COUNT(DISTINCT acp.id) AS export_result'); $qb->select('COUNT(DISTINCT acp.id) AS export_result');

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